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 }
里面的代码还没有好好的重构,所以,看起来会更直白,该类实现了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)”获得!