美文网首页
Replugin源码解析之replugin-plugin-gra

Replugin源码解析之replugin-plugin-gra

作者: PeytonWu | 来源:发表于2017-11-03 15:56 被阅读0次

    概述

    1.1gradle编译流程及replugin-plugin-gradle插件的切入点
    图2.png
    1.2项目结构预览
    图1.png
    1.3代码结构
    \qihoo\replugin\replugin-plugin-gradle\src
    └─main
        ├─groovy
        │  └─com
        │      └─qihoo360
        │          └─replugin
        │              └─gradle
        │                  └─plugin
        │                      │  AppConstant.groovy                      # 程序常量定义区
        │                      │  ReClassPlugin.groovy                    # 插件动态编译方案入口
        │                      │  
        │                      ├─debugger
        │                      │      PluginDebugger.groovy               # 用于插件调试的gradle task实现
        │                      │      
        │                      ├─injector
        │                      │  │  BaseInjector.groovy                  # 注入器基类
        │                      │  │  IClassInjector.groovy                # 注入器接口类
        │                      │  │  Injectors.groovy                     # 注入器枚举类,定义了全部注入器
        │                      │  │  
        │                      │  ├─identifier
        │                      │  │      GetIdentifierExprEditor.groovy   # javassist 允许修改方法里的某个表达式,此类为替换 getIdentifier 方法中表达式的实现类
        │                      │  │      GetIdentifierInjector.groovy     # GetIdentifier 方法注入器
        │                      │  │      
        │                      │  ├─loaderactivity
        │                      │  │      LoaderActivityInjector.groovy    # Activity代码注入器
        │                      │  │      
        │                      │  ├─localbroadcast
        │                      │  │      LocalBroadcastExprEditor.groovy  # 替换几个广播相关方法表达式的实现类
        │                      │  │      LocalBroadcastInjector.groovy    # 广播代码注入器
        │                      │  │      
        │                      │  └─provider
        │                      │          ProviderExprEditor.groovy       # 替换ContentResolver类的几个方法表达式
        │                      │          ProviderExprEditor2.groovy      # 替换ContentProviderClient类的几个方法表达式
        │                      │          ProviderInjector.groovy         # Provider之ContentResolver代码注入器
        │                      │          ProviderInjector2.groovy        # Provider之ContentProviderClient代码注入器
        │                      │          
        │                      ├─inner
        │                      │      ClassFileVisitor.groovy             # 类文件遍历类
        │                      │      CommonData.groovy                   # 实体类
        │                      │      ReClassTransform.groovy             # 核心类,基于 transform api 实现动态修改class文件的总调度入口
        │                      │      Util.groovy                         # 工具类
        │                      │      
        │                      ├─manifest
        │                      │      IManifest.groovy                    # 接口类
        │                      │      ManifestAPI.groovy                  # 操作Manifest的API类
        │                      │      ManifestReader.groovy               # Manifest读取工具类
        │                      │      
        │                      └─util
        │                              CmdUtil.groovy                     # 命令行工具类
        │                              
        └─resources
            └─META-INF
                └─gradle-plugins
                        replugin-plugin-gradle.properties                 # 指定 gradle 插件实现类
    
    

    源码分析

    2.1入口类ReClassPlugin.groovy源码如下
    /**
     * @author RePlugin Team
     */
    public class ReClassPlugin implements Plugin<Project> {
    
        @Override
        public void apply(Project project) {
    
            println "${AppConstant.TAG} Welcome to replugin world ! "
    
            /* Extensions */
            project.extensions.create(AppConstant.USER_CONFIG, ReClassConfig)
    
            def isApp = project.plugins.hasPlugin(AppPlugin)
            if (isApp) {
    
                def config = project.extensions.getByName(AppConstant.USER_CONFIG)
    
                def android = project.extensions.getByType(AppExtension)
    
                def forceStopHostAppTask = null
                def startHostAppTask = null
                def restartHostAppTask = null
    
                android.applicationVariants.all { variant ->
                    PluginDebugger pluginDebugger = new PluginDebugger(project, config, variant)
    
                    def variantData = variant.variantData
                    def scope = variantData.scope
    
                    def assembleTask = variant.getAssemble()
    
                    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
    
    
                    def uninstallPluginTaskName = scope.getTaskName(AppConstant.TASK_UNINSTALL_PLUGIN, "")
                    def uninstallPluginTask = project.task(uninstallPluginTaskName)
    
                    uninstallPluginTask.doLast {
                        //generate json
                        pluginDebugger.uninstall()
                    }
                    uninstallPluginTask.group = AppConstant.TASKS_GROUP
    
    
                    if (null == forceStopHostAppTask) {
                        forceStopHostAppTask = project.task(AppConstant.TASK_FORCE_STOP_HOST_APP)
                        forceStopHostAppTask.doLast {
                            //generate json
                            pluginDebugger.forceStopHostApp()
                        }
                        forceStopHostAppTask.group = AppConstant.TASKS_GROUP
                    }
    
                    if (null == startHostAppTask) {
                        startHostAppTask = project.task(AppConstant.TASK_START_HOST_APP)
                        startHostAppTask.doLast {
                            //generate json
                            pluginDebugger.startHostApp()
                        }
                        startHostAppTask.group = AppConstant.TASKS_GROUP
                    }
    
                    if (null == restartHostAppTask) {
                        restartHostAppTask = project.task(AppConstant.TASK_RESTART_HOST_APP)
                        restartHostAppTask.doLast {
                            //generate json
                            pluginDebugger.startHostApp()
                        }
                        restartHostAppTask.group = AppConstant.TASKS_GROUP
                        restartHostAppTask.dependsOn(forceStopHostAppTask)
                    }
    
    
                    if (assembleTask) {
                        installPluginTask.dependsOn assembleTask
                    }
    
                    def runPluginTaskName = scope.getTaskName(AppConstant.TASK_RUN_PLUGIN, "")
                    def runPluginTask = project.task(runPluginTaskName)
                    runPluginTask.doLast {
                        pluginDebugger.run()
                    }
                    runPluginTask.group = AppConstant.TASKS_GROUP
    
                    def installAndRunPluginTaskName = scope.getTaskName(AppConstant.TASK_INSTALL_AND_RUN_PLUGIN, "")
                    def installAndRunPluginTask = project.task(installAndRunPluginTaskName)
                    installAndRunPluginTask.doLast {
                        pluginDebugger.run()
                    }
                    installAndRunPluginTask.group = AppConstant.TASKS_GROUP
                    installAndRunPluginTask.dependsOn installPluginTask
                }
    
                CommonData.appPackage = android.defaultConfig.applicationId
    
                println ">>> APP_PACKAGE " + CommonData.appPackage
    
                def transform = new ReClassTransform(project)
                // 将 transform 注册到 android
                android.registerTransform(transform)
            }
        }
    }
    

    2.1.1 首先向Plugin传递参数,通过project.extensions.create(AppConstant.USER_CONFIG, ReClassConfig),将ReClassConfig类的常量配置信息赋值给AppConstant.USER_CONFIG,即工程中配置的repluginPluginConfig{...}信息,然后在接下来的代码中获取用户配置的相关信息赋值给config对象,该对象即存有用户配置的所有信息,实际上该对象即为ReClassConfig类型,源码如下

    class ReClassConfig {
    
        /** 编译的 App Module 的名称 */
        def appModule = ':app'
    
        /** 用户声明要忽略的注入器 */
        def ignoredInjectors = []
    
        /** 执行 LoaderActivity 替换时,用户声明不需要替换的 Activity */
        def ignoredActivities = []
    
        /** 自定义的注入器 */
        def customInjectors = []
    
        /** 插件名字,默认null */
        def pluginName = null
    
        /** 手机存储目录,默认"/sdcard/" */
        def phoneStorageDir = "/sdcard/"
    
        /** 宿主包名,默认null */
        def hostApplicationId = null
    
        /** 宿主launcherActivity,默认null */
        def hostAppLauncherActivity = null
    }
    

    2.1.2 判断project中是否含有AppPlugin类型插件,即我们在项目中是应用了该类型插件的apply plugin: 'com.android.application,然后获取其配置赋值给android对象,该对象即为AppExtension类型
    2.1.3 遍历android extension的Application variants 组合。android gradle 插件,会对最终的包以多个维度进行组合。ApplicationVariant的组合 = {ProductFlavor} x {BuildType} 种组合.
    2.1.4new PluginDebugger(project, config, variant),初始化PluginDebugger类实例,主要配置了最终生成的插件应用的文件路径,以及adb文件的路径,是为了后续基于adb命令做push apk到SD卡上做准备。
    该类构造函数如下,最点在于后2行

     public PluginDebugger(Project project, def config, def variant) {
            this.project = project
            this.config = config
            this.variant = variant
            ApplicationVariantData variantData = this.variant.variantData
            VariantScope scope = variantData.scope
            GlobalScope globalScope = scope.globalScope
            GradleVariantConfiguration variantConfiguration = variantData.variantConfiguration
            String archivesBaseName = globalScope.getArchivesBaseName();
            String apkBaseName = archivesBaseName + "-" + variantConfiguration.getBaseName()
            File apkDir = new File(globalScope.getBuildDir(), "outputs/apk")
            String unsigned = (variantConfiguration.getSigningConfig() == null
                    ? "-unsigned.apk"
                    : ".apk");
            String apkName = apkBaseName + unsigned
    
            apkFile = new File(apkDir, apkName) //获取生成的apk路径,即output\apks\下的各个apk
    
            adbFile = globalScope.androidBuilder.sdkInfo.adb; //获取adb路径,即adb命令可执行路径
         
        }
    

    2.1.5def assembleTask = variant.getAssemble(),获取assemble task(即打包apk的task),后续的installPluginTask任务需要依赖此task
    2.1.6生成installPluginTask 的gradle task 名字,并调用project的task()方法创建此Task。然后指定此task的任务内容:

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

    这些startHostAppuninstallforceStopHostAppstartHostAppinstall内容所做的事基本都可以从名字,看得出来,大概流程即为 检验配置健壮性-->生成对应adb命令并执行
    startHostApp为例子,即是打开宿主app,打开相关源代码如下

        /**
         * 启动宿主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)) {//生成cmd命令,并调用syncExecute方法执行
                return false
            }
            return true
        }
    
       /**
         * 检查用户配置项是否为空
         * @param config
         * @return
         */
        private boolean isConfigNull() {
    
            //检查adb环境
            if (null == adbFile || !adbFile.exists()) {
                System.err.println "${AppConstant.TAG} Could not find the adb file !!!"
                return true
            }
    
            if (null == config) {
                System.err.println "${AppConstant.TAG} the config object can not be null!!!"
                System.err.println "${AppConstant.CONFIG_EXAMPLE}"
                return true
            }
    
            if (null == config.hostApplicationId) {
                System.err.println "${AppConstant.TAG} the config hostApplicationId can not be null!!!"
                System.err.println "${AppConstant.CONFIG_EXAMPLE}"
                return true
            }
    
            if (null == config.hostAppLauncherActivity) {
                System.err.println "${AppConstant.TAG} the config hostAppLauncherActivity can not be null!!!"
                System.err.println "${AppConstant.CONFIG_EXAMPLE}"
                return true
            }
    
            return false
        }
    
    /**
         * 同步阻塞执行命令
         * @param cmd 命令
         * @return 命令执行完毕返回码
         */
        public static int syncExecute(String cmd){
    
            int cmdReturnCode
    
            try {
                println "${AppConstant.TAG} \$ ${cmd}"
    
                Process process = cmd.execute()
                process.inputStream.eachLine {
                    println "${AppConstant.TAG} - ${it}"
                }
                process.waitFor()
    
                cmdReturnCode = process.exitValue()
    
            }catch (Exception e){
                System.err.println "${AppConstant.TAG} the cmd run error !!!"
                System.err.println "${AppConstant.TAG} ${e}"
                return -1
            }
    
            return cmdReturnCode
        }
    

    NOTE:由于调用了dolast,该任务只是被太添加到工程中并会立马执行,只有用户通过gradle命令调用时才会执行
    2.1.7重复类似2.1.6的工作添加uninstallPluginTaskrestartHostAppTaskrunPluginTask任务

    看到这里,我们该插播一下调试方案的整体原理了:
    2.1.7.1 replugin-host-lib 的DebuggerReceivers类中,注册了一系列用于快速调试的广播,而replugin-host-lib是会内置在宿主应用中的。

    2.1.7.2 replugin-plugin-gradle 中创建了一系列gradle task,用于启动停止重启宿主应用,安装卸载运行插件应用。这些gradle task都是被动型task,需要通过命令行主动的运行这些task。

    2.1.7.3 打开命令行终端,执行replugin插件项目的某个gradle task,以实现快速调试功能。比如:gradlew.bat rpInstallPluginDebug,最终就会将宿主和插件运行起来。
    这些gradle task被手动执行后,task会执行一系列任务,比如通过adb push 插件到sdcard,或通过am命令发送广播,启动activity等。当发送一系列步骤1中注册的广播后,宿主应用收到广播后会执行对应的操作,比如启动插件的activity等。
    2.1.8 获取配置的applictaionId并打印

       CommonData.appPackage = android.defaultConfig.applicationId
       println ">>> APP_PACKAGE " + CommonData.appPackage
    

    2.1.9
    生成ReClassTransform并注册到工程中,具体如何更改生成class 文件及相关工作,将在下一篇中分析

    相关文章

      网友评论

          本文标题:Replugin源码解析之replugin-plugin-gra

          本文链接:https://www.haomeiwen.com/subject/udetmxtx.html