组件通讯设计原理及注入实现原理

组件通讯设计原理及注入实现原理

主要涉及一下核心知识点

  • 注解
  • 注解处理器
  • 自动生成代码 (javapoet/kotlinpoet)
  • 自定义插件
  • ASM
  • transform

本文主要设计设计和思考实现思路、 解决问题的方式及经验, 至于以上的各个知识点网上一大把, 读者可自行学习, 这里不再陈述。

所有功能已进行实现, 其用方式依赖及实现源码已在 Github 上 ServiceAssistant, 该库不管组件通讯的 Service 还是进行注入操作 Injected 都是懒加载, 而且一步到位, 跟用户直接设计一个单例对象或者 new 一个对象一样, 用户可以直接去依赖使用或者阅读源码。 当然整个库使用和一个组件化的设计思路及写法也可直接阅读 Demo。

以组件通讯开始

Android 项目的组件化的好处不在多提, 我们知道组件之间是单独独立不能相互依赖的, 那么组件化可能遇到的一个很大的阻力就是组件之间怎么通讯呢? 当然现在也有很好的通讯方式比如 Aroute 等, 但有没有更加灵活和方便的或者说一个新的方式去进行组件通讯或者注入呢, 还有就是解决组件之间设置回调, 这就是我写这个库和文字的初衷。

我们设计组件化, 那么每个组件都是可以单独运行的, 最简单的模型如下:

组件化基本模型

以上可能是最小的一个组件化模型了, 从上面的图设计可以思考如下:

  • 组件 A 和组件 B 之间是没有任何连线也就是没有依赖, 组件 A 和 组件 B 怎么相互调用进行通讯呢?
  • 如果想让组件 A 和 组件 B 单独运行, 当然 App 壳也可以单独运行。 乖乖, 不说组件 A, 组件 B 单独运行了, 当 组件 A 或者 组件 B 设置成单独运行时, App 壳就不能去依赖这个单独运行的组件了, 想让 App 壳也单独运行, 那么 App 壳就不能强依赖于组件了, 这该怎么解决?

当然以上问题都在该库中解决, 还有一个惊喜就是在实现该库时顺便加入了注入的另一个功能, 也方便使用者可以使用该库可以方便使用注入功能。

那么好我们就开始去分析和解决以上问题。

组件之间的通讯

通过上面的组件图可以看出, 组件之间是没有依赖的。 我们思考去想想, 组件 A 想去调用组件 B 的某个功能, 组件 A 必须要知道 组件 B 都提供出来了什么功能吧, 如果组件 A 完全不知道 B 给我们提供出什么的话, 谈什么去调用 组件 B 呢, 就行我们要去调用 Android 系统的服务时, 就比如我们去调用获取图片, 我们总要知道系统给我们提供了什么能力去调用吧, 所以我们设计出一个组件的时候需要设计我们向外部抛出公开什么能力供别人去调用。

大概设计图如下:

组件通讯模型

其中 ApiA 和 ApiB 里都是接口, 也就是组件 A 和组件 B 向外部公开出来的能力, 当然比如组件 A 提供的能力 ApiA 的实现肯定是组件 A 内部去实现, 组件 B 提供出来的能力 ApiB 肯定是 组件 B 内部去实现。 如果组件 A 去调用组件 B 的能力, 只需要知道组件 B 提供出来什么能力, 也就是去依赖 ApiB 即可, 反之 组件 B 去调用组件 A 的能力也是一样。 那么组件去依赖另一个组件提供出来的能力也就是接口, 怎么去调用到对应的实现呢, 这, 这就是该库要做的事情了。

先介绍下库的使用:

比如我们在组件 A 中公开登录的能力 (ApiA)

interface ILoginAbilityApi {

    /**
     * 登录
     */
    fun toLogin(context: Context)

    fun addLoginStateChangedListener(listener: ILoginStateChangedListener)

    fun removeLoginStateChangeListener(listener: ILoginStateChangedListener)

    interface ILoginStateChangedListener {
        fun change(state: Boolean)
    }
}

在组件 A 中去实现该能力

@Service
class LoginAbilityApiImpl : IService<ILoginAbilityApi>, ILoginAbilityApi {

    /**
     * 使用方提供
     */
    override fun getService(): ILoginAbilityApi {
        return LoginAbilityApiImpl()
    }

    /**
     * 登录
     */
    override fun toLogin(context: Context) {
        LoginActivity.showActivity(context)
    }

    override fun addLoginStateChangedListener(listener: ILoginAbilityApi.ILoginStateChangedListener) {
        sLoginStateChangedListener.add(listener)
    }

    override fun removeLoginStateChangeListener(listener: ILoginAbilityApi.ILoginStateChangedListener) {
        sLoginStateChangedListener.remove(listener)
    }

    companion object {
        private val sLoginStateChangedListener =
            mutableListOf<ILoginAbilityApi.ILoginStateChangedListener>()

        fun notifyLoginState(state: Boolean) {
            sLoginStateChangedListener.forEach {
                it.change(state)
            }
        }
    }

}

那么在组件 B 中去依赖 组件 A 提供出来的能力 ApiA, 去调用登录的写法如下:

val service = Service.getService(ILoginAbilityApi::class.java)
if (service == null) {
    Toast.makeText(this, "未发现登录组件", Toast.LENGTH_SHORT).show()
    return
}
    
service.toLogin(this)

在这里请允许我提下注入使用:

定义接口:

interface IAccountRepo {

    fun getAccountData(): String
}

实现接口:

@NeedInjected
class AccountRepoImpl : IAccountRepo {

    override fun getAccountData(): String {
        return "account data"
    }

}

使用:

class MainActivity : AppCompatActivity() {

    @Injected
    private lateinit var mAboutRepo: IAboutRepo

    @Injected
    private lateinit var mSettingRepo: ISettingRepo

    @Injected
    private lateinit var mAccountRepo: IAccountRepo
    
}

那么好, 那我们刚才说的第二个问题不也就迎刃而解了嘛, 请看下图:

上层访问下层

okk, 开始我们的实现思考及原理之旅。

组件化通讯之 Service 的实现原理(注解、 插件、 ASM、 transform)

总体实现思路: 我们把所有 api 及实现服务对应收集起来, 放到一个固定的 map 中, 当然中间要去实现的时候要考虑到其单例和懒加载。 然后我们需要什么服务时直接去从这个 map 拿不就 OK 了吗? 是的。 如果我们从这个固定 map 中取, 取到了就是找到了该组件, 如果取不到就是没有找到该组件。

map 里我们存什么呢? 肯定是 api 和实现的对应关系哈。

收集所有的 api 及实现

通过 transfrom 去扫描所有的 api 及实现, 那么最简单的来个注解吧, 然后直接扫描到该注解然后获取该类的信息收集出来就可以, 所以我们加一个 @Service 注解。

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class Service

我们扫描搜集的对应关系利用 ASM 合理的保存到一个固定 Service 中, 当然这个也是使用者获取对应服务的一个入口。 其中我们要保存的对应关系合理的写入 getService 方法中。

object Service {

    private val sServiceRelation = mutableMapOf<String, Any?>()
    private val sLock = Any()

    @JvmStatic
    fun <T> getService(clazz: Class<T>): T? {
        return null
    }
}

使用 transform 去扫描 jar 和 dir 类上面有注解的的信息并收集。 其伪代码如下:

  • transfrom:
class ServiceAssistantTransform : Transform() {

    private val mNeedScanClassInfo = mutableListOf<Pair<String, String>>()
    private var mServiceFile: File? = null
    
    override fun transform(transformInvocation: TransformInvocation?) {
        
        1. 扫描所有的 dir 中的文件 {
            ServiceAssistantClassVisitor(去扫描处理) {
                1.1 把扫描出来的对应关系保存到 mNeedScanClassInfo 中。
                1.2 把里面处理完的放到到输出文件中。
            }
        }
        
        2. 扫描所有 jar 中的文件 {
            ServiceAssistantClassVisitor(去扫描处理) {
                2.1 把扫描出来的对应关系保存到 mNeedScanClassInfo 中。
                2.2 把里面处理完的放到到输出文件中。
                2.3 如果扫描到我们需要的写入对应关系的类的输出文件进行先保存只 mServiceFile 中, 以便后面使用。
            }
        }
    }
    
}
  • ServiceAssistantClassVisitor (去访问类信息)
class ServiceAssistantClassVisitor(
    private val byteArray: ByteArray,
    private val serviceTargetFindBack: () -> Unit,
    private val needScanClassInfoBack: (String, String) -> Unit
) : ClassVisitor(Opcodes.ASM7) {

    private lateinit var mVisitorClassName: String
    private lateinit var mVisitorClassSignature: String

    override fun visit(
        version: Int,
        access: Int,
        name: String?,
        signature: String?,
        superName: String?,
        interfaces: Array<out String>?
    ) {
        super.visit(version, access, name, signature, superName, interfaces)
        this.mVisitorClassName = name ?: ""
        this.mVisitorClassSignature = signature ?: ""
        // 这里就是如果扫描到我们要存放对应关系的 Service 类, 回调出去。
        if (this.mVisitorClassName == ServiceAssistantConstant.PATH_SERVICE_REFERENCE) {
            // is service
            serviceTargetFindBack.invoke()
        }
    }

    override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor {

        descriptor?.let {
            // 我们只关注的 Service 注解
            if (it.indexOf(ServiceAssistantConstant.SIGNATURE_SERVICE_ANNOTATION) < 0) return@let
            
            // ......
            
            // 获取出来我们加入注解的对应关系并回调出去进行收集
            needScanClassInfoBack.invoke(
                targetInterface.replace("/", "."),
                mVisitorClassName
            )
        }
        return super.visitAnnotation(descriptor, visible)
    }

}

通过我们以上 transform 1 和 2 步我们可以得到所有的服务对应关系及我们需要插入代码的 Service 类所在的文件。

那么好我们现在要做的就是把所收集的对应关系使用 ASM 重新合理的插入目标 Service 类中, 如果我们插入成功了, 那么我们运行的时候直接从 Service 中获取不就完事了嘛。

我们已经有存在目标 Service 类的 jar 文件了, 那么我们直接在此扫描这一个文件即可。 伪代码如下:

  • 扫描存在 Service 目标类的 jar 文件
扫描存在目标 Service 的 jar 文件 {
    1. 找到 Service 类并进行访问处理
    2. 注意注意!!! 这里是覆盖哈, 把我们处理完后的 jar 直接覆盖之前没有处理的 jar 文件哈, 如果在复制的话就重复了。 
}
  • 访问 Service 类找到我们要插入代码的方法, 并在此访问改方法进行处理
class ServiceClassVisitor(
    private val byteArray: ByteArray,
    private val needInsertInfo: List<Pair<String, String>>
) :
    ClassVisitor(Opcodes.ASM7) {

    override fun visitMethod(
        access: Int,
        name: String?,
        descriptor: String?,
        signature: String?,
        exceptions: Array<out String>?
    ): MethodVisitor {
        if(如果是我们要插入的方法){
            return 处理该方法
        }
        
        super.visitMethod(access, name, descriptor, signature, exceptions)
    }
}
  • 利用 ASM 合理的写入我们的目标 Service 中 getService 方法中
class ServiceClassMethodVisitor(
    private val needInsertInfo: List<Pair<String, String>>,
    methodVisitor: MethodVisitor, access: Int, name: String?, desc: String?
) :
    AdviceAdapter(Opcodes.ASM7, methodVisitor, access, name, desc) {

    override fun onMethodExit(opcode: Int) {
        super.onMethodExit(opcode)

        mv.visitCode()
        val label0 = Label()
        val label1 = Label()
        val label2 = Label()
        mv.visitTryCatchBlock(label0, label1, label2, null)
        val label3 = Label()
        val label4 = Label()
        mv.visitTryCatchBlock(label3, label4, label2, null)
        val label5 = Label()
        mv.visitTryCatchBlock(label2, label5, label2, null)
        val label6 = Label()
        mv.visitLabel(label6)
        mv.visitVarInsn(Opcodes.ALOAD, 0)
        mv.visitMethodInsn(
            Opcodes.INVOKEVIRTUAL,
            ServiceAssistantConstant.PATH_CLASS,
            ServiceAssistantConstant.DESC_GET_NAME,
            ServiceAssistantConstant.DESC_RETURN_STRING_FULL,
            false
        )
        mv.visitVarInsn(Opcodes.ASTORE, 1)
        val label7 = Label()
        mv.visitLabel(label7)
        mv.visitFieldInsn(
            Opcodes.GETSTATIC,
            ServiceAssistantConstant.PATH_SERVICE_REFERENCE,
            ServiceAssistantConstant.DESC_S_SERVICE_RELATION,
            ServiceAssistantConstant.SIGNATURE_MAP
        )
        mv.visitVarInsn(Opcodes.ALOAD, 1)
        mv.visitMethodInsn(
            Opcodes.INVOKEINTERFACE,
            ServiceAssistantConstant.PATH_MAP,
            ServiceAssistantConstant.DESC_GET,
            ServiceAssistantConstant.SIGNATURE_OBJECT_OBJECT,
            true
        )
        mv.visitVarInsn(Opcodes.ASTORE, 2)
        val label8 = Label()
        mv.visitLabel(label8)
        mv.visitVarInsn(Opcodes.ALOAD, 2)
        val label9 = Label()
        mv.visitJumpInsn(Opcodes.IFNULL, label9)
        val label10 = Label()
        mv.visitLabel(label10)
        mv.visitVarInsn(Opcodes.ALOAD, 2)
        mv.visitInsn(Opcodes.ARETURN)
        mv.visitLabel(label9)
        mv.visitFrame(
            Opcodes.F_APPEND,
            2,
            arrayOf<Any>(
                ServiceAssistantConstant.PATH_STRING,
                ServiceAssistantConstant.PATH_OBJECT
            ),
            0,
            null
        )
        mv.visitFieldInsn(
            Opcodes.GETSTATIC,
            ServiceAssistantConstant.PATH_SERVICE_REFERENCE,
            ServiceAssistantConstant.DESC_S_LOCK,
            ServiceAssistantConstant.SIGNATURE_OBJECT
        )
        mv.visitInsn(Opcodes.DUP)
        mv.visitVarInsn(Opcodes.ASTORE, 3)
        mv.visitInsn(Opcodes.MONITORENTER)
        mv.visitLabel(label0)
        mv.visitFieldInsn(
            Opcodes.GETSTATIC,
            ServiceAssistantConstant.PATH_SERVICE_REFERENCE,
            ServiceAssistantConstant.DESC_S_SERVICE_RELATION,
            ServiceAssistantConstant.SIGNATURE_MAP
        )
        mv.visitVarInsn(Opcodes.ALOAD, 1)
        mv.visitMethodInsn(
            Opcodes.INVOKEINTERFACE,
            ServiceAssistantConstant.PATH_MAP,
            ServiceAssistantConstant.DESC_GET,
            ServiceAssistantConstant.SIGNATURE_OBJECT_OBJECT,
            true
        )
        mv.visitVarInsn(Opcodes.ASTORE, 2)
        val label11 = Label()
        mv.visitLabel(label11)
        mv.visitVarInsn(Opcodes.ALOAD, 2)
        mv.visitJumpInsn(Opcodes.IFNULL, label3)
        val label12 = Label()
        mv.visitLabel(label12)
        mv.visitVarInsn(Opcodes.ALOAD, 2)
        mv.visitVarInsn(Opcodes.ALOAD, 3)
        mv.visitInsn(Opcodes.MONITOREXIT)
        mv.visitLabel(label1)
        mv.visitInsn(Opcodes.ARETURN)
        mv.visitLabel(label3)

        needInsertInfo.forEach {
            mv.visitFrame(
                Opcodes.F_APPEND,
                1,
                arrayOf<Any>(ServiceAssistantConstant.PATH_OBJECT),
                0,
                null
            )
            mv.visitLdcInsn(it.first)
            mv.visitVarInsn(Opcodes.ALOAD, 1)
            mv.visitMethodInsn(
                Opcodes.INVOKEVIRTUAL,
                ServiceAssistantConstant.PATH_STRING,
                ServiceAssistantConstant.DESC_EQUALS,
                ServiceAssistantConstant.SIGNATURE_OBJECT_BOOLEAN,
                false
            )
            val label13 = Label()
            mv.visitJumpInsn(Opcodes.IFEQ, label13)
            val label14 = Label()
            mv.visitLabel(label14)
            mv.visitTypeInsn(
                Opcodes.NEW,
                it.second.replace(".", "/")
            )
            mv.visitInsn(Opcodes.DUP)
            mv.visitMethodInsn(
                Opcodes.INVOKESPECIAL,
                it.second.replace(".", "/"),
                ServiceAssistantConstant.DESC_INIT,
                ServiceAssistantConstant.DESC_SIGNATURE_CONSTRUCTORS,
                false
            )
            mv.visitVarInsn(Opcodes.ASTORE, 2)
            val label15 = Label()
            mv.visitLabel(label15)
            mv.visitFieldInsn(
                Opcodes.GETSTATIC,
                ServiceAssistantConstant.PATH_SERVICE_REFERENCE,
                ServiceAssistantConstant.DESC_S_SERVICE_RELATION,
                ServiceAssistantConstant.SIGNATURE_MAP
            )
            mv.visitVarInsn(Opcodes.ALOAD, 1)
            mv.visitVarInsn(Opcodes.ALOAD, 2)
            mv.visitMethodInsn(
                Opcodes.INVOKEINTERFACE,
                ServiceAssistantConstant.PATH_MAP,
                ServiceAssistantConstant.DESC_PUT,
                ServiceAssistantConstant.SIGNATURE_OBJECT_OBJECT_OBJECT,
                true
            )
            mv.visitInsn(Opcodes.POP)
            mv.visitLabel(label13)
        }

        mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null)
        mv.visitVarInsn(Opcodes.ALOAD, 2)
        mv.visitVarInsn(Opcodes.ALOAD, 3)
        mv.visitInsn(Opcodes.MONITOREXIT)
        mv.visitLabel(label4)
        mv.visitInsn(Opcodes.ARETURN)
        mv.visitLabel(label2)
        mv.visitFrame(
            Opcodes.F_SAME1,
            0,
            null,
            1,
            arrayOf<Any>(ServiceAssistantConstant.PATH_THROWABLE)
        )
        mv.visitVarInsn(Opcodes.ASTORE, 4)
        mv.visitVarInsn(Opcodes.ALOAD, 3)
        mv.visitInsn(Opcodes.MONITOREXIT)
        mv.visitLabel(label5)
        mv.visitVarInsn(Opcodes.ALOAD, 4)
        mv.visitInsn(Opcodes.ATHROW)
        val label16 = Label()
        mv.visitLabel(label16)
        mv.visitLocalVariable(
            ServiceAssistantConstant.DESC_CLAZZ,
            ServiceAssistantConstant.SIGNATURE_CLASS,
            ServiceAssistantConstant.SIGNATURE_CLASS_T_T,
            label6,
            label16,
            0
        )
        mv.visitLocalVariable(
            ServiceAssistantConstant.DESC_NAME,
            ServiceAssistantConstant.SIGNATURE_STRING,
            null,
            label7,
            label16,
            1
        )
        mv.visitLocalVariable(
            ServiceAssistantConstant.DESC_SERVICE,
            ServiceAssistantConstant.SIGNATURE_OBJECT,
            null,
            label8,
            label16,
            2
        )
        mv.visitMaxs(3, 5)
        mv.visitEnd()

    }

}

其中这里面哪些代码我也看不懂, 我们可以利用 ASM 插件生成这些代码, 其插件是 ASM Bytecode Viewer。 这个使用的技巧和心得我们在后面进行讲解。

总之利用上面的在 Service 类最终的代码如下:

object Service {

    private val sServiceRelation = mutableMapOf<String, Any?>()
    private val sLock = Any()

    @JvmStatic
    fun <T> getService(clazz: Class<T>): T? {
        val name = clazz.name
        var service = sServiceRelation[name]
        if (service != null) {
            return service as T?
        }
        synchronized(sLock) {
            service = sServiceRelation[name]
            if (service != null) {
                return service as T?
            }

            if ("cn.xiaoxige.loginapi.ILoginAbilityApi" == name) {
                service = LoginAbilityApiImpl()
                sServiceRelation[name] = service
                return service as T?
            }

            if ("xxx" == name) {
                service = xxxImpl()
                sServiceRelation[name] = service
                return service as T?
            }
        }
        return null
    }
}

看到了嘛, 这个时候使用 Service.getService(xxx::class.java) 即可获取到对应的实现啦!

注入的实现原理(注解处理器、 javepoet、 transform、 ASM)

这个的实现稍微比较复杂点, 总体思路如下:

通过注解处理器找到通过 NeedInjected 注解的类信息, 然后利用 javapoet 进行生成一个对应的代理初始化类(这个类的类名是以接口名 + Producer), 然后通过 transform 扫描所有的 Injected 注解的属性, 在对应的类的构造函数中对其进行赋值, 也就是调用 javepoet 生成的代理初始化类进行赋值。

让我们开始注解处理器收集 NeedInjected 注解的类信息并使用 javapoet 进行生成相应的代理初始化类吧。

注解处理器

@AutoService(Processor::class)
class AnnotationProcessor : AbstractProcessor() {

    private lateinit var mFiler: Filer

    private val mNeedInjectedInfo = mutableMapOf<String, Pair<String, Boolean>>()

    override fun init(p0: ProcessingEnvironment?) {
        super.init(p0)
        this.mFiler = p0.filer
    }
    
    override fun process(p0: MutableSet<out TypeElement>?, p1: RoundEnvironment?): Boolean {
        if (p0 == null || p0.isEmpty()) return false
        p0.forEach { element ->
            if (element.qualifiedName.contentEquals(NeedInjected::class.java.canonicalName)) {
                p1?.getElementsAnnotatedWith(NeedInjected::class.java)?.forEach {
                        // ......
                        // 收集信息
                    if (handleNeedInjected(it as TypeElement).not()) return false
                }
            }
        }
        
        // 生成相应的代理初始化类
        mNeedInjectedInfo.keys.forEach {
            val value = mNeedInjectedInfo[it]
            AutoWriteInjectedInfoProducer(
                it,
                value,
                mFiler
            ).write()
        }

        mNeedInjectedInfo.clear()

        return true
    }

    private fun handleNeedInjected(
        needInjected: TypeElement
    ): Boolean {
        val interfaces = needInjected.interfaces
        if (interfaces.isEmpty() || interfaces.size > 1) {
            e("Currently, only one interface injection is supported")
        }
        val interfacePath = interfaces[0].toString()
        val annotation = needInjected.getAnnotation(NeedInjected::class.java)
        mNeedInjectedInfo[interfacePath] =
            Pair(needInjected.qualifiedName.toString(), annotation.isSingleCase)
        return true
    }

}

其中 mNeedInjectedInfo 保存了所有 NeedInjected 注解信息和对应的类信息。 (Map<接口, Pair<类, 是否为单例>>)

利用 javapoet 生成代理初始化类


class AutoWriteInjectedInfoProducer(
    private val injectedInterface: String,
    private val needInjectedInfo: Pair<String, Boolean>?,
    private val filer: Filer
) {

    fun write() {

        // 生成类相关的信息
        val injectedInfoProducerFullClass = getInjectedProducerClassFullName()
        val injectedInfoProducerFullClassInfo =
            injectedInfoProducerFullClass.getPackageAndClassName()

        // 目标接口信息
        val injectedInterfaceInfo = injectedInterface.getPackageAndClassName()

        // 注解
        val annotation =
            AnnotationSpec.builder(ClassName.get("androidx.annotation", "Keep")).build()

        // 属性
        val field = createField(
            injectedInterfaceInfo.first,
            injectedInterfaceInfo.second
        )
        val lockField = createLockField()

        // 方法
        val method = createMethod(injectedInterfaceInfo)

        val autoClass = TypeSpec.classBuilder(injectedInfoProducerFullClassInfo.second)
            .addJavadoc("This class is a Service Assistant Processor transfer center class.\n which is automatically generated. Please do not make any changes.\n")
            .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
            .addAnnotation(annotation)
            .addField(lockField)
            .addField(field)
            .addMethod(method)
            .build()

        JavaFile.builder(injectedInfoProducerFullClassInfo.first, autoClass)
            .build().writeTo(filer)
    }

    private fun createField(packageInfo: String, className: String): FieldSpec {
        return FieldSpec.builder(ClassName.get(packageInfo, className), NAME_TARGET_INSTANCE)
            .addModifiers(Modifier.STATIC, Modifier.PRIVATE)
            .addJavadoc("target entity class")
            .initializer("null")
            .build()
    }

    private fun createLockField(): FieldSpec {
        return FieldSpec.builder(
            Any::class.java,
            "sLock",
            Modifier.PRIVATE,
            Modifier.FINAL,
            Modifier.STATIC
        )
            .addJavadoc("Changed mainly for lock guarantee instance\n")
            .initializer("""new ${'$'}T()""", Any::class.java)
            .build()
    }

    private fun createMethod(injectedInterfaceInfo: Pair<String, String>): MethodSpec {

        val methodSpaceBuilder = MethodSpec
            .methodBuilder(NAME_GET_TARGET_INSTANCE_METHOD)
            .addJavadoc("How to get the target instance")
            .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
            .returns(ClassName.get(injectedInterfaceInfo.first, injectedInterfaceInfo.second))

        // 如果未发现, 那么直接返回 null
        if (needInjectedInfo == null) {
            return methodSpaceBuilder.addStatement("return null").build()
        }

        // 生成目标对象的信息
        val needInjectedInterfaceInfo = needInjectedInfo.first.getPackageAndClassName()
        // 如果为非单例, 那么每次都会产生一个新对象
        if (!needInjectedInfo.second) {
            return methodSpaceBuilder.addStatement(
                """return new ${'$'}T()""",
                ClassName.get(needInjectedInterfaceInfo.first, needInjectedInterfaceInfo.second)
            ).build()
        }

        // 单例模式
        methodSpaceBuilder.beginControlFlow("if($NAME_TARGET_INSTANCE != null)")
        methodSpaceBuilder.addStatement("return $NAME_TARGET_INSTANCE")
        methodSpaceBuilder.endControlFlow()

        methodSpaceBuilder.beginControlFlow("synchronized(sLock)")

        // 再次判断是否为空
        methodSpaceBuilder.beginControlFlow("if($NAME_TARGET_INSTANCE != null)")
        methodSpaceBuilder.addStatement("return $NAME_TARGET_INSTANCE")
        methodSpaceBuilder.endControlFlow()

        methodSpaceBuilder.addStatement(
            """$NAME_TARGET_INSTANCE = new ${'$'}T()""",
            ClassName.get(needInjectedInterfaceInfo.first, needInjectedInterfaceInfo.second)
        )
        methodSpaceBuilder.addStatement("return $NAME_TARGET_INSTANCE")

        methodSpaceBuilder.endControlFlow()

        return methodSpaceBuilder.build()
    }

    private fun getInjectedProducerClassFullName(): String = "${injectedInterface}Producer"

    companion object {
        private const val NAME_TARGET_INSTANCE = "sInstance"
        private const val NAME_GET_TARGET_INSTANCE_METHOD = "getInstance"
    }
}

通过以上代码, 通过扫描进行生成的类如下:

  • 不是单例的模式生成的类
@Keep
public final class ISettingRepoProducer {

  private static final Object sLock = new Object();
  private static ISettingRepo sInstance = null;

  public static ISettingRepo getInstance() {
    return new SettingRepoImpl();
  }
}
  • 单例的模式生成的类
@Keep
public final class IAboutRepoProducer {

    private static final Object sLock = new Object();
    private static IAboutRepo sInstance = null;

    public static IAboutRepo getInstance() {
        if (sInstance != null) {
            return sInstance;
        }
        synchronized (sLock) {
            if (sInstance != null) {
                return sInstance;
            }
            sInstance = new AboutRepoImpl();
            return sInstance;
        }
    }
}

看到这里看来我们的前期工作已经做好了, 现在开始我们的 transform 利用 ASM 然后对属性在构造函数里进行赋值吧。

使用 transform 利用 ASM 对属性操作

这里需要考虑两个问题,

  • 就是如果一个类有多个构造函数呢, 不能内次都对其赋值吧! 这里的解决方式为, 在有 Injected 的类里生成一个 Boolean 属性, 在每一个构造函数中对这个变量进行判读, 如果没有赋值则进行赋值, 如果已经赋值了那么就不在进行赋值。
  • 如果我们扫描到 Injected 属性但是没有找到对应的代理初始化类咋整, 这个情况是需要考虑的哈, 可能用户忘记实现或没有引入改实现的库, 比如我依赖的实现没有引入呢对吧。这里的解决方式为, 在复制的时候进行 try, 先进行 Class.forName, 如果没有找到该类就不进行赋值。

访问所有的类找到存在 Injected 属性的类

class ServiceAssistantClassVisitor(
    private val byteArray: ByteArray,
    private val serviceTargetFindBack: () -> Unit,
    private val needScanClassInfoBack: (String, String) -> Unit
) : ClassVisitor(Opcodes.ASM7) {

    private lateinit var mVisitorClassName: String
    private var mIsInsertInitField = false
    private var mIsAutoInitFieldName: String? = null
    private val mFieldInfo = mutableMapOf<String, String>()

    override fun visit(
        version: Int,
        access: Int,
        name: String?,
        signature: String?,
        superName: String?,
        interfaces: Array<out String>?
    ) {
        super.visit(version, access, name, signature, superName, interfaces)
        this.mVisitorClassName = name ?: ""
        // 是否已经生成了控制重复赋值的变量
        this.mIsInsertInitField = false
        // 生成变量的名字
        this.mIsAutoInitFieldName = "is${DigestUtils.md5Hex(this.mVisitorClassName)}"
    }

    override fun visitField(
        access: Int,
        name: String?,
        descriptor: String?,
        signature: String?,
        value: Any?
    ): FieldVisitor {
        // 对该属性的注解进行访问
        return ServiceAssistantFieldVisitor(
            super.visitField(
                access,
                name,
                descriptor,
                signature,
                value
            )
        ) {
            // ......
            // 变量和对应接口的关系
            this.mFieldInfo[name] = descriptor
            // 如果没有生成控制变量进行控制
            if (!this.mIsInsertInitField) {
                cv.visitField(
                    Opcodes.ACC_VOLATILE or Opcodes.ACC_PRIVATE,
                    this.mIsAutoInitFieldName,
                    ServiceAssistantConstant.SIGNATURE_BOOLEAN,
                    null,
                    false
                ).visitEnd()
                this.mIsInsertInitField = true
            }
        }
    }

    override fun visitMethod(
        access: Int,
        name: String?,
        descriptor: String?,
        signature: String?,
        exceptions: Array<out String>?
    ): MethodVisitor {
        // 仅仅对构造函数进行处理
        return if (name == null || name != ServiceAssistantConstant.DESC_INIT || this.mFieldInfo.isEmpty()) {
            super.visitMethod(access, name, descriptor, signature, exceptions)
        } else {
            // 进行代码生成
            ServiceAssistantMethodVisitor(
                super.visitMethod(access, name, descriptor, signature, exceptions),
                this.mVisitorClassName,
                this.mIsAutoInitFieldName!!,
                this.mFieldInfo,
                access, name, descriptor
            )
        }

    }

    override fun visitEnd() {
        this.mIsInsertInitField = false
        this.mIsAutoInitFieldName = null
        super.visitEnd()
    }

}

对属性直接的访问

class ServiceAssistantFieldVisitor(
    fieldVisitor: FieldVisitor,
    private val targetAnnotationBack: () -> Unit
) :
    FieldVisitor(Opcodes.ASM7, fieldVisitor) {

    override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor {
        if (ServiceAssistantConstant.SIGNATURE_INJECTED_ANNOTATION == descriptor) {
            targetAnnotationBack.invoke()
        }
        return super.visitAnnotation(descriptor, visible)
    }

}

处理很简单, 就是如果是 Injected 注解的属性进行回调出去处理。

利用 ASM 对构造函数的赋值操作

class ServiceAssistantMethodVisitor(
    methodVisitor: MethodVisitor,
    private val visitorClassName: String,
    private val isAutoInitFieldName: String,
    private val fieldInfo: Map<String, String>,
    access: Int,
    name: String?,
    desc: String?
) : AdviceAdapter(Opcodes.ASM7, methodVisitor, access, name, desc) {

    override fun onMethodEnter() {
        super.onMethodEnter()

        mv.visitInsn(ACONST_NULL)
        mv.visitVarInsn(ALOAD, 0)
        mv.visitFieldInsn(
            GETFIELD,
            visitorClassName,
            isAutoInitFieldName,
            ServiceAssistantConstant.SIGNATURE_BOOLEAN
        )
        val label1 = Label()
        mv.visitJumpInsn(IF_ACMPEQ, label1)
        mv.visitVarInsn(ALOAD, 0)
        mv.visitFieldInsn(
            GETFIELD,
            visitorClassName,
            isAutoInitFieldName,
            ServiceAssistantConstant.SIGNATURE_BOOLEAN
        )
        mv.visitMethodInsn(
            INVOKEVIRTUAL,
            ServiceAssistantConstant.PATH_BOOLEAN,
            ServiceAssistantConstant.NAME_BOOLEAN,
            ServiceAssistantConstant.DESC_RETURN_BOOLEAN,
            false
        )
        val label2 = Label()
        mv.visitJumpInsn(IFNE, label2)
        mv.visitLabel(label1)

        fieldInfo.keys.forEach {
            val value = fieldInfo[it]
                ?: throw RuntimeException("Injection target interface signature error")
            insertInjectedProducer(it, value)
        }

        mv.visitVarInsn(ALOAD, 0)
        mv.visitInsn(ICONST_1)
        mv.visitMethodInsn(
            INVOKESTATIC,
            ServiceAssistantConstant.PATH_BOOLEAN,
            ServiceAssistantConstant.DESC_VALUE_OF,
            ServiceAssistantConstant.DESC_RETURN_BOOLEAN_FULL,
            false
        )
        mv.visitFieldInsn(
            PUTFIELD,
            visitorClassName,
            isAutoInitFieldName,
            ServiceAssistantConstant.SIGNATURE_BOOLEAN
        )
        mv.visitLabel(label2)

    }

    private fun insertInjectedProducer(name: String, injectedInterface: String) {

        val targetInterfaceProducer = ServiceAssistantConstant.getInjectedProducerClassFullName(
            injectedInterface.substring(
                1,
                injectedInterface.length - 1
            )
        )

        val label0 = Label()
        val label1 = Label()
        val label2 = Label()
        mv.visitTryCatchBlock(label0, label1, label2, ServiceAssistantConstant.PATH_EXCEPTION)
        mv.visitLabel(label0)
        mv.visitLineNumber(33, label0)
        mv.visitLdcInsn(targetInterfaceProducer.replace("/", "."))
        mv.visitMethodInsn(
            INVOKESTATIC,
            ServiceAssistantConstant.PATH_CLASS,
            ServiceAssistantConstant.DESC_FOR_NAME,
            ServiceAssistantConstant.SIGNATURE_STRING_CLASS,
            false
        )
        mv.visitInsn(POP)
        val label3 = Label()
        mv.visitLabel(label3)
        mv.visitLineNumber(34, label3)
        mv.visitVarInsn(ALOAD, 0)
        mv.visitMethodInsn(
            INVOKESTATIC,
            targetInterfaceProducer,
            ServiceAssistantConstant.NAME_GET_TARGET_INSTANCE_METHOD,
            "()$injectedInterface",
            false
        )
        mv.visitFieldInsn(
            PUTFIELD,
            visitorClassName,
            name,
            injectedInterface
        )
        mv.visitLabel(label1)
        mv.visitLineNumber(36, label1)
        val label4 = Label()
        mv.visitJumpInsn(GOTO, label4)
        mv.visitLabel(label2)
        mv.visitLineNumber(35, label2)
        mv.visitFrame(F_SAME1, 0, null, 1, arrayOf<Any>(ServiceAssistantConstant.PATH_EXCEPTION))
        mv.visitVarInsn(ASTORE, 1)
        mv.visitLabel(label4)
    }

}

通过上面的操作其生成的代码的对应关系如下

  • 需要注入的类
class TestTest {

    @Injected
    private IAccountRepo mAccountRepo;

    @Injected
    private IAboutRepo mAboutRepo;


}
  • 生成后的代码
class TestTest {
    @Injected
    private IAccountRepo mAccountRepo;
    private volatile Boolean is35887f203c598919a929ebf9203e4f24;
    @Injected
    private IAboutRepo mAboutRepo;

    TestTest() {
        if (null == this.is35887f203c598919a929ebf9203e4f24 || !this.is35887f203c598919a929ebf9203e4f24) {
            try {
                Class.forName("cn.xiaoxige.accountcomponent.repo.IAccountRepoProducer");
                this.mAccountRepo = IAccountRepoProducer.getInstance();
            } catch (Exception var3) {
            }

            try {
                Class.forName("cn.xiaoxige.serviceassistant.repo.IAboutRepoProducer");
                this.mAboutRepo = IAboutRepoProducer.getInstance();
            } catch (Exception var2) {
            }

            this.is35887f203c598919a929ebf9203e4f24 = true;
        }

    }
}

到这里, 你应该对其原理都明白了吧。 最后在说下使用 ASM 插件生成代码的技巧。

利用 ASM 生成代码的技巧(以注入为例)

通过上面我们可以知道我们需要写入构造函数的代码其实也很复杂, 其中我们拿到注入信息后还需要循环进行生成, 还是在 if 中间, 咋办? 技巧和心得如下

  • 装上 ASM Bytecode Viewer 插件, 这个技巧不说了哈, 我们利用的就是这个插件呢

  • 新建一个测试类手写想通的代码进行生成, 点击右键生成, 生成的 ASM 代码里我们可以看到 visitLineNumber(1, label0) 这行, 其中 1 就是我们源码里对应的行数, 如果我们生成的代码在 1 行之 10 行的代码, 那么我们主需要赋值 visitLineNumber 1 ~ visitLineNumber 11 之间的代码, 赋值后可以把 visitLineNumber 这行再删除掉哦, 有人说如果我源码 10 行就结尾了没有 11 行咋办, 你可以在源码里加上一个打印嘛, 比如 Log.e("tag", "")。

  • 光上面的技巧就行了嘛, 不行不行, 还不够, 按照注入为例, 我们拿到对应关系是需要循环生成的, 而且还是在 if 中间, 这个改怎么办呀, 别急, 我们可以分批进行。 以注入为例, 比如

class TestTest {

    @Injected
    private IAccountRepo mAccountRepo;

    private Boolean mIsInit;

    private void test() {

        if (null == mIsInit || !mIsInit) {

            Log.e("TAG", "");

            mIsInit = true;
        }

    }

    private void test1() {
        try {
            Class.forName("cn.xiaoxige.accountcomponent.repo.IAccountRepo");
            mAccountRepo = IAccountRepoProducer.getInstance();
        } catch (Exception e) {
        }
    }

}

那么好, 我们先找到 test 方法对应的 ASM 代码复制出来, 然后把打 log 的 ASM 代码删除, 这个利用第二个技巧也就是通过源码的行编号查找就很容易找到对应的位置哈。 在删除的 AMS 代码里就可以写 for 循环了哈, 然后再把 test1 方法生成对应的 ASM 复制放在循环里, 最后的最后别忘了把类名啥的用我们收集到的进行替换哈。

到这里就终结了。

其他

其中组件的设计、 以及该库的详细用法比如获取服务, 组件通讯, 组件回调, 注入, 等可以参考文章开头的 github 地址哈, 也可以参考其源码实现。 最后最后也希望大家可以给个 Star 哈哈。

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

推荐阅读更多精彩内容