(注:本文适合有一定java基础的童鞋看,至少明白注解Annotation是什么)
贴上我的Android网络通信库地址
https://github.com/MyLifeForTheOrc/gm-httpengine-studio
最近在annotation
分支上工作,就为了增加注解支持。
目标是像ButterKnife一样酷炫,现在也差不多。
首先看下改进后的(酷炫)使用方法,如果我需要做一个http请求,只需要以下几步:
定义API
java
package org.gemini.httpengine.examples; import org.gemini.httpengine.annotation.GET; import org.gemini.httpengine.annotation.Path; import org.gemini.httpengine.annotation.TaskId; import org.gemini.httpengine.library.OnResponseListener; /** * Created by geminiwen on 15/5/21. */ public interface UserAPI { interface TASKID { String TASK_GET_LOGIN = "login"; } @Path("http://www.baidu.com") //定义URL地址 @TaskId(TASKID.TASK_GET_LOGIN) //给这个请求加一个taskId,标识请求 @GET //标明这个请求是一个GET请求 void login(OnResponseListener l, String username, String password); }
在Activity中调用API
java
@Override public void onClick(View v) { if(v == mTestButton) { UserAPI api = InjectFactory.inject(UserAPI.class); //注入API实例 api.login(this, "geminiwen", "password"); //调用接口 } }
获取接口
首先实现OnResponseListener
接口
java
@Override public void onResponse(GMHttpResponse response, GMHttpRequest request) { byte[] result = null; try { result = response.getRawData(); //获取数据 } catch (Exception e) { Log.e("error", "wtf?", e); } // Toast.makeText(this,result,Toast.LENGTH_LONG).show(); }
使用了注解的方式,是不是感觉很干净彻底?
但是这里只有接口,并没有实现类啊,这到底是怎么做到的呢?
OK,我们看看它背后做了什么。
这里我们使用Android Studio
举例
首先可以看下Android Application Module下面的build
文件夹
其他文件都很正常,除了两个"不速之客",UserAPI$$APIINJECTOR.java
和 UserAPI$$APIINJECTOR.class
。没错,这就是使用Annotation Processor
生成的java文件了。
我们看看生成了什么。
把它格式化一下如下:
java
package org.gemini.httpengine.examples; import org.gemini.httpengine.library.*; public class UserAPI$$APIINJECTOR implements org.gemini.httpengine.examples.UserAPI { public void login(org.gemini.httpengine.library.OnResponseListener l, java.lang.String username, java.lang.String password) { final String FIELD_USERNAME = "username"; final String FIELD_PASSWORD = "password"; GMHttpParameters httpParameter = new GMHttpParameters(); httpParameter.setParameter(FIELD_USERNAME, username); httpParameter.setParameter(FIELD_PASSWORD, password); GMHttpRequest.Builder builder = new GMHttpRequest.Builder(); builder.setHttpParameters(httpParameter); builder.setTaskId("login"); builder.setUrl("http://www.baidu.com"); builder.setMethod("GET"); builder.setOnResponseListener(l); GMHttpService service = GMHttpService.getInstance(); service.executeHttpMethod(builder.build()); } }
这里的代码就是调用GMHttpEngine
里面带的API,进行HTTP的请求。
OK,我们得到结论了,它的秘密就是库利用一些特殊的特性,帮你生成了一个实现类,并利用InjectFactory.inject
这个方法,把这个实现类用反射的方式,生成出来,返回给你的接口。
那新问题产生了,生成代码
这么叼的事是怎么做到的呢。我们必须了解apt
这个东西的存在
给个APT介绍的传送门(鸟语):http://docs.oracle.com/javase/7/docs/technotes/guides/apt/
这时候我们知道了有Annotation Processor
这个东西的存在,我们的目标就是自定义一个Annotation Processor
了。
看第一个接口叫AbstractProcessor
,完全限定名是:javax.annotation.processing.AbstractProcessor;
它是一个抽象类,所以我们要自定义一个类,去继承它,它最主要的就是里面的process
接口
java
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { Map<TypeElement, APIClassInjector> targetClassMap = findAndParseTargets(roundEnv); for (Map.Entry<TypeElement, APIClassInjector> entry : targetClassMap.entrySet()) { TypeElement typeElement = entry.getKey(); APIClassInjector injector = entry.getValue(); try { String value = injector.brewJava(); JavaFileObject jfo = filer.createSourceFile(injector.getFqcn(), typeElement); Writer writer = jfo.openWriter(); writer.write(value); writer.flush(); writer.close(); } catch (Exception e) { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage(), typeElement); } } return false; }
看见里面一个叫JavaFileObject
的对象没有,它就是生成java文件最重要的对象了。这里我们干的事情,就是分析我们的注解(Annotation)
然后,生成相应的代码,利用JavaFileObject进行写入,就好了。
当然,你需要告诉Annotation Processor
你要处理哪一些注解,具体方法就是重写它的getSupportedAnnotationTypes
方法
java
@Override public Set<String> getSupportedAnnotationTypes() { Set<String> supportTypes = new LinkedHashSet<>(); supportTypes.add(Path.class.getCanonicalName()); return supportTypes; }
因为Path
注解是GMHttpEngine
所使用的核心注解,所以我这里就写了这个,从接口看,它可以支持一批的注解,对于不同的注解要有不同的处理方式。
除此之外,你需要定义resources
文件夹,新建一个文件叫javax.annotation.processing.Processor
里面的内容就是你的Annotation Processor
的完全限定名。目录结构如下:
里面的内容是
最后说了这么多,再整理下神奇的流程。
- 首先我们要定义一下自己的注解。
- 自定义我们自己的
Annotation Processor
,即继承AbstractProcessor
类。 - 在
process
方法中分析注解,生成java代码。 - 在
resources\META-INF\services\javax.annotation.processing.Processor
文件中注册类名。 - 导入就可以使用注解标识API请求自动生成代码啦~
噢~这里要说下在开发注解功能碰到的坑:
如果使用AndroidStudio 需要注意的是,
Android Library
并不是普通的JavaSE
,所以并没有提供javax
的一些功能,所以,在新建Module的时候不能选Android Library
而应该选Java Library
,而且因为它只在编译的时候使用到JavaSE
的功能,所以并不用担心在手机上跑的时候会出问题。
好了,想了解更多,欢迎star
欢迎fork
欢迎在SegmentFault
上一起讨论问题~
也欢迎给我邮件 geminiwen@segmentfault.com