组件通讯设计原理及注入实现原理
组件通讯设计原理及注入实现原理
主要涉及一下核心知识点
- 注解
- 注解处理器
- 自动生成代码 (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 哈哈。