# 关于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<out TypeElement>?, p1: RoundEnvironment?): Boolean {
LogUtils.warn("KKProcessor process")
return true
}
在这个方法中,我们需要解析出我们要的注解
//解析class
env.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产物中,这样,就拿到了被注解的类和其中的被注解的成员变量。
- 生成代码
- 创建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<Field>?, 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中取值。
网友评论