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//文件写入

实现流程

  1. 首先我们需要先创建出Annotation库,创建出对应的注解。
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface BuilderActivity {
    String routerValue() default "";
}
  1. 创建出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")
  1. 开始编译,解析注解
    在编译时,会调用到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产物中,这样,就拿到了被注解的类和其中的被注解的成员变量。
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()
        }
    }

这样,对应的生成文件就创建出来了。

  1. 依赖注入
    在文件生成之后,我们通过对应的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)
    }
  1. 判断当前的activity是否被BuilderActivity修饰过
  2. 如果被BuilderActivity修饰过,遍历fields,判断是否被Fields修饰过
  3. 如果被Fields修饰过,从intent中获取field的name对应的value,以object的形式取出即可
  4. 通过反射,给field赋值为上一步取出的值。
  5. 完成
  • 对于fragment
    fragment的注入其实与activity基本一致,只是fragment没有相对应的生命周期的监听,不过我们可以在统一的基类的onCreateView方法中调用inject方法进行注入。实际的注入流程完全一样。不过activity是从intent中取值,fragment是从argument中取值。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值