概述
1.1gradle编译流程及replugin-plugin-gradle插件的切入点
1.2项目结构预览
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()
}
这些startHostApp
、uninstall
、 forceStopHostApp
、startHostApp
、install
内容所做的事基本都可以从名字,看得出来,大概流程即为 检验配置健壮性-->生成对应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的工作添加uninstallPluginTask
、restartHostAppTask
、runPluginTask
任务
看到这里,我们该插播一下调试方案的整体原理了:
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 文件及相关工作,将在下一篇中分析