Android 插件化框架replugin-plugin-gradle源碼解析

1、插件入口 ReClassPluging.groovy


public class ReClassPlugin implements Plugin<Project> {

    @Override
    public void apply(Project project) {

			......
            def restartHostAppTask = null

            android.applicationVariants.all { variant ->
                PluginDebugger pluginDebugger = new PluginDebugger(project, config, variant)

                def variantData = variant.variantData
                def scope = variantData.scope

                def assembleTask = VariantCompat.getAssembleTask(variant)

                def installPluginTaskName = scope.getTaskName(AppConstant.TASK_INSTALL_PLUGIN, "")
                def installPluginTask = project.task(installPluginTaskName)

                installPluginTask.doLast {
                    pluginDebugger.startHostApp()
                    pluginDebugger.uninstall()
                    pluginDebugger.forceStopHostApp()
                    pluginDebugger.startHostApp()
                    pluginDebugger.install()
                }
                installPluginTask.group = AppConstant.TASKS_GROUP

			......

		}

	}
}

2、調試 Debugger


 installPluginTask.doLast {
                    pluginDebugger.startHostApp()
                    pluginDebugger.uninstall()
                    pluginDebugger.forceStopHostApp()
                    pluginDebugger.startHostApp()
                    pluginDebugger.install()
                }

PluginDebugger.groovy


  /**
     * 強制停止宿主app
     * @return 是否命令執行成功
     */
    public boolean forceStopHostApp() {

        if (isConfigNull()) {
            return false
        }

        String cmd = "${adbFile.absolutePath} shell am force-stop ${config.hostApplicationId}"
        if (0 != CmdUtil.syncExecute(cmd)) {
            return false
        }
        return true
    }

    /**
     * 啓動宿主app
     * @return 是否命令執行成功
     */
    public boolean startHostApp() {

        if (isConfigNull()) {
            return false
        }

        String cmd = "${adbFile.absolutePath} shell am start -n \"${config.hostApplicationId}/${config.hostAppLauncherActivity}\" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER"
        if (0 != CmdUtil.syncExecute(cmd)) {
            return false
        }
        return true
    }

3、動態編譯 ReClassPluging.groovy


public class ReClassPlugin implements Plugin<Project> {

    @Override
    public void apply(Project project) {

			.......
            def transform = new ReClassTransform(project)
            // 將 transform 註冊到 android
            android.registerTransform(transform)

	}
}

ReClassTransform.groovy


    @Override
    void transform(Context context,
                   Collection<TransformInput> inputs,
                   Collection<TransformInput> referencedInputs,
                   TransformOutputProvider outputProvider,
                   boolean isIncremental) throws IOException, TransformException, InterruptedException {

        welcome()

        /* 讀取用戶配置 */
        def config = project.extensions.getByName('repluginPluginConfig')


        File rootLocation = null
        try {
            rootLocation = outputProvider.rootLocation
        } catch (Throwable e) {
            //android gradle plugin 3.0.0+ 修改了私有變量,將其移動到了IntermediateFolderUtils中去
            rootLocation = outputProvider.folderUtils.getRootFolder()
        }
        if (rootLocation == null) {
            throw new GradleException("can't get transform root location")
        }
        println ">>> rootLocation: ${rootLocation}"
        // Compatible with path separators for window and Linux, and fit split param based on 'Pattern.quote'
        def variantDir = rootLocation.absolutePath.split(getName() + Pattern.quote(File.separator))[1]
        println ">>> variantDir: ${variantDir}"

        CommonData.appModule = config.appModule
        CommonData.ignoredActivities = config.ignoredActivities

        def injectors = includedInjectors(config, variantDir)
        if (injectors.isEmpty()) {
            copyResult(inputs, outputProvider) // 跳過 reclass
        } else {
            doTransform(inputs, outputProvider, config, injectors) // 執行 reclass
        }
    }

    /**
     * 返回用戶未忽略的注入器的集合
     */
    def includedInjectors(def cfg, String variantDir) {
        def injectors = []
        Injectors.values().each {
            //設置project
            it.injector.setProject(project)
            //設置variant關鍵dir
            it.injector.setVariantDir(variantDir)
            if (!(it.nickName in cfg.ignoredInjectors)) {
                injectors << it.nickName
            }
        }
        injectors
    }

4、LoaderActivityInjector,主要用來替換插件Activity類中的Activity父類爲XXPluginActivity父類
ProviderInjector,主要用來替換 插件中的 ContentResolver相關的方法調用爲插件庫的PluginProviderClient中的對應方法調用。


public class LoaderActivityInjector extends BaseInjector {

    def private static LOADER_PROP_FILE = 'loader_activities.properties'

    /* LoaderActivity 替換規則 */
    def private static loaderActivityRules = [
            'android.app.Activity'                    : 'com.qihoo360.replugin.loader.a.PluginActivity',
            'android.app.TabActivity'                 : 'com.qihoo360.replugin.loader.a.PluginTabActivity',
            'android.app.ListActivity'                : 'com.qihoo360.replugin.loader.a.PluginListActivity',
            'android.app.ActivityGroup'               : 'com.qihoo360.replugin.loader.a.PluginActivityGroup',
            'android.support.v4.app.FragmentActivity' : 'com.qihoo360.replugin.loader.a.PluginFragmentActivity',
            'android.support.v7.app.AppCompatActivity': 'com.qihoo360.replugin.loader.a.PluginAppCompatActivity',
            'android.preference.PreferenceActivity'   : 'com.qihoo360.replugin.loader.a.PluginPreferenceActivity',
            'android.app.ExpandableListActivity'      : 'com.qihoo360.replugin.loader.a.PluginExpandableListActivity'
    ]

    @Override
    def injectClass(ClassPool pool, String dir, Map config) {
        init()

        /* 遍歷程序中聲明的所有 Activity */
        //每次都new一下,否則多個variant一起構建時只會獲取到首個manifest
        new ManifestAPI().getActivities(project, variantDir).each {
            // 處理沒有被忽略的 Activity
            if (!(it in CommonData.ignoredActivities)) {
                handleActivity(pool, it, dir)
            }
        }
    }

    /**
     * 處理 Activity
     *
     * @param pool
     * @param activity Activity 名稱
     * @param classesDir class 文件目錄
     */
    private def handleActivity(ClassPool pool, String activity, String classesDir) {
        def clsFilePath = classesDir + File.separatorChar + activity.replaceAll('\\.', '/') + '.class'
        if (!new File(clsFilePath).exists()) {
            return
        }

        println ">>> Handle $activity"

        def stream, ctCls
        try {
            stream = new FileInputStream(clsFilePath)
            ctCls = pool.makeClass(stream);
/*
             // 打印當前 Activity 的所有父類
            CtClass tmpSuper = ctCls.superclass
            while (tmpSuper != null) {
                println(tmpSuper.name)
                tmpSuper = tmpSuper.superclass
            }
*/
            // ctCls 之前的父類
            def originSuperCls = ctCls.superclass

            /* 從當前 Activity 往上回溯,直到找到需要替換的 Activity */
            def superCls = originSuperCls
            while (superCls != null && !(superCls.name in loaderActivityRules.keySet())) {
                // println ">>> 向上查找 $superCls.name"
                ctCls = superCls
                superCls = ctCls.superclass
            }

            // 如果 ctCls 已經是 LoaderActivity,則不修改
            if (ctCls.name in loaderActivityRules.values()) {
                // println "    跳過 ${ctCls.getName()}"
                return
            }

            /* 找到需要替換的 Activity, 修改 Activity 的父類爲 LoaderActivity */
            if (superCls != null) {
                def targetSuperClsName = loaderActivityRules.get(superCls.name)
                // println "    ${ctCls.getName()} 的父類 $superCls.name 需要替換爲 ${targetSuperClsName}"
                CtClass targetSuperCls = pool.get(targetSuperClsName)

                if (ctCls.isFrozen()) {
                    ctCls.defrost()
                }
                ctCls.setSuperclass(targetSuperCls)

                // 修改聲明的父類後,還需要方法中所有的 super 調用。
                ctCls.getDeclaredMethods().each { outerMethod ->
                    outerMethod.instrument(new ExprEditor() {
                        @Override
                        void edit(MethodCall call) throws CannotCompileException {
                            if (call.isSuper()) {
                                if (call.getMethod().getReturnType().getName() == 'void') {
                                    call.replace('{super.' + call.getMethodName() + '($$);}')
                                } else {
                                    call.replace('{$_ = super.' + call.getMethodName() + '($$);}')
                                }
                            }
                        }
                    })
                }

                ctCls.writeFile(CommonData.getClassPath(ctCls.name))
                println "    Replace ${ctCls.name}'s SuperClass ${superCls.name} to ${targetSuperCls.name}"
            }

        } catch (Throwable t) {
            println "    [Warning] --> ${t.toString()}"
        } finally {
            if (ctCls != null) {
                ctCls.detach()
            }
            if (stream != null) {
                stream.close()
            }
        }
    }

    def private init() {
        /* 延遲初始化 loaderActivityRules */
        // todo 從配置中讀取,而不是寫死在代碼中
        if (loaderActivityRules == null) {
            def buildSrcPath = project.project(':buildsrc').projectDir.absolutePath
            def loaderConfigPath = String.join(File.separator, buildSrcPath, 'res', LOADER_PROP_FILE)

            loaderActivityRules = new Properties()
            new File(loaderConfigPath).withInputStream {
                loaderActivityRules.load(it)
            }

            println '\n>>> Activity Rules:'
            loaderActivityRules.each {
                println it
            }
            println()
        }
    }
}

public class ProviderExprEditor extends ExprEditor {

    static def PROVIDER_CLASS = 'com.qihoo360.replugin.loader.p.PluginProviderClient'

    public def filePath

    @Override
    void edit(MethodCall m) throws CannotCompileException {
        final String clsName = m.getClassName()
        final String methodName = m.getMethodName()
        if (!clsName.equalsIgnoreCase('android.content.ContentResolver')) {
            return
        }
        if (!(methodName in ProviderInjector.includeMethodCall)) { // println "跳過$methodName"
            return
        }
        try {
            replaceStatement(m, methodName, m.lineNumber)
        } catch (Exception e) { //確保不影響其他 MethodCall
            println "    [Warning] --> ProviderExprEditor : ${e.toString()}"
        }
    }

    def private replaceStatement(MethodCall methodCall, String method, def line) {
        if (methodCall.getMethodName() == 'registerContentObserver' || methodCall.getMethodName() == 'notifyChange') {
            methodCall.replace('{' + PROVIDER_CLASS + '.' + method + '(com.qihoo360.replugin.RePlugin.getPluginContext(), $$);}')
        } else {
            methodCall.replace('{$_ = ' + PROVIDER_CLASS + '.' + method + '(com.qihoo360.replugin.RePlugin.getPluginContext(), $$);}')
        }
        println ">>> Replace: ${filePath} Provider.${method}():${line}"
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章