这一篇,我们来学习怎么自己创建注解和注解处理器。
首先创建两个java的module,一定要是java的。
然后所有的module都添加这两个依赖
注意,对于annotions_compiler 的依赖方式,要annotationProcessor
implementation project(':annotations') annotationProcessor project(':annotions_compiler')
在annotation里创建一个BindPath类
/** * 定义注解需要用@interface声明,并且需要写原注解@Target(ElementType.TYPE)和@Retention(RetentionPolicy.CLASS) * 注解需要定义javaLib */ @Target(ElementType.TYPE)//声明这个注解放在什么上面的 TYPE是类上面 @Retention(RetentionPolicy.CLASS)//声明这个注解的生命周期 三个生命周期 java--->class--->run public @interface BindPath { String value(); }
然后就是注解处理器,对于注解处理器,我们要特别注意一下
这是annotation_compiler的build.gradle,我们需要根据自己开发环境的不同来选择不同的google依赖。
dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') //As 3.4+ // annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4' // compileOnly 'com.google.auto.service:auto-sevice:1.0-rc4' //依赖google 注解处理器服务包 implementation 'com.google.auto.service:auto-service:1.0-rc3' implementation project(':annotations') }
然后创建AnnotationCompiler
/** * 注解处理器 * AbstractProcessor父类 */ @AutoService(Processor.class)//注册注解处理器 public class AnnotionCompiler extends AbstractProcessor { //生成文件的对象 Filer filer; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); filer = processingEnv.getFiler(); } /** * 声明我们这个注解处理器需要处理哪些注解 * * @return */ @Override public Set<String> getSupportedAnnotationTypes() { Set<String> types = new HashSet<>(); types.add(BindPath.class.getCanonicalName()); return types; } /** * 声明我们支持的java版本 * * @return */ @Override public SourceVersion getSupportedSourceVersion() { return processingEnv.getSourceVersion(); } /** * 这个方法是注解处理的核心方法,写文件就放在这里进行写 element节点 TypeElement就是类节点 * * @param annotations * @param roundEnv * @return */ @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { //通过这个API能拿到所有这个模块中所有的用到了BindPath注解的节点 Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(BindPath.class); Map<String, String> map = new HashMap<>(); for (Element element : elementsAnnotatedWith) { //找到类节点 TypeElement typeElement = (TypeElement) element; //获取到activity集合的key String key = typeElement.getAnnotation(BindPath.class).value();//找到key BindPath()中的值 String value = typeElement.getQualifiedName().toString();//带路径的名字 map.put(key, value); } if (map.size() > 0) { Writer writer = null; String utilName = "ActivityUtils" + System.currentTimeMillis(); try { JavaFileObject sourceFile = filer.createSourceFile("com.tengxincar.mobile.utils." + utilName); writer = sourceFile.openWriter(); writer.write("package com.tengxincar.mobile.utils;\n" + "\n" + "import com.tengxincar.mobile.arouter.ARouter;\n" + "import com.tengxincar.mobile.arouter.IRouter;\n" + "\n" + "public class " + utilName + " implements IRouter {\n" + " @Override\n" + " public void putActivity() {\n"); Iterator<String> iterator = map.keySet().iterator(); while (iterator.hasNext()) { String key = iterator.next(); String value = map.get(key); writer.write("ARouter.getInstance().putActivity(\"" + key + "\"," + value + ".class);\n"); } writer.write("}\n}"); } catch (IOException e) { e.printStackTrace(); } finally { if (writer != null) { try { writer.close(); } catch (IOException e) { e.printStackTrace(); } } } } return false; } }
这个注解处理器,其实大部分内容是固定的,不固定的大概就是process这个方法,里面用了各种不熟悉的api,大致就是通过writer写java文件,这个java文件就是上一篇里的我们需要在每个module里自己写的那个,其中我觉得比较厉害的点在找到类节点,然后获取Annotation的value,并且获取到类节点带路径的值,可能是自己java基础不太好,我只能理解,其他的代码我也都作了注释,只能说大神都好牛逼啊。
注解和注解处理器都写完了,我们继续回到之前的ARouter,我们在ARouter的init方法里来把所有有BindPath注解的Activity都添加到集合中
public void init(Context context) { this.context = context; //包名下面所有的类名 List<String> classNames = getClassName("com.tengxincar.mobile.utils"); for (String className : classNames) { try { //得到类 Class<?> aClass = Class.forName(className); //是否是IRouter的实现类 if (IRouter.class.isAssignableFrom(aClass)) { //接口的引用指向子类的实例 IRouter iRouter = (IRouter) aClass.newInstance(); iRouter.putActivity(); } } catch (Exception e) { e.printStackTrace(); } } }
private List<String> getClassName(String packgeName) { //创建一个Class对象的集合 List<String> classList = new ArrayList<>(); String path = null; try { //通过包管理器,获取到应用信息类然后获取到APK的完整路径 path = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0).sourceDir; //根据APK的完整路径获取到编译后的dex文件 DexFile dexFile = new DexFile(path); //获得编译后的dex文件中的所有class Enumeration enumeration = dexFile.entries(); //然后进行遍历 while (enumeration.hasMoreElements()) { //遍历所有的class包名 String name = (String) enumeration.nextElement(); //判断类的包名是否符合 if (name.contains(packgeName)) { classList.add(name); } } } catch (Exception e) { e.printStackTrace(); } return classList; }
这里面我觉得比较关键的点就是getClassName方法,老师说这个方法网上都有,但是我还是不会写的啊,emmmm
然后就是这个地方我觉得比较牛逼
if (IRouter.class.isAssignableFrom(aClass)) { //接口的引用指向子类的实例 IRouter iRouter = (IRouter) aClass.newInstance(); iRouter.putActivity(); }
我们创建的java类都实现了IRouter的接口,然后就可以调用java类里的putActivity,从而实现真正的把注解值和对应的class放到我们的Map集合中。
然后接下来就是在我们LoginModule里添加注解
@BindPath("loginmodle/LoginModule") public class LoginModuleActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login_module); } public void jumpToPersonal(View view) { ARouter.getInstance().jumpToActivity("personalmodule/personalmodule", null); } }
然后实现跳转。
其实这并不是真的ARouter,这大概是原理,我就是个老白(老+小白),跟着大神学,随手记录吧
最后贴一个github,里面有我实现的demo,欢迎交流。
(这是网易的公开课,网易大大不会告我侵权吧,逃。)