<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
之前就和大家介紹過AGP(Android Gradle Plugin) 7.0.0
版本之後Transform
已經過期即將廢棄的事情。而且也簡單的介紹了替換的方式是Transform Action
,經過我這一陣子的學習和調研,發現只能說答對了一半吧。下面介紹個新東西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.
當前官方推薦使用的應該是這個類,這個類的底層實現就是基於gradle
原生的Transform Action
,這次的學習過程其實走了一點點彎路,一開始嘗試的是Transform Action
,但是貌似彎彎繞繞的,最後也沒有成功,而且Transform Action
的輸入產物都是單一檔案,修改也是針對單一檔案的,所以貌似也不完全是一個很好的替換方案,之前文章介紹的那種複雜的asm操作則無法負荷了。
AsmClassVisitorFactory
根據官方說法,編譯速度會有提升,大概18%左右,這個下面我們會在使用階段對其進行介紹的。
我們先從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 }
簡單的分析下這個介面,我們要做的就是在createClassVisitor
這個方法中返回一個ClassVisitor
,正常我們在構造ClassVisitor
範例的時候是需要傳入下一個ClassVisitor
範例的,所以我們之後在new的時候傳入nextClassVisitor就行了。
另外就是isInstrumentable
,這個方法是判斷當前類是否要進行掃描,因為如果所有類都要通過ClassVisitor進行掃描還是太耗時了,我們可以通過這個方法過濾掉很多我們不需要掃描的類。
@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,所以其中包含的內容相對來說比較少,但是應該也勉強夠用了。這部分大家簡單看看就行了,就不多做介紹了呢。
AGP版本升級之後,應該是為了區分新舊版的Extension
,所以在AppExtension
的基礎上,新增了一個AndroidComponentsExtension
出來。
我們的transformClassesWith
就需要註冊在這個上面。這個需要考慮到變種,和之前的Transform
還是有比較大的區別的,這樣我們就可以基於不同的變種增加對應的適配工作了。
val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java) androidComponents.onVariants { variant -> variant.transformClassesWith(PrivacyClassVisitorFactory::class.java, InstrumentationScope.ALL) {} variant.setAsmFramesComputationMode(FramesComputationMode.COPY_FRAMES) }
這次還是在之前的敏感許可權api替換的位元組碼替換工具的基礎上進行測試開發。
看看我們正常是如何寫一個簡單的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();
首先我們會構造好一個空的ClassWriter
,接著會構造一個ClassVisitor
範例,然後傳入這個ClassWriter
。然後我們構造一個ClassReader
範例,然後將byte陣列傳入,之後呼叫classReader.accept方法,之後我們就能在visitor中逐個存取資料了。
那麼其實我們的類資訊,方法啥的都是通過ClassReader讀入的,然後由當前的ClassVisitor
存取完之後交給我們最後一個ClassWriter
。
其中ClassWriter
也是一個ClassVisitor
物件,他複雜重新將修改過的類轉化成byte資料。可以看得出來ClassVisitor
就有一個非常簡單的連結串列結構,之後逐層向下存取。
介紹完了這個哦,我們做個大膽的假設,如果我們這個ClassVisitor
連結串列前插入幾個不同的ClassVisitor
,那麼我們是不是就可以讓asm修改逐個生效,然後也不需要多餘的io操作了呢。這就是新的asm api 的設計思路了,也是我們這邊大佬的位元組碼框架大佬的設計。另外bytex內的設計思路也是如此。
tips ClassNode 因為是先生成的語法樹,所以和一般的ClassVisitor有點小區別,需要在visitEnd方法內呼叫accept(next)
接下來我們上實戰咯。我將之前的程式碼套用到這次的邏輯上來。
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,其實我可以將掃描規則限定在特定包名內,這樣就可以加快構建速度了。
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 }
這部分比較簡單,把邏輯抽象定義在類ClassNode
內,然後在visitEnd
方法的時候呼叫我之前說的accept(nextVisitor)
方法。
另外就是註冊邏輯了,和我前面介紹的內容基本都是一樣的。
AsmClassVisitorFactory
相比較於之前的Transform
確實簡化了非常非常多,我們不需要關心之前的增量更新等等邏輯,只要專注於asm api的操作就行了。
其次就是因為減少了io操作,所以其速度自然也就比之前有所提升。同時因為基於的是Transform Action
,所以整體效能還是非常ok的,那部分增量可以說是更簡單了。
另外我也和我的同事大佬交流過哦,複雜的這種類似上篇文章介紹的,最好還是使用Gradle Task
的形式進行修改。
以上就是Android開發AsmClassVisitorFactory使用詳解的詳細內容,更多關於Android開發AsmClassVisitorFactory的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45