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