組件通訊設(shè)計(jì)原理及注入實(shí)現(xiàn)原理
主要涉及一下核心知識(shí)點(diǎn)
- 注解
- 注解處理器
- 自動(dòng)生成代碼 (javapoet/kotlinpoet)
- 自定義插件
- ASM
- transform
本文主要設(shè)計(jì)設(shè)計(jì)和思考實(shí)現(xiàn)思路、 解決問題的方式及經(jīng)驗(yàn), 至于以上的各個(gè)知識(shí)點(diǎn)網(wǎng)上一大把, 讀者可自行學(xué)習(xí), 這里不再陳述。
所有功能已進(jìn)行實(shí)現(xiàn), 其用方式依賴及實(shí)現(xiàn)源碼已在 Github 上 ServiceAssistant, 該庫不管組件通訊的 Service 還是進(jìn)行注入操作 Injected 都是懶加載, 而且一步到位, 跟用戶直接設(shè)計(jì)一個(gè)單例對(duì)象或者 new 一個(gè)對(duì)象一樣, 用戶可以直接去依賴使用或者閱讀源碼。 當(dāng)然整個(gè)庫使用和一個(gè)組件化的設(shè)計(jì)思路及寫法也可直接閱讀 Demo。
以組件通訊開始
Android 項(xiàng)目的組件化的好處不在多提, 我們知道組件之間是單獨(dú)獨(dú)立不能相互依賴的, 那么組件化可能遇到的一個(gè)很大的阻力就是組件之間怎么通訊呢? 當(dāng)然現(xiàn)在也有很好的通訊方式比如 Aroute 等, 但有沒有更加靈活和方便的或者說一個(gè)新的方式去進(jìn)行組件通訊或者注入呢, 還有就是解決組件之間設(shè)置回調(diào), 這就是我寫這個(gè)庫和文字的初衷。
我們?cè)O(shè)計(jì)組件化, 那么每個(gè)組件都是可以單獨(dú)運(yùn)行的, 最簡(jiǎn)單的模型如下:
以上可能是最小的一個(gè)組件化模型了, 從上面的圖設(shè)計(jì)可以思考如下:
- 組件 A 和組件 B 之間是沒有任何連線也就是沒有依賴, 組件 A 和 組件 B 怎么相互調(diào)用進(jìn)行通訊呢?
- 如果想讓組件 A 和 組件 B 單獨(dú)運(yùn)行, 當(dāng)然 App 殼也可以單獨(dú)運(yùn)行。 乖乖, 不說組件 A, 組件 B 單獨(dú)運(yùn)行了, 當(dāng) 組件 A 或者 組件 B 設(shè)置成單獨(dú)運(yùn)行時(shí), App 殼就不能去依賴這個(gè)單獨(dú)運(yùn)行的組件了, 想讓 App 殼也單獨(dú)運(yùn)行, 那么 App 殼就不能強(qiáng)依賴于組件了, 這該怎么解決?
當(dāng)然以上問題都在該庫中解決, 還有一個(gè)驚喜就是在實(shí)現(xiàn)該庫時(shí)順便加入了注入的另一個(gè)功能, 也方便使用者可以使用該庫可以方便使用注入功能。
那么好我們就開始去分析和解決以上問題。
組件之間的通訊
通過上面的組件圖可以看出, 組件之間是沒有依賴的。 我們思考去想想, 組件 A 想去調(diào)用組件 B 的某個(gè)功能, 組件 A 必須要知道 組件 B 都提供出來了什么功能吧, 如果組件 A 完全不知道 B 給我們提供出什么的話, 談什么去調(diào)用 組件 B 呢, 就行我們要去調(diào)用 Android 系統(tǒng)的服務(wù)時(shí), 就比如我們?nèi)フ{(diào)用獲取圖片, 我們總要知道系統(tǒng)給我們提供了什么能力去調(diào)用吧, 所以我們?cè)O(shè)計(jì)出一個(gè)組件的時(shí)候需要設(shè)計(jì)我們向外部拋出公開什么能力供別人去調(diào)用。
大概設(shè)計(jì)圖如下:
其中 ApiA 和 ApiB 里都是接口, 也就是組件 A 和組件 B 向外部公開出來的能力, 當(dāng)然比如組件 A 提供的能力 ApiA 的實(shí)現(xiàn)肯定是組件 A 內(nèi)部去實(shí)現(xiàn), 組件 B 提供出來的能力 ApiB 肯定是 組件 B 內(nèi)部去實(shí)現(xiàn)。 如果組件 A 去調(diào)用組件 B 的能力, 只需要知道組件 B 提供出來什么能力, 也就是去依賴 ApiB 即可, 反之 組件 B 去調(diào)用組件 A 的能力也是一樣。 那么組件去依賴另一個(gè)組件提供出來的能力也就是接口, 怎么去調(diào)用到對(duì)應(yīng)的實(shí)現(xiàn)呢, 這, 這就是該庫要做的事情了。
先介紹下庫的使用:
比如我們?cè)诮M件 A 中公開登錄的能力 (ApiA)
interface ILoginAbilityApi {
/**
* 登錄
*/
fun toLogin(context: Context)
fun addLoginStateChangedListener(listener: ILoginStateChangedListener)
fun removeLoginStateChangeListener(listener: ILoginStateChangedListener)
interface ILoginStateChangedListener {
fun change(state: Boolean)
}
}
在組件 A 中去實(shí)現(xiàn)該能力
@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, 去調(diào)用登錄的寫法如下:
val service = Service.getService(ILoginAbilityApi::class.java)
if (service == null) {
Toast.makeText(this, "未發(fā)現(xiàn)登錄組件", Toast.LENGTH_SHORT).show()
return
}
service.toLogin(this)
在這里請(qǐng)?jiān)试S我提下注入使用:
定義接口:
interface IAccountRepo {
fun getAccountData(): String
}
實(shí)現(xiàn)接口:
@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
}
那么好, 那我們剛才說的第二個(gè)問題不也就迎刃而解了嘛, 請(qǐng)看下圖:
okk, 開始我們的實(shí)現(xiàn)思考及原理之旅。
組件化通訊之 Service 的實(shí)現(xiàn)原理(注解、 插件、 ASM、 transform)
總體實(shí)現(xiàn)思路: 我們把所有 api 及實(shí)現(xiàn)服務(wù)對(duì)應(yīng)收集起來, 放到一個(gè)固定的 map 中, 當(dāng)然中間要去實(shí)現(xiàn)的時(shí)候要考慮到其單例和懶加載。 然后我們需要什么服務(wù)時(shí)直接去從這個(gè) map 拿不就 OK 了嗎? 是的。 如果我們從這個(gè)固定 map 中取, 取到了就是找到了該組件, 如果取不到就是沒有找到該組件。
map 里我們存什么呢? 肯定是 api 和實(shí)現(xiàn)的對(duì)應(yīng)關(guān)系哈。
收集所有的 api 及實(shí)現(xiàn)
通過 transfrom 去掃描所有的 api 及實(shí)現(xiàn), 那么最簡(jiǎn)單的來個(gè)注解吧, 然后直接掃描到該注解然后獲取該類的信息收集出來就可以, 所以我們加一個(gè) @Service 注解。
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class Service
我們掃描搜集的對(duì)應(yīng)關(guān)系利用 ASM 合理的保存到一個(gè)固定 Service 中, 當(dāng)然這個(gè)也是使用者獲取對(duì)應(yīng)服務(wù)的一個(gè)入口。 其中我們要保存的對(duì)應(yīng)關(guān)系合理的寫入 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 把掃描出來的對(duì)應(yīng)關(guān)系保存到 mNeedScanClassInfo 中。
1.2 把里面處理完的放到到輸出文件中。
}
}
2. 掃描所有 jar 中的文件 {
ServiceAssistantClassVisitor(去掃描處理) {
2.1 把掃描出來的對(duì)應(yīng)關(guān)系保存到 mNeedScanClassInfo 中。
2.2 把里面處理完的放到到輸出文件中。
2.3 如果掃描到我們需要的寫入對(duì)應(yīng)關(guān)系的類的輸出文件進(jìn)行先保存只 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 ?: ""
// 這里就是如果掃描到我們要存放對(duì)應(yīng)關(guān)系的 Service 類, 回調(diào)出去。
if (this.mVisitorClassName == ServiceAssistantConstant.PATH_SERVICE_REFERENCE) {
// is service
serviceTargetFindBack.invoke()
}
}
override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor {
descriptor?.let {
// 我們只關(guān)注的 Service 注解
if (it.indexOf(ServiceAssistantConstant.SIGNATURE_SERVICE_ANNOTATION) < 0) return@let
// ......
// 獲取出來我們加入注解的對(duì)應(yīng)關(guān)系并回調(diào)出去進(jìn)行收集
needScanClassInfoBack.invoke(
targetInterface.replace("/", "."),
mVisitorClassName
)
}
return super.visitAnnotation(descriptor, visible)
}
}
通過我們以上 transform 1 和 2 步我們可以得到所有的服務(wù)對(duì)應(yīng)關(guān)系及我們需要插入代碼的 Service 類所在的文件。
那么好我們現(xiàn)在要做的就是把所收集的對(duì)應(yīng)關(guān)系使用 ASM 重新合理的插入目標(biāo) Service 類中, 如果我們插入成功了, 那么我們運(yùn)行的時(shí)候直接從 Service 中獲取不就完事了嘛。
我們已經(jīng)有存在目標(biāo) Service 類的 jar 文件了, 那么我們直接在此掃描這一個(gè)文件即可。 偽代碼如下:
- 掃描存在 Service 目標(biāo)類的 jar 文件
掃描存在目標(biāo) Service 的 jar 文件 {
1. 找到 Service 類并進(jìn)行訪問處理
2. 注意注意?。?! 這里是覆蓋哈, 把我們處理完后的 jar 直接覆蓋之前沒有處理的 jar 文件哈, 如果在復(fù)制的話就重復(fù)了。
}
- 訪問 Service 類找到我們要插入代碼的方法, 并在此訪問改方法進(jìn)行處理
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 合理的寫入我們的目標(biāo) 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。 這個(gè)使用的技巧和心得我們?cè)诤竺孢M(jìn)行講解。
總之利用上面的在 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
}
}
看到了嘛, 這個(gè)時(shí)候使用 Service.getService(xxx::class.java) 即可獲取到對(duì)應(yīng)的實(shí)現(xiàn)啦!
注入的實(shí)現(xiàn)原理(注解處理器、 javepoet、 transform、 ASM)
這個(gè)的實(shí)現(xiàn)稍微比較復(fù)雜點(diǎn), 總體思路如下:
通過注解處理器找到通過 NeedInjected 注解的類信息, 然后利用 javapoet 進(jìn)行生成一個(gè)對(duì)應(yīng)的代理初始化類(這個(gè)類的類名是以接口名 + Producer), 然后通過 transform 掃描所有的 Injected 注解的屬性, 在對(duì)應(yīng)的類的構(gòu)造函數(shù)中對(duì)其進(jìn)行賦值, 也就是調(diào)用 javepoet 生成的代理初始化類進(jìn)行賦值。
讓我們開始注解處理器收集 NeedInjected 注解的類信息并使用 javapoet 進(jìn)行生成相應(yīng)的代理初始化類吧。
注解處理器
@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
}
}
}
// 生成相應(yīng)的代理初始化類
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 注解信息和對(duì)應(yīng)的類信息。 (Map<接口, Pair<類, 是否為單例>>)
利用 javapoet 生成代理初始化類
class AutoWriteInjectedInfoProducer(
private val injectedInterface: String,
private val needInjectedInfo: Pair<String, Boolean>?,
private val filer: Filer
) {
fun write() {
// 生成類相關(guān)的信息
val injectedInfoProducerFullClass = getInjectedProducerClassFullName()
val injectedInfoProducerFullClassInfo =
injectedInfoProducerFullClass.getPackageAndClassName()
// 目標(biāo)接口信息
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))
// 如果未發(fā)現(xiàn), 那么直接返回 null
if (needInjectedInfo == null) {
return methodSpaceBuilder.addStatement("return null").build()
}
// 生成目標(biāo)對(duì)象的信息
val needInjectedInterfaceInfo = needInjectedInfo.first.getPackageAndClassName()
// 如果為非單例, 那么每次都會(huì)產(chǎn)生一個(gè)新對(duì)象
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"
}
}
通過以上代碼, 通過掃描進(jìn)行生成的類如下:
- 不是單例的模式生成的類
@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;
}
}
}
看到這里看來我們的前期工作已經(jīng)做好了, 現(xiàn)在開始我們的 transform 利用 ASM 然后對(duì)屬性在構(gòu)造函數(shù)里進(jìn)行賦值吧。
使用 transform 利用 ASM 對(duì)屬性操作
這里需要考慮兩個(gè)問題,
- 就是如果一個(gè)類有多個(gè)構(gòu)造函數(shù)呢, 不能內(nèi)次都對(duì)其賦值吧! 這里的解決方式為, 在有 Injected 的類里生成一個(gè) Boolean 屬性, 在每一個(gè)構(gòu)造函數(shù)中對(duì)這個(gè)變量進(jìn)行判讀, 如果沒有賦值則進(jìn)行賦值, 如果已經(jīng)賦值了那么就不在進(jìn)行賦值。
- 如果我們掃描到 Injected 屬性但是沒有找到對(duì)應(yīng)的代理初始化類咋整, 這個(gè)情況是需要考慮的哈, 可能用戶忘記實(shí)現(xiàn)或沒有引入改實(shí)現(xiàn)的庫, 比如我依賴的實(shí)現(xiàn)沒有引入呢對(duì)吧。這里的解決方式為, 在復(fù)制的時(shí)候進(jìn)行 try, 先進(jìn)行 Class.forName, 如果沒有找到該類就不進(jìn)行賦值。
訪問所有的類找到存在 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 ?: ""
// 是否已經(jīng)生成了控制重復(fù)賦值的變量
this.mIsInsertInitField = false
// 生成變量的名字
this.mIsAutoInitFieldName = "is${DigestUtils.md5Hex(this.mVisitorClassName)}"
}
override fun visitField(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
value: Any?
): FieldVisitor {
// 對(duì)該屬性的注解進(jìn)行訪問
return ServiceAssistantFieldVisitor(
super.visitField(
access,
name,
descriptor,
signature,
value
)
) {
// ......
// 變量和對(duì)應(yīng)接口的關(guān)系
this.mFieldInfo[name] = descriptor
// 如果沒有生成控制變量進(jìn)行控制
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 {
// 僅僅對(duì)構(gòu)造函數(shù)進(jìn)行處理
return if (name == null || name != ServiceAssistantConstant.DESC_INIT || this.mFieldInfo.isEmpty()) {
super.visitMethod(access, name, descriptor, signature, exceptions)
} else {
// 進(jìn)行代碼生成
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()
}
}
對(duì)屬性直接的訪問
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)
}
}
處理很簡(jiǎn)單, 就是如果是 Injected 注解的屬性進(jìn)行回調(diào)出去處理。
利用 ASM 對(duì)構(gòu)造函數(shù)的賦值操作
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)
}
}
通過上面的操作其生成的代碼的對(duì)應(yīng)關(guān)系如下
- 需要注入的類
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;
}
}
}
到這里, 你應(yīng)該對(duì)其原理都明白了吧。 最后在說下使用 ASM 插件生成代碼的技巧。
利用 ASM 生成代碼的技巧(以注入為例)
通過上面我們可以知道我們需要寫入構(gòu)造函數(shù)的代碼其實(shí)也很復(fù)雜, 其中我們拿到注入信息后還需要循環(huán)進(jìn)行生成, 還是在 if 中間, 咋辦? 技巧和心得如下
裝上 ASM Bytecode Viewer 插件, 這個(gè)技巧不說了哈, 我們利用的就是這個(gè)插件呢
新建一個(gè)測(cè)試類手寫想通的代碼進(jìn)行生成, 點(diǎn)擊右鍵生成, 生成的 ASM 代碼里我們可以看到 visitLineNumber(1, label0) 這行, 其中 1 就是我們?cè)创a里對(duì)應(yīng)的行數(shù), 如果我們生成的代碼在 1 行之 10 行的代碼, 那么我們主需要賦值 visitLineNumber 1 ~ visitLineNumber 11 之間的代碼, 賦值后可以把 visitLineNumber 這行再刪除掉哦, 有人說如果我源碼 10 行就結(jié)尾了沒有 11 行咋辦, 你可以在源碼里加上一個(gè)打印嘛, 比如 Log.e("tag", "")。
光上面的技巧就行了嘛, 不行不行, 還不夠, 按照注入為例, 我們拿到對(duì)應(yīng)關(guān)系是需要循環(huán)生成的, 而且還是在 if 中間, 這個(gè)改怎么辦呀, 別急, 我們可以分批進(jìn)行。 以注入為例, 比如
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 方法對(duì)應(yīng)的 ASM 代碼復(fù)制出來, 然后把打 log 的 ASM 代碼刪除, 這個(gè)利用第二個(gè)技巧也就是通過源碼的行編號(hào)查找就很容易找到對(duì)應(yīng)的位置哈。 在刪除的 AMS 代碼里就可以寫 for 循環(huán)了哈, 然后再把 test1 方法生成對(duì)應(yīng)的 ASM 復(fù)制放在循環(huán)里, 最后的最后別忘了把類名啥的用我們收集到的進(jìn)行替換哈。
到這里就終結(jié)了。
其他
其中組件的設(shè)計(jì)、 以及該庫的詳細(xì)用法比如獲取服務(wù), 組件通訊, 組件回調(diào), 注入, 等可以參考文章開頭的 github 地址哈, 也可以參考其源碼實(shí)現(xiàn)。 最后最后也希望大家可以給個(gè) Star 哈哈。