以下內容為原創,歡迎轉載,轉載請注明
來自天天博客: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 }
里面的代碼還沒有好好的重構,所以,看起來會更直白,該類實現了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)”獲得!