Replugin源码解析之replugin-plugin-gradle ---1 添加生成调试相关task

概述

1.1gradle编译流程及replugin-plugin-gradle插件的切入点
Replugin源码解析之replugin-plugin-gradle ---1 添加生成调试相关task_第1张图片
图2.png
1.2项目结构预览
Replugin源码解析之replugin-plugin-gradle ---1 添加生成调试相关task_第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 {

    @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-gradle ---1 添加生成调试相关task)