你们在本身写
library
的时候估计也遇到过这种困惑:一个library
中的某个类中有些方法或类只想给该library
中的类使用,并不想暴露出去,可是因为项目的包的层级关系,不得不把方法写为public
,致使暴露给了外界!!!java
当时这个问题确实困惑了我一段时间,总不能本身为了避免对外暴露,把 方法/类
写为 非public
吧?那我本身的 library
如何去调用呢?难道本身写反射?太蠢了吧。android
说时迟那时快,就想着本身搞个什么骚操做 hook 一下
library
生成的jar/aar
包吧。脑壳一热大腿一拍,妈的,写个插件吧!git
因而,这边就有了本篇文章的主角 Seeker(Github 传送门)。github
在开始以前,先在这里认个错,以前脑壳热的有点快,其实这个问题早就有了解决的方案,@RestrictTo,有兴趣的能够点进去了解一下。json
在解决问题以前,建议你们多去搜一下有没有已有的解决方案,我是立刻写完的时候才发现有 @RestrictTo
,吐血ing,中途有点难受,差点憋出内伤,最后仍是自我安慰,就当学习 gradle
了 TAT...api
在我看来要解决这个问题有两个方向:缓存
library
最后打包成 aar/jar
的源码,改变方法的 modifier
。build
过程直接报错,告诉用户这个方法不能够调用。因为第二种方案有点暴力,太过不近人情,既然不让我用,你为啥要暴露出来?暴露出来又报错是什么鬼?处于以上考虑,我选择了一条艰难的道路。app
有了大体方向后,开始准备撸代码,首先,须要先设计供用户使用的
Api
层,毕竟大佬们用的好才是真的好 ;)ide
我定义了一个 @Hide
注解,参数是一个 enum
类型,能够指定 modifier
,代码以下:性能
public enum Modifier {
/** The modifier {@code public} */ PUBLIC,
/** The modifier {@code protected} */ PROTECTED,
/** The modifier {@code private} */ PRIVATE,
/** The modifier with the default value */ DEFAULT;
}
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface Hide {
Modifier value() default Modifier.PRIVATE;
}
复制代码
添加 @Hide
注解到须要 hook 的方法上面,你也能够指定为不一样的 modifier
,最后在你的 library build.gradle
中 apply
一下个人插件便可!!!
Api 设计的很简洁,对业务也没有什么侵入性,由于咱们的
library
最后是须要打包成aar/jar
给其余人调用的,因此归根结底咱们须要hook
一下uploadArchives
task 的执行过程。
咱们给方法加上
@Hide
以后,须要找到这些方法,给后面 hook 字节码的时候用,要作到这一步还有什么比APT
更加合适的呢。
APT
的使用较为简单,没什么须要注意的地方,在此处省略,有兴趣的能够自行了解一下。
总之,咱们须要在这一步获取到全部含有
@Hide
的方法,而后保存一份到本地,这里我保存的是 json 文件。
这里咱们须要拆分为两步:
uploadArchives
task由于咱们最终但愿打包出来的 jar/aar
发生改变,而打包是经过 uploadArchives
task 作的,因此咱们须要对这个 task 进行分析并在某一步。
要分析这个 task ,咱们须要先知道这个 task 依赖了哪些 task
在 含有 uploadArchives task
的 build.gradle
中加入如下代码,打印下 uploadArchives
的依赖。
void printTaskDependency(Task task) {
task.getTaskDependencies().getDependencies(task).any() {
println(">>${it.path}")
printTaskDependency(it)
}
}
gradle.getTaskGraph().whenReady {
printTaskDependency project.tasks.findByName('uploadArchives')
}
复制代码
接着,随便运行一个 gradle 命令,为了方便,直接运行 ./gradlew clean
,查看打印的日志。
>>:mock-lib:sourcesJar
>>:mock-lib:bundleRelease
>>:mock-lib:mergeReleaseConsumerProguardFiles
>>:mock-lib:processReleaseManifest
>>:mock-lib:checkReleaseManifest
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:generateReleaseRFile
>>:mock-lib:packageReleaseResources
>>:mock-lib:generateReleaseResources
>>:mock-lib:compileReleaseRenderscript
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:generateReleaseResValues
>>:mock-lib:processReleaseManifest
>>:mock-lib:checkReleaseManifest
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:platformAttrExtractor
>>:mock-lib:packageReleaseResources
>>:mock-lib:generateReleaseResources
>>:mock-lib:compileReleaseRenderscript
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:generateReleaseResValues
>>:mock-lib:prepareLintJar
>>:mock-lib:extractReleaseAnnotations
>>:mock-lib:compileReleaseJavaWithJavac
>>:mock-lib:javaPreCompileRelease
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:generateReleaseRFile
>>:mock-lib:packageReleaseResources
>>:mock-lib:generateReleaseResources
>>:mock-lib:compileReleaseRenderscript
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:generateReleaseResValues
>>:mock-lib:processReleaseManifest
>>:mock-lib:checkReleaseManifest
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:platformAttrExtractor
>>:mock-lib:generateReleaseBuildConfig
>>:mock-lib:checkReleaseManifest
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:compileReleaseAidl
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:compileReleaseRenderscript
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:generateReleaseSources
>>:mock-lib:generateReleaseRFile
>>:mock-lib:packageReleaseResources
>>:mock-lib:generateReleaseResources
>>:mock-lib:compileReleaseRenderscript
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:generateReleaseResValues
>>:mock-lib:processReleaseManifest
>>:mock-lib:checkReleaseManifest
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:platformAttrExtractor
>>:mock-lib:prepareLintJar
>>:mock-lib:compileReleaseRenderscript
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:generateReleaseBuildConfig
>>:mock-lib:checkReleaseManifest
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:compileReleaseAidl
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:generateReleaseRFile
>>:mock-lib:packageReleaseResources
>>:mock-lib:generateReleaseResources
>>:mock-lib:compileReleaseRenderscript
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:generateReleaseResValues
>>:mock-lib:processReleaseManifest
>>:mock-lib:checkReleaseManifest
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:platformAttrExtractor
>>:mock-lib:generateReleaseBuildConfig
>>:mock-lib:checkReleaseManifest
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:compileReleaseAidl
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:compileReleaseRenderscript
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:transformClassesAndResourcesWithSyncLibJarsForRelease
>>:mock-lib:extractReleaseAnnotations
>>:mock-lib:compileReleaseJavaWithJavac
>>:mock-lib:javaPreCompileRelease
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:generateReleaseRFile
>>:mock-lib:packageReleaseResources
>>:mock-lib:generateReleaseResources
>>:mock-lib:compileReleaseRenderscript
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:generateReleaseResValues
>>:mock-lib:processReleaseManifest
>>:mock-lib:checkReleaseManifest
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:platformAttrExtractor
>>:mock-lib:generateReleaseBuildConfig
>>:mock-lib:checkReleaseManifest
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:compileReleaseAidl
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:compileReleaseRenderscript
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:generateReleaseSources
>>:mock-lib:generateReleaseRFile
>>:mock-lib:packageReleaseResources
>>:mock-lib:generateReleaseResources
>>:mock-lib:compileReleaseRenderscript
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:generateReleaseResValues
>>:mock-lib:processReleaseManifest
>>:mock-lib:checkReleaseManifest
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:platformAttrExtractor
>>:mock-lib:prepareLintJar
>>:mock-lib:compileReleaseRenderscript
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:generateReleaseBuildConfig
>>:mock-lib:checkReleaseManifest
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:compileReleaseAidl
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:generateReleaseRFile
>>:mock-lib:packageReleaseResources
>>:mock-lib:generateReleaseResources
>>:mock-lib:compileReleaseRenderscript
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:generateReleaseResValues
>>:mock-lib:processReleaseManifest
>>:mock-lib:checkReleaseManifest
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:platformAttrExtractor
>>:mock-lib:generateReleaseBuildConfig
>>:mock-lib:checkReleaseManifest
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:compileReleaseAidl
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:compileReleaseRenderscript
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:compileReleaseJavaWithJavac
>>:mock-lib:javaPreCompileRelease
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:generateReleaseRFile
>>:mock-lib:packageReleaseResources
>>:mock-lib:generateReleaseResources
>>:mock-lib:compileReleaseRenderscript
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:generateReleaseResValues
>>:mock-lib:processReleaseManifest
>>:mock-lib:checkReleaseManifest
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:platformAttrExtractor
>>:mock-lib:generateReleaseBuildConfig
>>:mock-lib:checkReleaseManifest
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:compileReleaseAidl
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:compileReleaseRenderscript
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:generateReleaseSources
>>:mock-lib:generateReleaseRFile
>>:mock-lib:packageReleaseResources
>>:mock-lib:generateReleaseResources
>>:mock-lib:compileReleaseRenderscript
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:generateReleaseResValues
>>:mock-lib:processReleaseManifest
>>:mock-lib:checkReleaseManifest
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:platformAttrExtractor
>>:mock-lib:prepareLintJar
>>:mock-lib:compileReleaseRenderscript
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:generateReleaseBuildConfig
>>:mock-lib:checkReleaseManifest
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:compileReleaseAidl
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:transformResourcesWithMergeJavaResForRelease
>>:mock-lib:processReleaseJavaRes
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:compileReleaseJavaWithJavac
>>:mock-lib:javaPreCompileRelease
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:generateReleaseRFile
>>:mock-lib:packageReleaseResources
>>:mock-lib:generateReleaseResources
>>:mock-lib:compileReleaseRenderscript
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:generateReleaseResValues
>>:mock-lib:processReleaseManifest
>>:mock-lib:checkReleaseManifest
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:platformAttrExtractor
>>:mock-lib:generateReleaseBuildConfig
>>:mock-lib:checkReleaseManifest
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:compileReleaseAidl
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:compileReleaseRenderscript
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:generateReleaseSources
>>:mock-lib:generateReleaseRFile
>>:mock-lib:packageReleaseResources
>>:mock-lib:generateReleaseResources
>>:mock-lib:compileReleaseRenderscript
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:generateReleaseResValues
>>:mock-lib:processReleaseManifest
>>:mock-lib:checkReleaseManifest
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:platformAttrExtractor
>>:mock-lib:prepareLintJar
>>:mock-lib:compileReleaseRenderscript
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:generateReleaseBuildConfig
>>:mock-lib:checkReleaseManifest
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:compileReleaseAidl
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:compileReleaseAidl
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:packageReleaseRenderscript
>>:mock-lib:transformNativeLibsWithSyncJniLibsForRelease
>>:mock-lib:transformNativeLibsWithMergeJniLibsForRelease
>>:mock-lib:mergeReleaseJniLibFolders
>>:mock-lib:generateReleaseAssets
>>:mock-lib:compileReleaseShaders
>>:mock-lib:mergeReleaseShaders
>>:mock-lib:compileReleaseNdk
>>:mock-lib:preReleaseBuild
>>:mock-lib:preBuild
>>:mock-lib:packageReleaseAssets
>>:mock-lib:generateReleaseAssets
>>:mock-lib:compileReleaseShaders
>>:mock-lib:mergeReleaseShaders
>>:mock-lib:compileReleaseShaders
>>:mock-lib:mergeReleaseShaders
复制代码
经过上面打印的信息能够看到依赖的
task
仍是蛮多的,咱们从前日后一步步排查。注:每一个人打印出来的内容可能不太同样,定义的 task 可能不一样。
sourceJar : 先看第一个 task sourceJar
,这个 task 是,我这边本身定义的,用于打包 java 源代码的 task
,由于是自定义的,因此能够忽略,直接看下一个 task 。
bundleRelease: 这个 task 是作什么的呢?大概从字面意思能够猜出和打包有关,咱们在 build.gradle
中输入 bundle
看看 IDE 的提示。
幸运!果真有相应的提示,直接看到了这个对应的是 AndroidZip
类,毋庸置疑,这个确定和打包有关。
再往前看看其余的 task: 放眼望去,基本上都是 package*/compile*/generate*/
之类开头的,看名字就能够才出来这些是作什么的,(手动滑稽脸),咱们应该是找到了须要 hook
的 task 了!!!
结果上面的分析和大胆的猜想,咱们须要 hook 一下
bundle*
这个task,这个 task 既然是打包用的,那么咱们须要在这个打包以前找到字节码存放的位置,而后去 hook 它!!!
自定义 gradle plugin 的过程和 gradle 的生命周期等等在此处不进行叙述了,有兴趣能够去网上自行了解。
咱们在自定义的插件的
afterEvaluate
中寻找bundle*
task:
mProject.afterEvaluate {
processVariant()
}
void processVariant() throws NotFoundException {
// variant 通常有 debug 和 release
mProject.android.libraryVariants.all { variant ->
process(variant)
}
}
void process(variant){
String taskPath = 'bundle' + mVariant.name.capitalize()
Task bundleTask = mProject.tasks.findByPath(taskPath)
if (bundleTask == null) {
throw new RuntimeException("Can not find task ${taskPath}!")
}
bundleTask.doFirst {
// do hook
}
}
复制代码
咱们在打包以前执行字节码的 hook 便可。
要 hook 字节码文件,咱们这边须要考虑如下几个事情。
字节码文件的存储路径在哪?
经过一系列查找(我没有找到如何在 gradle 中获取该路径的方法,有大佬知道麻烦告知),最终找到了相对路径:
/intermediates/packaged-classes/(release/debug)
如何改变字节码文件?
这边引入了一个第三方库 javassist 去改变字节码文件。
要如何改变?
经过以前
APT
期间生成的json
文件,遍历字节码文件,找到相应的方法后,改变modifier
为@Hide
对应的modifier
,而后删除@Hide
.
以上问题咱们都知道解决的方案了,剩下的就是实施过程了,javassist
的使用方式也在此再也不叙述了,有兴趣能够自行去看下,下面列出一些我在写这个插件过程当中遇到的一些问题.
问题1、javassist 寻找类的问题
在
javassist
中,咱们去寻找某一个类须要经过一个类ClassPool
来进行,再次以前咱们须要把须要用到的类的字节码路径
导入到ClassPool
中,在这里,遇到了第一个问题,在 gradle 项目中有的类是直接缓存在~/.gradle/
文件夹下的,有的类引用的是项目libs
目录下的,而且有的是.jar
包,有的是.aar
包,咱们如何去把这些类一一导入?
回答: 获取 gradle 的 dependencies
依赖,而后获取依赖的路径,而后加上本地的字节码文件,若是是 .jar
文件,则直接解压到某一个特定的临时文件夹中(task执行完毕后须要删除这些临时文件),若是是 .aar
文件,则先解压 .aar
后再解压其中的 classes.jar
文件.
// 获取 gradle dependencies 的过程
private List<Configuration> mCopyDependencies
private void copyDependencies(Configuration configuration) {
if (configuration == null) {
return
}
Configuration copyConf = null
try {
copyConf = mProject.configurations.getByName("${configuration.name}Copy")
} catch (Exception ignore) {
}
if (copyConf == null) {
copyConf = mProject.configurations.create("${configuration.name}Copy")
}
copyConf.visible = false
copyConf.extendsFrom configuration
mCopyDependencies.add(copyConf)
}
private void configureDependencies() {
mCopyDependencies = new ArrayList<>()
copyDependencies(mProject.configurations.getByName("implementation"))
copyDependencies(mProject.configurations.getByName("api"))
copyDependencies(mProject.configurations.getByName("compile"))
copyDependencies(mProject.configurations.getByName("compileOnly"))
copyDependencies(mProject.configurations.getByName("provided"))
}
复制代码
// 获取 dependencies 的本地路径
// 该方法执行在 afterEvaluate 中
private void resolveArtifacts() {
def set = new HashSet<>()
mCopyDependencies.forEach({
it.each {
set.add(it.path)
}
})
// ...
}
复制代码
在此期间,你能够获取/更改/删除你依赖的第三方库,根据需求不一样,能够作任何操做.
问题2、方法变为非public了,调用该方法的地方怎么办?
对于这个问题,没有很优雅的处理方式,我这边在
APT
过程当中生成了一个反射代理类,一个@Hide
对应一个反射的方法,而且会对反射进行缓存,保证了每一个方法的反射只会调用一次,保证性能.
library 的目录结构
其中的部分类
经过该插件生成的 .jar 的目录结构
_*RefDelegate 类
,这就是生成的反射代理类.
打出的 jar 包中的部分源码
调用 @Hide 的新旧类对比
从上面的图片能够看出,生成的 aar/jar
的字节码中,方法的 modifier 已经变为指定的 modifier 了,而且调用的地方也使用反射代理类去进行调用了.
对于此次开源来讲,整体是失败的,可是在写这个开源的过程当中,确实学到了不少东西,知道了如何去 hook
系统的 task
,如何去 hook
字节码等,我以为更重要的是解决问题的思路,有了问题,如何一步步的去解决它,想自定义一个 gradle
插件,应该从什么地方入手等.
最后,若是你们在看 Seeker
源码的过程当中遇到任何问题,能够直接提交 issue
,若是对于文章里面某些内容感兴趣的也能够直接评论哈,我会看状况抽时间写出相应的内容,若是遇到关于 gradle
的一些疑问或者遇到问题,我们也能够进行探讨~互相学习,互相伤害~
再次厚颜无耻的放上本身的 Seeker Github 传送门.