Android(安卓)processor的一次尝试
关于Adnroid processor
LovelyInject
项目地址:https://github.com/xiejinlong/LovelyInject
这个是一个基于https://github.com/enbandari/TieGuanYin库实现的一个简易版的intent注入框架。
使用流程
使用注解
可以使用的有3个注解,BuilderActivity,BuilderFragment和BuilderModel,这三个注解是用来修饰类的,三个注解的retention都是编译期间,targetType都是ElementType.TYPE,也就是用来修饰Class。
其中,BuilderActivity用来修饰Activity,可以指定默认的跳转Scheme,会生成一个通过scheme跳转的静态方法。
BuilderFragme用来修饰Fragment。
BuilderModel用来修饰普通的model类。
@Retention(RetentionPolicy.CLASS)@Target(ElementType.TYPE)
- 修饰activity
@BuilderActivity(routerValue = "to_test_scheme") class TestActivity: Activity() { @Fields var name: String? = null @Fields var age: Int = 0 @Fields var msg: String? = null }
- 修饰fragment
@BuilderFragment class TestFragment: Fragment() { @Fields var name: String? = null @Fields var age: Int = 0 @Fields var msg: String? = null }
- 修饰model类
@BuilderModel class TestModel { @Fields var name: String? = null @Fields var age: Int = 0 @Fields var msg: String? = null }
编译build
- 生成的ActivityBuilder
public final class TestActivityBuilder { public static final String FIELD_NAME = "name"; public static final String FIELD_AGE = "age"; public static final String FIELD_MSG = "msg"; private String name; private int age; private String msg; public static TestActivityBuilder builder() { TestActivityBuilder builder = new TestActivityBuilder(); return builder; } public TestActivityBuilder name(String name) { this.name = name; return this; } public TestActivityBuilder age(int age) { this.age = age; return this; } public TestActivityBuilder msg(String msg) { this.msg = msg; return this; } private void fillIntent(Intent intent) { intent.putExtra("name", name); intent.putExtra("age", age); intent.putExtra("msg", msg); } public void start(Context context) { Intent intent = new Intent(context, TestActivity.class); fillIntent(intent); context.startActivity(intent); }
- 生成的fragmentBuilder
public final class TestFragmentBuilder { public static final String FIELD_NAME = "name"; public static final String FIELD_AGE = "age"; public static final String FIELD_MSG = "msg"; private String name; private int age; private String msg; public static TestFragmentBuilder builder() { TestFragmentBuilder builder = new TestFragmentBuilder(); return builder; } public TestFragmentBuilder name(String name) { this.name = name; return this; } public TestFragmentBuilder age(int age) { this.age = age; return this; } public TestFragmentBuilder msg(String msg) { this.msg = msg; return this; } private void fillIntent(Intent intent) { intent.putExtra("name", name); intent.putExtra("age", age); intent.putExtra("msg", msg); } public TestFragment build() { TestFragment fragment = new TestFragment(); Intent intent = new Intent(); fillIntent(intent); fragment.setArguments(intent.getExtras()); return fragment; }}
-生成的modelBuilder
public final class TestModelBuilder { public static final String FIELD_NAME = "name"; public static final String FIELD_AGE = "age"; public static final String FIELD_MSG = "msg"; private String name; private int age; private String msg; public static TestModelBuilder builder() { TestModelBuilder builder = new TestModelBuilder(); return builder; } public TestModelBuilder name(String name) { this.name = name; return this; } public TestModelBuilder age(int age) { this.age = age; return this; } public TestModelBuilder msg(String msg) { this.msg = msg; return this; } public TestModel build() { TestModel model = new TestModel(); return model; }}
这三个builder文件基本类似,每个@Fields修饰的成员变量都将生成一个对应的builder方法。activityBuilder会多一个fillIntent方法和start方法,用来填充intent和开启新页面。而fragmentBuilder会多一个fillIntent方法和build方法,fillIntent也是用来填充intent,而build方法是用来返回fragment实例的。
使用Builder
- in ActivityBuilder
//调用TestActivityBuilder.builder().age(12) .name("xie") .msg("我是从mainAc过来的参数") .start(this)//使用变量,in TestActivity Toast.makeText(this, "我是 $name,今年 $age, $msg", Toast.LENGTH_LONG).show()
- in fragmentBuilder
//调用fragmentManager.beginTransaction() .replace(R.id.mainLayout, TestFragmentBuilder.builder() .name("lovely") .age(13) .msg("我是从testAc过来的参数").build()) .commitAllowingStateLoss()//使用变量,in TestFragment Toast.makeText(context, "我是 $name,今年 $age, $msg", Toast.LENGTH_LONG).show()
Prossor生成代码原理
上面就是通过builder生成的代码来给我简化使用流程,每一个activity和fragment都会相对应的生成一个Builder类来供我们使用。下面我们来详细了解一下这个框架的原理实现。
基本元素
Element
Element是基类,可在不同的情况下转化成不同的子类,具体的类型可以通过getKind方法获得
- TypeElement: 表示类或者接口
- VariableElement: 表示字段参数
- PackageElement: 表示一个包
- ExecutableElement: 表示方法
- TypeParameterElement: 表示范型参数
TypeMirror
Element表示的是元素,而TypeMirror表示的是参数类型。可以通过getkind来获取参数类型。
TypeSpec
是javapoet库用来生成文件的主要的类。
ProcessingEnvironment
ProcessingEnvironment中提供了4个工具接口
- lateinit var types: Types //java类型工具
- lateinit var elements: Elements //注解获取出来的元素
- lateinit var messager: Messager//消息输出
- lateinit var filer: Filer//文件写入
实现流程
- 首先我们需要先创建出Annotation库,创建出对应的注解。
@Retention(RetentionPolicy.CLASS)@Target(ElementType.TYPE)public @interface BuilderActivity { String routerValue() default "";}
- 创建出Processor库,并且自定义Processor
class KKProcessor : AbstractProcessor() {}
在这里需要注意的是,我们必须手动的建立Processor索引,不然编译期间不会执行到这个Processor。
需要在和java同级目下创建出resources/META-INF/services/javax.annotation.processing.Processor文件,然后在内部添加processor的引用。
com.inject.xie.processor.KKProcessor
而且,需要在app的gradle中添加该processor的编译。
kapt project(":Processor")
- 开始编译,解析注解
在编译时,会调用到processor的process方法
override fun process(p0: MutableSet?, p1: RoundEnvironment?): Boolean { LogUtils.warn("KKProcessor process") return true }
在这个方法中,我们需要解析出我们要的注解
//解析classenv.getElementsAnnotatedWith(BuilderActivity::class.java) .asSequence() .filter(SuperficialValidation::validateElement) .filter { it.kind.isClass } .toList() .forEach { element -> LogUtils.warn("KKProcessor parasClass ${element.simpleName} is Activity~") if (ProcessorEnv.types.isSubtype(element.asType(), ClassType.KKACTIVITY.typeMirror)) { classMap[element] = KKActivityBuilder(element as TypeElement) } }
通过getElementsAnnotatedWith方法来获取被BuilderActivity修饰的所有的类。其他几个注解类似,然后存储在classMap,这里需要注意一点,process方法可能会执行多次,所以需要将解析的产物放在map中或者每次解析都将list清空。
然后解析field
private fun parasFiled(env: RoundEnvironment) { env.getElementsAnnotatedWith(Fields::class.java) .asSequence() .filter(SuperficialValidation::validateElement) .filter { it.kind.isField } .toList() .forEach { element -> LogUtils.warn("KKProcessor parasFiled ${element.simpleName}") classMap[element.enclosingElement]?.addFiled(element) } }
将解析生成的fields存储到上一步生成的class产物中,这样,就拿到了被注解的类和其中的被注解的成员变量。
4. 生成代码
- 创建class
val classFileBuilder = TypeSpec.classBuilder(builderClassName) .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
- 创建成员及成员的builder方法
fields.forEach { field -> LogUtils.warn("fieldBuilder ${field.name}") //构造临时变量 classFileBuilder.addField(FieldSpec.builder(field.asTypeName(), field.name, Modifier.PRIVATE).build()) //构造变量相关的静态变量 classFileBuilder.addField(FieldSpec.builder(String::class.java, KKActivityBuilder.CONST_POSIX + field.name.toUpperCase()) .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) .initializer("\$S", field.name) .build()) //构造相关变量的builder方法 classFileBuilder.addMethod(MethodSpec.methodBuilder(field.name) .addModifiers(Modifier.PUBLIC) .addParameter(field.asTypeName(), field.name) .addStatement("this.${field.name} = ${field.name}") .addStatement("return this") .returns(builderClassTypeName) .build()) }
- 创建方法
首先需要静态的builder方法
//构造主builder classFileBuilder.addMethod(MethodSpec.methodBuilder("builder") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(builderClassTypeName) .addStatement("\$T builder = new \$T()", builderClassTypeName, builderClassTypeName) .addStatement("return builder").build())
对于Activity,需要创建fillIntent和start方法
//对于Activity,需要创建fillIntent和start方法 val intentMethod = MethodSpec.methodBuilder("fillIntent") .addModifiers(Modifier.PRIVATE) .addParameter(INTENT.java, "intent") fields.forEach { field -> //给fillIntent方法添加元素 intentMethod.addStatement("intent.putExtra(\$S, \$L)", field.name, field.name) } typeBuilder.addMethod(intentMethod.build()) //start typeBuilder.addMethod(MethodSpec.methodBuilder("start") .addModifiers(Modifier.PUBLIC) .addParameter(CONTEXT.java, "context") .addStatement("Intent intent = new Intent(context, \$L.class)", simpleName) .addStatement("fillIntent(intent)") .addStatement("context.startActivity(intent)") .build()) }
对于fragment需要创建fillIntent和build方法
//fragment也需要fillIntent val intentMethod = MethodSpec.methodBuilder("fillIntent") .addModifiers(Modifier.PRIVATE) .addParameter(ClassType.INTENT.java, "intent") fields.forEach { field -> //给fillIntent方法添加元素 intentMethod.addStatement("intent.putExtra(\$S, \$L)", field.name, field.name) } typeBuilder.addMethod(intentMethod.build()) val originClassName = ClassName.get(packageName, simpleName.toString()) //通过builder方法返回实例 typeBuilder.addMethod(MethodSpec.methodBuilder("build") .returns(originClassName) .addModifiers(Modifier.PUBLIC) .addStatement("\$T fragment = new \$T()", originClassName, originClassName) .addStatement("Intent intent = new Intent()") .addStatement("fillIntent(intent)") .addStatement("fragment.setArguments(intent.getExtras())") .addStatement("return fragment") .build()) }
- 写入文件
当构建好了TypeSpec,通过Filer进行文件写入
private fun writeJavaToFile(typeSpec: TypeSpec) { try { val file = JavaFile.builder(packageName, typeSpec).build() file.writeTo(ProcessorEnv.filer) } catch (e: IOException) { e.printStackTrace() } }
这样,对应的生成文件就创建出来了。
- 依赖注入
在文件生成之后,我们通过对应的Builder类来启动activity或者创建fragment实例,那我们如何直接在activity或者fragment中直接使用被注解的成员变量呢?这个其实也比较简单。
- 对于activity
在application中注册activity监听,然后通过onActivityCreate的回调方法中进行inject,这个方法会在oncreate之前调用。
override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) { if (activity == null) { return } if (!activity.javaClass.isAnnotationPresent(BuilderActivity::class.java)) { //该activity没有被Builder标注,跳过 return } Log.d("KKActivityBuilder", "onActivityCreated~") val intent = activity.intent ?: return var fields = activity.javaClass.declaredFields inject(activity, fields, intent.extras) }fun inject(activity: Activity?, fields: Array?, extras: Bundle?) { if (fields == null) { Log.d("KKActivityBuilder", "declaredFields is null, should return~") return } fields.forEach { field -> if (field.isAnnotationPresent(Fields::class.java)) { val name = field.name try { val access = field.isAccessible if (!access) field.isAccessible = true val value = getIntentExtra(extras, name) if (value != null) { field.set(activity, getIntentExtra(extras, name)) } else { Log.d("KKActivityBuilder", "get value is null, continue~") } if (!access) field.isAccessible = false } catch (e: Exception) { Log.e("KKActivityBuilder", "error in -> ${e.message}") } } } } fun getIntentExtra(extras: Bundle?, name: String): Any? { return extras?.get(name) }
- 判断当前的activity是否被BuilderActivity修饰过
- 如果被BuilderActivity修饰过,遍历fields,判断是否被Fields修饰过
- 如果被Fields修饰过,从intent中获取field的name对应的value,以object的形式取出即可
- 通过反射,给field赋值为上一步取出的值。
- 完成
- 对于fragment
fragment的注入其实与activity基本一致,只是fragment没有相对应的生命周期的监听,不过我们可以在统一的基类的onCreateView方法中调用inject方法进行注入。实际的注入流程完全一样。不过activity是从intent中取值,fragment是从argument中取值。
更多相关文章
- Android群英传知识点回顾——第五章:Android(安卓)Scroll分析
- Android屏蔽Home键,适配所有版本
- 深入android数据库操作
- android 常用Bitmap处理方法收集:普通裁剪,缩放,圆形裁剪
- 将TaintDroid编译进Android(安卓)2.3
- Android(安卓)Studio lint工具所提示的需要注意的内容简要记录
- Android(安卓)Notification.Builder通知案例分享
- Android中调用System.exit(0)
- Android(安卓)图片处理方法大全