你們在本身寫
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 傳送門.