Replugin源码解析之replugin-plugin-gra
概述
该部分基础知识在Gradle学习-----Gradle自定义插件及Replugin源码解析之replugin-host-gradle涉及,不再重复累述
源码分析
ReClassTransform
入口相关类源码如下
public class ReClassTransform extends Transform {
private Project project
private def globalScope
/* 需要处理的 jar 包 */
def includeJars = [] as Set
def map = [:]
public ReClassTransform(Project p) {
this.project = p
AppPlugin appPlugin = project.plugins.getPlugin(AppPlugin)
// taskManager 在 2.1.3 中为 protected 访问类型的,在之后的版本为 private 访问类型的,
// 使用反射访问
def taskManager = BasePlugin.metaClass.getProperty(appPlugin, "taskManager")
this.globalScope = taskManager.globalScope;
}
@Override
String getName() {
return '___ReClass___'
}
@Override
void transform(Context context,
Collection<TransformInput> inputs,
Collection<TransformInput> referencedInputs,
TransformOutputProvider outputProvider,
boolean isIncremental) throws IOException, TransformException, InterruptedException {
welcome()
/* 读取用户配置 */
ReClassConfig config = project.extensions.getByName('repluginPluginConfig')
File rootLocation = null
try {
rootLocation = outputProvider.rootLocation
println ">>> rootLocation: throw----------->rootLocation=" + (rootLocation == null ? "null" :rootLocation)
} catch (Throwable e) {
//android gradle plugin 3.0.0+ 修改了私有变量,将其移动到了IntermediateFolderUtils中去
rootLocation = outputProvider.folderUtils.getRootFolder()
println ">>> rootLocation: throw----------->"
}
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
}
}
...//省略
}
查看入口transform
方法
2.1 project.extensions.getByName('repluginPluginConfig')
读取用户在replugin插件项目的build.gradle中配置的参数,在下面的代码中会用到,比如获取需要忽略的注入器ignoredInjectors
、需要忽略替换的ActivityignoredActivities
、自定义的代码注入器customInjectors
等。
2.2 includedInjectors
返回用户未忽略的注入器,代码如下
/**
* 返回用户未忽略的注入器的集合
*/
def includedInjectors(def cfg, String variantDir) {//product\debug
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
}
Injectors
为枚举,其中定义了5种IClassInjector
注入器的封装,被封装的分别对应操作对应组件及resource,源码如下
public enum Injectors {
LOADER_ACTIVITY_CHECK_INJECTOR('LoaderActivityInjector', new LoaderActivityInjector(), '替换 Activity 为 LoaderActivity'),//替换插件中的Activity的继承相关代码 为 replugin-plugin-library 中的XXPluginActivity父类
LOCAL_BROADCAST_INJECTOR('LocalBroadcastInjector', new LocalBroadcastInjector(), '替换 LocalBroadcast 调用'),//替换插件中的LocalBroadcastManager调用代码 为 插件库的调用代码。
PROVIDER_INJECTOR('ProviderInjector', new ProviderInjector(), '替换 Provider 调用'),//替换 插件中的 ContentResolver 调用代码 为 插件库的调用代码
PROVIDER_INJECTOR2('ProviderInjector2', new ProviderInjector2(), '替换 ContentProviderClient 调用'),//替换 插件中的 ContentProviderClient 调用代码 为 插件库的调用代码
GET_IDENTIFIER_INJECTOR('GetIdentifierInjector', new GetIdentifierInjector(), '替换 Resource.getIdentifier 调用') //替换 插件中的 Resource.getIdentifier 调用代码的参数 为 动态适配的参数
IClassInjector injector
String nickName
String desc
Injectors(String nickName, IClassInjector injector, String desc) {
this.injector = injector
this.nickName = nickName
this.desc = desc;
}
}
2.3 getInputTypes()
指明当前Trasfrom要处理的数据类型,可选类型包括CONTENT_CLASS
(代表要处理的数据是编译过的Java代码,而这些数据的容器可以是jar包也可以是文件夹),CONTENT_JARS
(包括编译过的Java代码和标准的Java资源),CONTENT_RESOURCES,CONTENT_NATIVE_LIBS
等。在replugin-plugin-gradle中是使用Transform来做代码插桩,所以选用CONTENT_CLASS
类型。
2.4doTransform
方法是执行reclass的关键,代码如下
* 执行 Transform
*/
def doTransform(Collection<TransformInput> inputs,
TransformOutputProvider outputProvider,
Object config,
def injectors) {
/* 初始化 ClassPool */
Object pool = initClassPool(inputs)
/* 进行注入操作 */
Util.newSection()
Injectors.values().each {
if (it.nickName in injectors) {
println ">>> Do: ${it.nickName}"
// 将 NickName 的第 0 个字符转换成小写,用作对应配置的名称
def configPre = Util.lowerCaseAtIndex(it.nickName, 0)
doInject(inputs, pool, it.injector, config.properties["${configPre}Config"])
} else {
println ">>> Skip: ${it.nickName}"
}
}
if (config.customInjectors != null) {
config.customInjectors.each {
doInject(inputs, pool, it)
}
}
/* 重打包 */
repackage()
/* 拷贝 class 和 jar 包 */
copyResult(inputs, outputProvider)
Util.newSection()
}
2.4.1 initClassPool
添加编译时需要引用的到类到 ClassPool。解压对应的jar的路径及名字,将解压后再重新打包的jar的路径及名字作为值,jar的名字作为key存放在map
中。includeJars
存放的是 android.jar的目录、其它各种引用到的jar进行解压后目录、及class所在目录。
/**
* 初始化 ClassPool
*/
def initClassPool(Collection<TransformInput> inputs) {
Util.newSection()
def pool = new ClassPool(true)
// 添加编译时需要引用的到类到 ClassPool, 同时记录要修改的 jar 到 includeJars
Util.getClassPaths(project, globalScope, inputs, includeJars, map).each {
println " $it"
pool.insertClassPath(it)
}
pool
}
Util.newSection()
为画一条直线长度为50个--,代码如下
def static newSection() {
50.times {
print '--'
}
println()
}
2.4.2
CtMethod:是一个class文件中的方法的抽象表示。一个CtMethod对象表示一个方法。(Javassit 库API)
CtClass:是一个class文件的抽象表示。一个CtClass(compile-time class)对象可以用来处理一个class文件。(Javassit 库API)
ClassPool:是一个CtClass对象的容器类。(Javassit 库API)
.class文件:.class文件是一种存储Java字节码的二进制文件,里面包含一个Java类或者接口。
遍历Injectors
枚举中值,与用户未忽略的注入器injectors
对比,如果未被忽略则执行doInject
否则打印跳过信息
2.4.3 doInject
即执行注入操作,分别对目录下的每个class文件执行handleDir
及对每个jar文件执行handleJar
源码如下
/**
* 执行注入操作
*/
def doInject(Collection<TransformInput> inputs, ClassPool pool,
IClassInjector injector, Object config) {
try {
inputs.each { TransformInput input ->
input.directoryInputs.each {
handleDir(pool, it, injector, config)
}
input.jarInputs.each {
handleJar(pool, it, injector, config)
}
}
} catch (Throwable t) {
println t.toString()
}
}
2.4.4
handleDir
即调用各个注入器的injectClass方法,handleJar
为通过jar名称获取map
中存放的对应解压目录,然后调用各个注入器的injectClass方法
/**
* 处理目录中的 class 文件
*/
def handleDir(ClassPool pool, DirectoryInput input, IClassInjector injector, Object config) {
println ">>> Handle Dir: ${input.file.absolutePath}"
injector.injectClass(pool, input.file.absolutePath, config)
}
/**
* 处理 jar
*/
def handleJar(ClassPool pool, JarInput input, IClassInjector injector, Object config) {
File jar = input.file
if (jar.absolutePath in includeJars) {
println ">>> Handle Jar: ${jar.absolutePath}"
String dirAfterUnzip = map.get(jar.getParent() + File.separatorChar + jar.getName()).replace('.jar', '')
injector.injectClass(pool, dirAfterUnzip, config)
}
}
2.4.5 各种注入器的injectClass
方法,针对class目录,遍历下面class文件进行处理,以LoaderActivityInjector
作为例子分析,源码如下
@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)
}
}
}
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()
}
}
2.4.6 handleActivity
代码如下,主要作用就是更改Activity的最顶层父类,对应关系为loaderActivityRules
数组。
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 &clsFilePath=-----------> $clsFilePath"
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()
}
}
}
2.4.7将解压修改后的jar重新打包,然后删除解压目录,只留jar。源码如下
/**
* 将解压的 class 文件重新打包,然后删除 class 文件
*/
def repackage() {
Util.newSection()
println '>>> Repackage...'
includeJars.each {
File jar = new File(it)
String JarAfterzip = map.get(jar.getParent() + File.separatorChar + jar.getName())
String dirAfterUnzip = JarAfterzip.replace('.jar', '')
// println ">>> 压缩目录 $dirAfterUnzip"
Util.zipDir(dirAfterUnzip, JarAfterzip)
// println ">>> 删除目录 $dirAfterUnzip"
FileUtils.deleteDirectory(new File(dirAfterUnzip))
}
}
2.4.8最后将处理好的class和jar赋值到输出目录,jar改名避免重名,交给下个task处理,至此Transform结束。
/**
* 拷贝处理结果
*/
def copyResult(def inputs, def outputs) {
// Util.newSection()
inputs.each { TransformInput input ->
input.directoryInputs.each { DirectoryInput dirInput ->
copyDir(outputs, dirInput)
}
input.jarInputs.each { JarInput jarInput ->
copyJar(outputs, jarInput)
}
}
}
/**
* 拷贝目录
*/
def copyDir(TransformOutputProvider output, DirectoryInput input) {
File dest = output.getContentLocation(input.name, input.contentTypes, input.scopes, Format.DIRECTORY)
FileUtils.copyDirectory(input.file, dest)
// println ">>> 拷贝目录 ${input.file.absolutePath} 到 ${dest.absolutePath}"
}
/**
* 拷贝 Jar
*/
def copyJar(TransformOutputProvider output, JarInput input) {
File jar = input.file
String jarPath = map.get(jar.absolutePath);
if (jarPath != null) {
jar = new File(jarPath)
}
String destName = input.name
def hexName = DigestUtils.md5Hex(jar.absolutePath)
if (destName.endsWith('.jar')) {
destName = destName.substring(0, destName.length() - 4)
}
File dest = output.getContentLocation(destName + '_' + hexName, input.contentTypes, input.scopes, Format.JAR)
FileUtils.copyFile(jar, dest)
/*
def path = jar.absolutePath
if (path in CommonData.includeJars) {
println ">>> 拷贝Jar ${path} 到 ${dest.absolutePath}"
}
*/
}