Android開(kāi)發(fā)AsmClassVisitorFactory使用詳解
前言
之前就和大家介紹過(guò)AGP(Android Gradle Plugin) 7.0.0
版本之后Transform
已經(jīng)過(guò)期即將廢棄的事情。而且也簡(jiǎn)單的介紹了替換的方式是Transform Action
,經(jīng)過(guò)我這一陣子的學(xué)習(xí)和調(diào)研,發(fā)現(xiàn)只能說(shuō)答對(duì)了一半吧。下面介紹個(gè)新東西AsmClassVisitorFactory
。
com.android.build.api.instrumentation.AsmClassVisitorFactory
A factory to create class visitor objects to instrument classes.
The implementation of this interface must be an abstract class where the parameters and instrumentationContext are left unimplemented. The class must have an empty constructor which will be used to construct the factory object.
當(dāng)前官方推薦使用的應(yīng)該是這個(gè)類(lèi),這個(gè)類(lèi)的底層實(shí)現(xiàn)就是基于gradle
原生的Transform Action
,這次的學(xué)習(xí)過(guò)程其實(shí)走了一點(diǎn)點(diǎn)彎路,一開(kāi)始嘗試的是Transform Action
,但是貌似彎彎繞繞的,最后也沒(méi)有成功,而且Transform Action
的輸入產(chǎn)物都是單一文件,修改也是針對(duì)單一文件的,所以貌似也不完全是一個(gè)很好的替換方案,之前文章介紹的那種復(fù)雜的asm操作則無(wú)法負(fù)荷了。
AsmClassVisitorFactory
根據(jù)官方說(shuō)法,編譯速度會(huì)有提升,大概18%左右,這個(gè)下面我們會(huì)在使用階段對(duì)其進(jìn)行介紹的。
我們先從AsmClassVisitorFactory
這個(gè)抽象接口開(kāi)始介紹起吧。
AsmClassVisitorFactory
@Incubating interface AsmClassVisitorFactory<ParametersT : InstrumentationParameters> : Serializable { /** * The parameters that will be instantiated, configured using the given config when registering * the visitor, and injected on instantiation. * * This field must be left unimplemented. */ @get:Nested val parameters: Property<ParametersT> /** * Contains parameters to help instantiate the visitor objects. * * This field must be left unimplemented. */ @get:Nested val instrumentationContext: InstrumentationContext /** * Creates a class visitor object that will visit a class with the given [classContext]. The * returned class visitor must delegate its calls to [nextClassVisitor]. * * The given [classContext] contains static information about the classes before starting the * instrumentation process. Any changes in interfaces or superclasses for the class with the * given [classContext] or for any other class in its classpath by a previous visitor will * not be reflected in the [classContext] object. * * [classContext] can also be used to get the data for classes that are in the runtime classpath * of the class being visited. * * This method must handle asynchronous calls. * * @param classContext contains information about the class that will be instrumented by the * returned class visitor. * @param nextClassVisitor the [ClassVisitor] to which the created [ClassVisitor] must delegate * method calls. */ fun createClassVisitor( classContext: ClassContext, nextClassVisitor: ClassVisitor ): ClassVisitor /** * Whether or not the factory wants to instrument the class with the given [classData]. * * If returned true, [createClassVisitor] will be called and the returned class visitor will * visit the class. * * This method must handle asynchronous calls. */ fun isInstrumentable(classData: ClassData): Boolean }
簡(jiǎn)單的分析下這個(gè)接口,我們要做的就是在createClassVisitor
這個(gè)方法中返回一個(gè)ClassVisitor
,正常我們?cè)跇?gòu)造ClassVisitor
實(shí)例的時(shí)候是需要傳入下一個(gè)ClassVisitor
實(shí)例的,所以我們之后在new的時(shí)候傳入nextClassVisitor就行了。
另外就是isInstrumentable
,這個(gè)方法是判斷當(dāng)前類(lèi)是否要進(jìn)行掃描,因?yàn)槿绻蓄?lèi)都要通過(guò)ClassVisitor進(jìn)行掃描還是太耗時(shí)了,我們可以通過(guò)這個(gè)方法過(guò)濾掉很多我們不需要掃描的類(lèi)。
@Incubating interface ClassData { /** * Fully qualified name of the class. */ val className: String /** * List of the annotations the class has. */ val classAnnotations: List<String> /** * List of all the interfaces that this class or a superclass of this class implements. */ val interfaces: List<String> /** * List of all the super classes that this class or a super class of this class extends. */ val superClasses: List<String> }
ClassData
并不是asm的api,所以其中包含的內(nèi)容相對(duì)來(lái)說(shuō)比較少,但是應(yīng)該也勉強(qiáng)夠用了。這部分大家簡(jiǎn)單看看就行了,就不多做介紹了呢。
新的Extension
AGP版本升級(jí)之后,應(yīng)該是為了區(qū)分新舊版的Extension
,所以在AppExtension
的基礎(chǔ)上,新增了一個(gè)AndroidComponentsExtension
出來(lái)。
我們的transformClassesWith
就需要注冊(cè)在這個(gè)上面。這個(gè)需要考慮到變種,和之前的Transform
還是有比較大的區(qū)別的,這樣我們就可以基于不同的變種增加對(duì)應(yīng)的適配工作了。
val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java) androidComponents.onVariants { variant -> variant.transformClassesWith(PrivacyClassVisitorFactory::class.java, InstrumentationScope.ALL) {} variant.setAsmFramesComputationMode(FramesComputationMode.COPY_FRAMES) }
實(shí)戰(zhàn)
這次還是在之前的敏感權(quán)限api替換的字節(jié)碼替換工具的基礎(chǔ)上進(jìn)行測(cè)試開(kāi)發(fā)。
ClassVisitor
看看我們正常是如何寫(xiě)一個(gè)簡(jiǎn)單的ClassVisitor的。
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassVisitor methodFilterCV = new ClassFilterVisitor(classWriter); ClassReader cr = new ClassReader(srcClass); cr.accept(methodFilterCV, ClassReader.SKIP_DEBUG); return classWriter.toByteArray();
首先我們會(huì)構(gòu)造好一個(gè)空的ClassWriter
,接著會(huì)構(gòu)造一個(gè)ClassVisitor
實(shí)例,然后傳入這個(gè)ClassWriter
。然后我們構(gòu)造一個(gè)ClassReader
實(shí)例,然后將byte數(shù)組傳入,之后調(diào)用classReader.accept方法,之后我們就能在visitor中逐個(gè)訪(fǎng)問(wèn)數(shù)據(jù)了。
那么其實(shí)我們的類(lèi)信息,方法啥的都是通過(guò)ClassReader讀入的,然后由當(dāng)前的ClassVisitor
訪(fǎng)問(wèn)完之后交給我們最后一個(gè)ClassWriter
。
其中ClassWriter
也是一個(gè)ClassVisitor
對(duì)象,他復(fù)雜重新將修改過(guò)的類(lèi)轉(zhuǎn)化成byte數(shù)據(jù)??梢钥吹贸鰜?lái)ClassVisitor
就有一個(gè)非常簡(jiǎn)單的鏈表結(jié)構(gòu),之后逐層向下訪(fǎng)問(wèn)。
介紹完了這個(gè)哦,我們做個(gè)大膽的假設(shè),如果我們這個(gè)ClassVisitor
鏈表前插入幾個(gè)不同的ClassVisitor
,那么我們是不是就可以讓asm修改逐個(gè)生效,然后也不需要多余的io操作了呢。這就是新的asm api 的設(shè)計(jì)思路了,也是我們這邊大佬的字節(jié)碼框架大佬的設(shè)計(jì)。另外bytex內(nèi)的設(shè)計(jì)思路也是如此。
tips ClassNode 因?yàn)槭窍壬傻恼Z(yǔ)法樹(shù),所以和一般的ClassVisitor有點(diǎn)小區(qū)別,需要在visitEnd方法內(nèi)調(diào)用accept(next)
實(shí)際代碼分析
接下來(lái)我們上實(shí)戰(zhàn)咯。我將之前的代碼套用到這次的邏輯上來(lái)。
abstract class PrivacyClassVisitorFactory : AsmClassVisitorFactory<InstrumentationParameters.None> { override fun createClassVisitor(classContext: ClassContext, nextClassVisitor: ClassVisitor): ClassVisitor { return PrivacyClassNode(nextClassVisitor) } override fun isInstrumentable(classData: ClassData): Boolean { return true } }
我在isInstrumentable都返回的是true,其實(shí)我可以將掃描規(guī)則限定在特定包名內(nèi),這樣就可以加快構(gòu)建速度了。
class PrivacyClassNode(private val nextVisitor: ClassVisitor) : ClassNode(Opcodes.ASM5) { override fun visitEnd() { super.visitEnd() PrivacyHelper.whiteList.let { val result = it.firstOrNull { whiteName -> name.contains(whiteName, true) } result }.apply { if (this == null) { // println("filter: $name") } } PrivacyHelper.whiteList.firstOrNull { name.contains(it, true) }?.apply { val iterator: Iterator<MethodNode> = methods.iterator() while (iterator.hasNext()) { val method = iterator.next() method.instructions?.iterator()?.forEach { if (it is MethodInsnNode) { it.isPrivacy()?.apply { println("privacy transform classNodeName: ${name@this}") it.opcode = code it.owner = owner it.name = name it.desc = desc } } } } } accept(nextVisitor) } } private fun MethodInsnNode.isPrivacy(): PrivacyAsmEntity? { val pair = PrivacyHelper.privacyList.firstOrNull { val first = it.first first.owner == owner && first.code == opcode && first.name == name && first.desc == desc } return pair?.second }
這部分比較簡(jiǎn)單,把邏輯抽象定義在類(lèi)ClassNode
內(nèi),然后在visitEnd
方法的時(shí)候調(diào)用我之前說(shuō)的accept(nextVisitor)
方法。
另外就是注冊(cè)邏輯了,和我前面介紹的內(nèi)容基本都是一樣的。
個(gè)人觀點(diǎn)
AsmClassVisitorFactory
相比較于之前的Transform
確實(shí)簡(jiǎn)化了非常非常多,我們不需要關(guān)心之前的增量更新等等邏輯,只要專(zhuān)注于asm api的操作就行了。
其次就是因?yàn)闇p少了io操作,所以其速度自然也就比之前有所提升。同時(shí)因?yàn)榛诘氖?code>Transform Action,所以整體性能還是非常ok的,那部分增量可以說(shuō)是更簡(jiǎn)單了。
另外我也和我的同事大佬交流過(guò)哦,復(fù)雜的這種類(lèi)似上篇文章介紹的,最好還是使用Gradle Task
的形式進(jìn)行修改。
以上就是Android開(kāi)發(fā)AsmClassVisitorFactory使用詳解的詳細(xì)內(nèi)容,更多關(guān)于Android開(kāi)發(fā)AsmClassVisitorFactory的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android 8.0中一些坑以及對(duì)應(yīng)的解決方法
這篇文章主要給大家介紹了關(guān)于Android 8.0中一些坑以及對(duì)應(yīng)的解決方法的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-09-09Android基于高德地圖完全自定義Marker的實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于Android基于高德地圖完全自定義Marker的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-07-07android創(chuàng)建數(shù)據(jù)庫(kù)(SQLite)保存圖片示例
這篇文章主要介紹了android創(chuàng)建數(shù)據(jù)庫(kù),保存圖片到數(shù)據(jù)庫(kù)再?gòu)臄?shù)據(jù)庫(kù)取圖片的方法,大家參考使用吧2014-01-01Android高仿微信對(duì)話(huà)列表滑動(dòng)刪除效果
這篇文章主要為大家詳細(xì)介紹了Android高仿微信對(duì)話(huà)列表滑動(dòng)刪除效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-08-08MotionLayout自定義開(kāi)關(guān)按鈕實(shí)例詳解
這篇文章主要為大家介紹了MotionLayout自定義開(kāi)關(guān)按鈕實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11Android編程設(shè)計(jì)模式之工廠(chǎng)方法模式實(shí)例詳解
這篇文章主要介紹了Android編程設(shè)計(jì)模式之工廠(chǎng)方法模式,結(jié)合實(shí)例形式詳細(xì)分析了Android工廠(chǎng)方法模式的概念、原理、使用方法及相關(guān)注意事項(xiàng),需要的朋友可以參考下2017-12-12Android適配利用webview加載后圖片顯示過(guò)大的問(wèn)題解決
這篇文章主要給大家介紹了關(guān)于Android適配利用webview加載后圖片顯示過(guò)大問(wèn)題的解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)各位Android開(kāi)發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07Android 中View.onDraw(Canvas canvas)的使用方法
這篇文章主要介紹了Android 中View.onDraw(Canvas canvas)的使用方法的相關(guān)資料,希望通過(guò)本文能幫助到大家,需要的朋友可以參考下2017-09-09