溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

Android開(kāi)發(fā)中AsmClassVisitorFactory如何使用

發(fā)布時(shí)間:2022-06-22 09:30:59 來(lái)源:億速云 閱讀:287 作者:iii 欄目:開(kāi)發(fā)技術(shù)

這篇文章主要講解了“Android開(kāi)發(fā)中AsmClassVisitorFactory如何使用”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“Android開(kāi)發(fā)中AsmClassVisitorFactory如何使用”吧!

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)。

demo地址

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)方法。

感謝各位的閱讀,以上就是“Android開(kāi)發(fā)中AsmClassVisitorFactory如何使用”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)Android開(kāi)發(fā)中AsmClassVisitorFactory如何使用這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI