<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何簡單方便地Hook Gradle插件?

          共 6241字,需瀏覽 13分鐘

           ·

          2022-05-13 22:55

          前言

          很多時(shí)候系統(tǒng)處于安全考慮,將很多東西對(duì)外隱藏,而有時(shí)我們偏偏又不得不去使用這些隱藏的東西。甚至,我們希望向系統(tǒng)中注入一些自己的代碼,修改原有代碼的邏輯,以提高程序的靈活性,這時(shí)候就需要用到代碼Hook
          Java或者Kotlin代碼中,代碼Hook有多種方案,比如反射,動(dòng)態(tài)代理,或者通過修改字節(jié)碼來實(shí)現(xiàn)HOOK,那么如果我們想要修改Gradle插件的代碼,該怎么實(shí)現(xiàn)呢?

          簡單使用

          我們首先來看一個(gè)簡單的例子,大家肯定都用過com.android.application插件,如果我們想要在這個(gè)插件中添加一些代碼,可以怎么操作呢?修改方式非常簡單

          1. 項(xiàng)目中添加buildSrc模塊
          2. buildSrc中添加com.android.tools.build:gradle:7.0.2依賴
          3. buildSrc中添加與插件中同名的AppPlugin即可,如下所示
          package com.android.build.gradle

          import org.gradle.api.Project

          class AppPluginBasePlugin() {
              override fun apply(project: Project) {
                  super.apply(project)
                  println("hook AppPlugin demo")
                  project.apply(INTERNAL_PLUGIN_ID)
              }
          }

          private val INTERNAL_PLUGIN_ID = mapOf("plugin" to "com.android.internal.application")

          然后我們?cè)偻揭幌马?xiàng)目,就可以發(fā)現(xiàn)hook AppPlugin demo的日志可以打印出來了,就這樣在AppPlugin中添加了我們想要的邏輯
          在了解怎么使用了之后,我們?cè)賮矸治鱿聻槭裁催@樣做就可以覆蓋插件中的AppPlugin,我們首先需要了解下Gradle插件到底是怎么運(yùn)行起來的

          Gradle運(yùn)行的入口是什么?

          我們都知道,Java運(yùn)行需要一個(gè)main函數(shù),Groovy作為一個(gè)JVM語言,相信也是一樣的,那么我們是怎么調(diào)用到Groovymain函數(shù)的呢?
          在我們運(yùn)行Gradle的時(shí)候,都是通過gradlew來運(yùn)行的,gradlew其實(shí)是對(duì)gradle的一個(gè)包裝,本質(zhì)上就是一個(gè)shell腳本

          exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

          可以看出,其實(shí)就是調(diào)用了GradleWrapperMain并傳遞給它一系列參數(shù),那我們?cè)賮砜聪?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">GradleWrapperMain

          public class GradleWrapperMain {
              ......
              //執(zhí)行 gradlew 腳本命令時(shí)觸發(fā)調(diào)用的入口。
              public static void main(String[] args) throws Exception {       
                  ......
                  //調(diào)用BootstrapMainStarter
                  wrapperExecutor.execute(
                          args,
                          new Install(logger, new Download(logger, "gradlew", wrapperVersion()), new PathAssembler(gradleUserHome)),
                          new BootstrapMainStarter());
              }
          }

          public class BootstrapMainStarter {
              public void start(String[] args, File gradleHome) throws Exception {
                  //調(diào)用GradleMain的main方法
                  Class<?> mainClass = contextClassLoader.loadClass("org.gradle.launcher.GradleMain");
                  Method mainMethod = mainClass.getMethod("main", String[].class);
                  mainMethod.invoke(null, new Object[]{args});
              }
              ......
          }

          可以看出

          1. gradlew其實(shí)就是調(diào)用到了GradlewWrapperMainmain方法
          2. 然后再通過BootstrapMainStarter方法調(diào)用到GradleMain,這里才是Gradle執(zhí)行真正的入口

          當(dāng)前插件是怎樣調(diào)用的?

          上面介紹了Gradle運(yùn)行了的入口,但是要從入口跟代碼跟到我們插件加載的入口是非常麻煩的,我們換個(gè)思路,看下AppPlugin是怎么被加載的

          class AppPluginBasePlugin() {
              override fun apply(project: Project) {
                  //...
                  RuntimeException().printStackTrace()
              }
          }

          我們?cè)诩虞dAppPlugin時(shí)通過以下方式直接打印出堆棧即可,堆棧如下所示:

          java.lang.RuntimeException
           at com.android.build.gradle.AppPlugin.apply(AppPlugin.kt:9)
           at com.android.build.gradle.AppPlugin.apply(AppPlugin.kt:5)
           at org.gradle.api.internal.plugins.ImperativeOnlyPluginTarget.applyImperative(ImperativeOnlyPluginTarget.java:43)
           ...
           at org.gradle.configuration.internal.DefaultUserCodeApplicationContext.apply(DefaultUserCodeApplicationContext.java:43)
           at org.gradle.api.internal.plugins.DefaultPluginManager.doApply(DefaultPluginManager.java:156)
           at org.gradle.api.internal.plugins.DefaultPluginManager.apply(DefaultPluginManager.java:127)
           ...
           at org.gradle.configuration.BuildTreePreparingProjectsPreparer.prepareProjects(BuildTreePreparingProjectsPreparer.java:64)
           at org.gradle.configuration.BuildOperationFiringProjectsPreparer$ConfigureBuild.run(BuildOperationFiringProjectsPreparer.java:52)
           ...

          通過這些堆棧,我們就可以看出AppPlugin是怎么一步一步被加載的,其中要注意到BuildTreePreparingProjectsPreparerDefaultPluginManager兩個(gè)步驟,分別承擔(dān)構(gòu)建classloader父子關(guān)系與設(shè)置當(dāng)前線程上下文classloader,感興趣的同學(xué)可以直接查看源碼

          Gradle類加載機(jī)制

          我們通過在buildSrc中添加同名類的方式就可以實(shí)現(xiàn)覆蓋插件中代碼的效果,猜想應(yīng)該是通過類似Java的類加載機(jī)制實(shí)現(xiàn),我們首先打印下app模塊的classLoader

          fun printClassloader(){
              println("classloader:"+this.javaClass.classLoader)
              println("classloader parent:"+this.javaClass.classLoader.parent)
              println("classloader grantparent:"+this.javaClass.classLoader.parent.parent)
          }

          如上,分別打印classloader與父祖classloader,輸出結(jié)果如下

          classloader:VisitableURLClassLoader(ClassLoaderScopeIdentifier.Id{coreAndPlugins:settings[:]:settings[:buildSrc]:buildSrc[:buildSrc]:root-project[:buildSrc]:Project/TopLevel/stage2(local)})
          classloader parent:VisitableURLClassLoader(ClassLoaderScopeIdentifier.Id{coreAndPlugins:settings[:]:settings[:buildSrc]:buildSrc[:buildSrc]:root-project[:buildSrc](export)})
          classloader grantparent:CachingClassLoader(FilteringClassLoader(VisitableURLClassLoader(legacy-mixin-loader)))

          可以看出,其實(shí)buildSrc模塊的classloader其實(shí)是當(dāng)前模塊的父classLoader,在雙親委托機(jī)制下,會(huì)首先委托給父classloader來查找,那么在buildSrc模塊中已經(jīng)加載了的類自然會(huì)覆蓋插件中的類了,也就可以輕松實(shí)現(xiàn)對(duì)插件代碼邏輯的修改

          總結(jié)

          由于在Gradle代碼運(yùn)行過程中,buildSrc模塊的classloader是項(xiàng)目中module的父classloader,因此在加載類的過程中,會(huì)首先委托給父classloader來查找,如果我們?cè)?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">buildSrc中存在一個(gè)與插件同名且包名也相同的類,就可以覆蓋插件中的代碼,從而達(dá)到修改原有代碼邏輯的目的


          最后歡迎大家加入 音視頻開發(fā)進(jìn)階 知識(shí)星球 ,這里有知識(shí)干貨、編程答疑、開發(fā)教程,還有很多精彩分享。


          更多內(nèi)容可以在星球菜單中找到,隨著時(shí)間推移,干貨也會(huì)越來越多!??!


          給出 10元 優(yōu)惠券,漲價(jià)在即,目前還是白菜價(jià),基本上提幾個(gè)問題就回本,投資自己就是最好的投資?。?!


          加我微信 ezglumes ,拉你進(jìn)技術(shù)交流群

          推薦閱讀:

          音視頻開發(fā)工作經(jīng)驗(yàn)分享 || 視頻版

          OpenGL ES 學(xué)習(xí)資源分享

          開通專輯 | 細(xì)數(shù)那些年寫過的技術(shù)文章專輯

          Android NDK 免費(fèi)視頻在線學(xué)習(xí)!?。?/a>

          你想要的音視頻開發(fā)資料庫來了

          推薦幾個(gè)堪稱教科書級(jí)別的 Android 音視頻入門項(xiàng)目

          覺得不錯(cuò),點(diǎn)個(gè)在看唄~

          瀏覽 20
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美成人性爱在线视频免费 | 黄色视频日本 | 在线免费视频一区二区 | 嫩BX区二区三区的区别 | 老师的粉嫩小又紧水又多A片视频 |