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}"
}
}