AndroidInject項目使用動態代理增加對網絡請求的支持


以下內容為原創,歡迎轉載,轉載請注明

來自天天博客:http://www.cnblogs.com/tiantianbyconan/p/3540427.html

 

AndroidInject項目是我寫的一個使用注解注入來簡化代碼的開源項目

https://github.com/wangjiegulu/androidInject

今天新增功能如下:

1. 增加@AIScreenSize注解,作用於屬性,用於注入當前設備的屏幕大小(寬高)
2. 增加對網絡請求的支持,使用動態代理實現:@AIGet注解,作用於接口方法,表示以GET來請求url;@AIPost注解,作用於接口方法,表示以POST來請求url;@AIParam,用於注入請求參數
3. 增加@AINetWorker注解,作用於屬性,用於注入網絡請求服務
4. 增加GET或POST請求時請求參數可使用Params類傳入,簡化代碼

 

主要執行代碼如下:

用@AINetWorker注解注入NetWorker接口的子類代理(動態代理模式):

1 @AINetWorker
2 private PersonWorker personWorker;

然后啟動線程,在線程中調用進行網絡請求:

1 new Thread(new Runnable() {
2     @Override
3     public void run() {
4 //        RetMessage<Person> retMsg = personWorker.getPersonsForGet("a1", "b1", "c1");
5 //        RetMessage<Person> retMsg = personWorker.getPersonsForGet2(new Params().add("aa", "a1").add("bb", "b1").add("cc", "c1"));
6         RetMessage<Person> retMsg = personWorker.getPersonsForPost2(new Params().add("aa", "a1").add("bb", "b1").add("cc", "c1"));
7         System.out.println(retMsg.getList().toString());
8     }
9 }).start();

請求的結果封裝在RetMessage類中(AndroidInject框架所作的事就是執行Get或者Post請求,獲得返回結果,然后json解析后封裝在RetMessage中):

package com.wangjie.androidinject.annotation.core.net;

import com.google.gson.Gson;

import java.util.List;

/**
 * Json響應結果包裝類
 * Created with IntelliJ IDEA.
 * Author: wangjie  email:tiantian.china.2@gmial.com
 * Date: 14-2-7
 * Time: 下午4:25
 */
public class RetMessage<T>
{
    private int resultCode; // 結果碼,必須包含

    private List<T> list; // 返回的數據

    private T obj; // 返回的數據

    private Integer size; // 返回數據長度

    private String errorMessage; // 返回錯誤信息

    public String toJson(){
        return new Gson().toJson(this);
    }
    // getter和setter方法省略...
}

接下來看下PersonWorker接口中所作的事情:

 1 package com.wangjie.androidinject;
 2 
 3 import com.wangjie.androidinject.annotation.annotations.net.AIGet;
 4 import com.wangjie.androidinject.annotation.annotations.net.AIParam;
 5 import com.wangjie.androidinject.annotation.annotations.net.AIPost;
 6 import com.wangjie.androidinject.annotation.core.net.RetMessage;
 7 import com.wangjie.androidinject.annotation.util.Params;
 8 import com.wangjie.androidinject.model.Person;
 9 
10 /**
11  * Created with IntelliJ IDEA.
12  * Author: wangjie  email:tiantian.china.2@gmail.com
13  * Date: 14-2-7
14  * Time: 下午1:44
15  */
16 public interface PersonWorker {
17     @AIGet("http://192.168.2.198:8080/HelloSpringMVC/person/findPersons?aa=#{a3}&bb=#{b3}&cc=#{c3}")
18     public RetMessage<Person> getPersonsForGet(@AIParam("a3")String a2, @AIParam("b3") String b2, @AIParam("c3") String c2);
19 
20     @AIPost("http://192.168.2.198:8080/HelloSpringMVC/person/findPersons")
21     public RetMessage<Person> getPersonsForPost(@AIParam("aa")String a2, @AIParam("bb") String b2, @AIParam("cc") String c2);
22 
23     @AIGet("http://192.168.2.198:8080/HelloSpringMVC/person/findPersons")
24     public RetMessage<Person> getPersonsForGet2(Params params);
25 
26     @AIPost("http://192.168.2.198:8080/HelloSpringMVC/person/findPersons")
27     public RetMessage<Person> getPersonsForPost2(Params params);
28 
29 
30 }

PersonWorker是自己寫的一個接口(以后需要有新的網絡請求,都可以類似編寫Worker),聲明了執行網絡請求的各種方法,這些方法需要加上@AIGet或者@AIPost注解,用於聲明請求方式,並在此注解中的value()值設置為所要請求的url(此注解的其他屬性后續會陸續擴展)

方法的@AIParam注解是作用與mybatis的@Param注解類似,可以設置請求攜帶的參數

如果參數比較多,則推薦使用Params來存放參數,以此來簡化代碼,Params類實質上就是一個HashMap,存放參數的鍵值對即可。

 

接下來分析下框架是怎么實現的,其實上面講過,主要是用Annotaion和動態代理了

首先看看PersonWorker的注入,在AIActivity(AIActivity,AndroidInject開源項目中的Activity使用注解的話,你寫的Activity必須繼承AIActivity,另外如果要使用FragmentActivity,則需要繼承AISupportFragmentActivity)啟動時,首先會去解析添加的注解,這里討論@AINetWorker注解,內部代碼很簡單:

1 /**
2  * 注入NetWorker
3  * @param field
4  * @throws Exception
5  */
6  private void netWorkerBind(Field field) throws Exception{
7         field.setAccessible(true);
8         field.set(present, NetInvoHandler.getWorker(field.getType()));
9  }

通過代碼可知,是使用反射來實現的,主要的代碼是這句:

NetInvoHandler.getWorker(field.getType());

這句代碼的作用是通過Class獲得一個PersonWorker實現類的代理對象,這里很明顯是使用了動態代理。

所以,最核心的類應該是NetInvoHandler這個類,這個類的代碼如下(篇幅問題,所以就折疊了):

  1 package com.wangjie.androidinject.annotation.core.net;
  2 
  3 import android.text.TextUtils;
  4 import com.google.gson.Gson;
  5 import com.wangjie.androidinject.annotation.annotations.net.AIGet;
  6 import com.wangjie.androidinject.annotation.annotations.net.AIParam;
  7 import com.wangjie.androidinject.annotation.annotations.net.AIPost;
  8 import com.wangjie.androidinject.annotation.util.Params;
  9 import com.wangjie.androidinject.annotation.util.StringUtil;
 10 
 11 import java.lang.annotation.Annotation;
 12 import java.lang.reflect.InvocationHandler;
 13 import java.lang.reflect.Method;
 14 import java.lang.reflect.Proxy;
 15 import java.util.HashMap;
 16 import java.util.Map;
 17 
 18 /**
 19  * Created with IntelliJ IDEA.
 20  * Author: wangjie  email:tiantian.china.2@gmail.com
 21  * Date: 14-2-7
 22  * Time: 下午1:40
 23  */
 24 public class NetInvoHandler implements InvocationHandler{
 25     private static HashMap<Class<?>, NetInvoHandler> invoHandlers = new HashMap<Class<?>, NetInvoHandler>();
 26 
 27     private Object proxy; // 代理對象
 28 
 29     public synchronized  static<T> T getWorker(Class<T> clazz){
 30         NetInvoHandler netInvoHandler = invoHandlers.get(clazz);
 31         if(null == netInvoHandler){
 32             netInvoHandler = new NetInvoHandler();
 33             netInvoHandler.setProxy(Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, netInvoHandler));
 34             invoHandlers.put(clazz, netInvoHandler);
 35         }
 36         return (T)netInvoHandler.getProxy();
 37     }
 38 
 39     @Override
 40     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 41 
 42         // get請求
 43         if(method.isAnnotationPresent(AIGet.class)){
 44             AIGet aiGet = method.getAnnotation(AIGet.class);
 45             String url = aiGet.value();
 46             if(TextUtils.isEmpty(url)){
 47                 throw new Exception("net work [" + method.getName() + "]@AIGet value()[url] is empty!!");
 48             }
 49             Annotation[][] annotaions = method.getParameterAnnotations();
 50             for(int i = 0; i < args.length; i++){
 51                 if(Params.class.isAssignableFrom(args[i].getClass())){ // 如果屬性為Params,則追加在后面
 52                     url = StringUtil.appendParamsAfterUrl(url, (Params)args[i]);
 53                 }else{ // 如果屬性添加了@AIParam注解,則替換鏈接中#{xxx}
 54                     String repName = ((AIParam)annotaions[i][0]).value();
 55                     url = url.replace("#{" + repName + "}", args[i] + "");
 56                 }
 57 
 58             }
 59             StringBuilder sb = NetWork.getStringFromUrl(url);
 60             if(null == sb){
 61                 return null;
 62             }
 63             return new Gson().fromJson(sb.toString(), method.getReturnType());
 64         }
 65 
 66         // post請求
 67         if(method.isAnnotationPresent(AIPost.class)){
 68             AIPost aiPost = method.getAnnotation(AIPost.class);
 69             String url = aiPost.value();
 70             if(TextUtils.isEmpty(url)){
 71                 throw new Exception("net work [" + method.getName() + "]@AIPost value()[url] is empty!!");
 72             }
 73             Annotation[][] annotaions = method.getParameterAnnotations();
 74             Map<String, String> map = new HashMap<String, String>();
 75             for(int i = 0; i < args.length; i++){
 76                 if(Params.class.isAssignableFrom(args[i].getClass())){ // 如果屬性為Params,則追加在后面
 77                     map.putAll((Params)args[i]);
 78                 }else{
 79                     String repName = ((AIParam)annotaions[i][0]).value();
 80                     map.put(repName, args[i] + "");
 81                 }
 82 
 83             }
 84             StringBuilder sb = NetWork.postStringFromUrl(url, map);
 85             if(null == sb){
 86                 return null;
 87             }
 88             return new Gson().fromJson(sb.toString(), method.getReturnType());
 89 
 90         }
 91 
 92 
 93         return null;
 94     }
 95 
 96 
 97     public Object getProxy() {
 98         return proxy;
 99     }
100 
101     public void setProxy(Object proxy) {
102         this.proxy = proxy;
103     }
104 }
View Code

里面的代碼還沒有好好的重構,所以,看起來會更直白,該類實現了InvocationHandler,很明顯的動態代理。

我們通過NetInvoHandler的getWorker靜態方法,來獲取一個指定Class的Worker實現類的代理對象,由於實際應用時,Worker接口應該會很多,為了不重復生成相同Worker實現類的代理對象,所以這里在生成一個后,保存起來,確保一個Worker只生成一個代理對象,一個NetInvoHandler。

這里有個地方需要注意一下,以前使用的動態代理,需要一個RealSubject,也就是真實對象,是Worker的實現類。這樣,在invoke方法中就可以調用真實對象的對應方法了,但是現在,進行網絡請求,我們沒有去寫一個類然后實現PersonWorker接口,因為沒有必要,我們完全可以在invoke方法中去執行相同的網絡請求。

請想下,之所以需要框架的存在 不就是為了把一些模板的東西給簡化掉么?現在的網絡請求這些步驟就是一些模板話的東西,我們需要的就是調用方法,自動進行網絡請求(框架做的事),然后返回給我結果。所以網絡請求這一步,寫在invoke方法中即可。

而不是所謂的編寫一個類,實現PersonWorker接口,在這個實現類中進行網絡請求,然后在invoke方法中調用真實對象的對應該方法。

因此,在Proxy.newProxyInstance的interfaces中填寫需要實現的接口,也就是現在的PersonWorker。

 

接下來看下invoke中做的事情,首先根據方法增加的注解來識別是GET請求還是POST請求。然后各自執行請求(因為我請求的執行,寫在NetWork中了,這里直接返回了請求結果字符串StringBuilder sb)。

接下來,使用Gson這個霸氣的工具,一鍵從json解析封裝成RetMessage對象。(所以,這里是需要Gson庫的支持,大家網上下載gson.jar,或者使用maven)

當然,要使用Gson一鍵解析封裝的前提是服務器端的編寫需要保存一致性,下面是我服務器端測試的代碼:

 1 @RequestMapping("/findPersons")
 2     public void findPersons(HttpServletRequest request, HttpServletResponse response, 
 3                                 @RequestParam("aa") String aa, 
 4                                 @RequestParam("bb") String bb, 
 5                                 @RequestParam("cc") String cc) throws IOException{
 6         System.out.println("aa: " + aa + ", bb: " + bb + ", cc: " + cc);
 7         RetMessage<Person> rm = new RetMessage<Person>();
 8 
 9         rm.setResultCode(0);
10         
11         List<Person> persons = new ArrayList<Person>();
12         for(int i = 0; i < 5; i++){
13             Person p = new Person();
14             p.setName("wangjie_" + i);
15             p.setAge(20 + i);
16             persons.add(p);
17         }
18         
19         rm.setList(persons);
20         
21         ServletUtil.obtinUTF8JsonWriter(response).write(rm.toJson());
22     }

服務器端返回結果時,也是封裝在RetMessage類中,這個類服務器端和客戶端是保持一致的,所以可以一鍵轉換。

如果你在開發的過程中,RetMessage類中封裝的東西不能滿足你的需求,可以自己編寫結果類,當然在Worker中聲明方法中返回值就應該是你寫的結果類了。

 

到此為止,講解完畢

另:如果,你需要寫一個查詢User信息的網絡請求,應該怎么寫?

只需編寫UserWorker接口,然后聲明方法findUsers(),寫上@AIGet或者@AIPost注解,寫明url和請求參數。然后通過@AINetWorker注解注入userWorker,然后開啟線程,調用userWorker的findUsers()方法即可。

當然UserWorker也可以不使用注解獲得,而是調用“NetInvoHandler.getWorker(UserWorker.class)”獲得!

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM