Adnroid 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产物中,这样,就拿到了被注解的类和其中的被注解的成员变量。

  1. 生成代码
  • 创建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中取值。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,192评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,858评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,517评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,148评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,162评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,905评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,537评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,439评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,956评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,083评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,218评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,899评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,565评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,093评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,201评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,539评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,215评论 2 358

推荐阅读更多精彩内容

  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,739评论 2 59
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,220评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,675评论 18 139
  • 经常看到很多惊艳的照片,心里莫名觉得高大上,话说,大家有没有想过,这些牛逼的照片,到底怎么拍出来的呢?看完下面这些...
    a104b284f747阅读 418评论 0 5
  • 我是一个讲故事的人。 我以笔为生。 因为害怕社交,我费尽心机的谋到一份写字赚钱的差事,想到只要我愿意,甚至可以一年...
    _桃止阅读 310评论 4 7