Android Gradle 依賴配置:implementation & api


背景:
Android Gradle plugin 3.0開始(對應Gradle版本 4.1及以上),原有的依賴配置類型compile已經被廢棄,開始使用implementationapiannotationProcessor類型分別替代。對應的,這三種替代配置類型針對具體的使用場景,具有不同的依賴行為。其中,implementationapi依賴又相對最為常用,對其具體含義也需要理解清,在實際項目中選擇依賴配置時,也才能游刃有余。

首先看一下Android官方文檔中關於依賴配置的詳細介紹:Add build dependencies

Android Gradle依賴配置 為陳述方便且不容易理解歧義,先划分出如下幾個術語。

 

  • 被引入的依賴模塊,簡稱 依賴模塊
  • 引入了被依賴模塊的當前模塊,簡稱 當前模塊
  • 依賴了當前模塊的上層模塊,簡稱 其他上層模塊

於是,官方文檔中的描述翻譯后對應的含義為:
1,implementation
此依賴配置,使Gradle意識到,當前模塊引入的依賴模塊,在編譯期間對其他上層模塊不可見,僅在運行時對其他上層模塊可見。這將會加快多模塊依賴的項目整體編譯速度,因為通過implementation引入的依賴模塊,如果依賴模塊內部有進行過Api的改動,由於其對其他上層模塊不可見,因此只需重新編譯依賴模塊自身以及使用到此改動的Api的當前模塊即可。

2, api
等同於原有的compile,此依賴配置,使Gradle意識到,其引入的依賴模塊,無論在編譯期還是在運行時,都對其他上層模塊可見,即通過api配置引入的依賴模塊,在依賴關系上具有傳遞性。這將會使得其他上層模塊可以直接使用依賴模塊的Api,但這同時意味着一旦依賴模塊發生Api的改動,將導致所有已經使用了依賴模塊改動了的Api的上層模塊都需要重新執行編譯。因此,一般會加大整個項目的編譯時間,如非必要,一般建議優先使用implementation依賴配置。

如此描述一般情況下還不是很容易理解。描述中最關鍵的幾個詞有:可見性依賴傳遞編譯期運行時,和 使Gradle意識到

下面先通過一個具體的例子感性認識下implementationapi 兩者的區別。 新建一個項目HappyCorn,具體項目結構如下:

Root project 'HappyCorn' +--- Project ':app' +--- Project ':LibA' +--- Project ':LibB' +--- Project ':LibC' \--- Project ':LibD' 復制代碼

其中,app為application類型,LibA、LibB、LibC、LibD分別是四個library類型。
LibA中新建如下類:

package com.happycorn.librarya;

public class LibAClass {
    public static String getName() { return "Library A"; } } 復制代碼

同樣的,LibB中:

package com.happycorn.libraryb;

public class LibBClass {
    public static String getName() { return "Library B"; } } 復制代碼

LibC中:

package com.happycorn.libraryc;

public class LibCClass {
    public static String getName() { OkHttpClient okHttpClient = new OkHttpClient(); return "Library C"; } } 復制代碼

LibD中:

package com.happycorn.libraryd;

public class LibDClass {
    public static String getName() { return "Library D"; } } 復制代碼

進行依賴配置,使得項目整體依賴類似於樹形結構:

對應的依賴配置分別如下:

  • :app依賴(implementationapi):LibA和:LibB
  • :LibA implementation 依賴:LibC
  • :LibB api 依賴:LibD

執行graldew命令,查看:app對那些進行了依賴:

./gradlew :app::dependencies
復制代碼

輸出結果為(單元測試等不太相干信息先去掉):

...
debugCompileClasspath - Resolved configuration for compilation for variant: debug +--- project :LibA \--- project :LibB \--- project :LibD debugRuntimeClasspath - Resolved configuration for runtime for variant: debug +--- project :LibA | \--- project :LibC \--- project :LibB \--- project :LibD releaseCompileClasspath - Resolved configuration for compilation for variant: release +--- project :LibA \--- project :LibB \--- project :LibD releaseRuntimeClasspath - Resolved configuration for runtime for variant: release +--- project :LibA | \--- project :LibC \--- project :LibB \--- project :LibD ... 復制代碼

從輸出信息中可以看出,無論是debug還是release變體,在編譯時與運行時所依賴的依賴模塊是不同的。對於:LibC,在編譯時對:app不可見,但在運行時對:app是可見的。

執行./gradlew :LibA:dependencies,確認下:LibA的依賴。 對應輸出結果:

debugCompileClasspath - Resolved configuration for compilation for variant: debug \--- project :LibC debugRuntimeClasspath - Resolved configuration for runtime for variant: debug \--- project :LibC releaseCompileClasspath - Resolved configuration for compilation for variant: release \--- project :LibC releaseRuntimeClasspath - Resolved configuration for runtime for variant: release \--- project :LibC 復制代碼

可見,:LibA確實已經依賴了:LibC。

進一步,如果此時在:app中分別調用:LibA、:LibB、:LibC、:LibD模塊的Api,發現:app中是無法直接調用:LibC的方法的。

 

因此,可以證實,通過 implementation引入的依賴模塊,在編譯期對其他上層模塊是不可見的,對應的依賴關系不具有傳遞性。

接下來繼續看依賴關系與模塊編譯之間的關系。 先執行命令清理掉歷史構建結果:

./gradlew clean
復制代碼

執行build task assembleDebug 或 :app:compileDebugJavaWithJavac:

./gradlew :app:compileDebugJavaWithJavac  --info
復制代碼

編譯成功,其中,關鍵信息輸出記錄為:

:LibC:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) started. > Task :LibC:compileDebugJavaWithJavac ... Compiling with JDK Java compiler API. Class dependency analysis for incremental compilation took 0.003 secs. Created jar classpath snapshot for incremental compilation in 0.0 secs. Written jar classpath snapshot for incremental compilation in 0.0 secs. :LibC:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) completed. Took 0.023 secs. ... :LibA:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) started. > Task :LibA:compileDebugJavaWithJavac ... Compiling with JDK Java compiler API. Class dependency analysis for incremental compilation took 0.001 secs. Created jar classpath snapshot for incremental compilation in 0.001 secs. Written jar classpath snapshot for incremental compilation in 0.0 secs. :LibA:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) completed. Took 0.024 secs. ... :LibD:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) started. > Task :LibD:compileDebugJavaWithJavac ... Compiling with JDK Java compiler API. Class dependency analysis for incremental compilation took 0.0 secs. Created jar classpath snapshot for incremental compilation in 0.0 secs. Written jar classpath snapshot for incremental compilation in 0.0 secs. :LibD:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) completed. Took 0.018 secs. ... :LibB:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) started. > Task :LibB:compileDebugJavaWithJavac ... Compiling with JDK Java compiler API. Class dependency analysis for incremental compilation took 0.002 secs. Created jar classpath snapshot for incremental compilation in 0.0 secs. Written jar classpath snapshot for incremental compilation in 0.0 secs. :LibB:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) completed. Took 0.033 secs. ... :app:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) started. > Task :app:compileDebugJavaWithJavac ... Compiling with JDK Java compiler API. Class dependency analysis for incremental compilation took 0.004 secs. Created jar classpath snapshot for incremental compilation in 0.0 secs. Written jar classpath snapshot for incremental compilation in 0.0 secs. :app:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) completed. Took 0.099 secs. ... 復制代碼

每個模塊都進行了對應的compile過程。且對應的順序為:LibC >> :LibA >> :LibD >> :LibB >> :app

再次執行build task compileDebugJavaWithJavac:

./gradlew :app:compileDebugJavaWithJavac  --info
復制代碼

編譯成功,此時,關鍵信息輸出記錄為:

:LibC:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 2,5,main]) started. > Task :LibC:compileDebugJavaWithJavac UP-TO-DATE Skipping task ':LibC:compileDebugJavaWithJavac' as it is up-to-date. :LibC:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 2,5,main]) completed. Took 0.003 secs. ... :LibA:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 2,5,main]) started. > Task :LibA:compileDebugJavaWithJavac UP-TO-DATE Skipping task ':LibA:compileDebugJavaWithJavac' as it is up-to-date. :LibA:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 2,5,main]) completed. Took 0.003 secs. ... :LibD:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 2,5,main]) started. > Task :LibD:compileDebugJavaWithJavac UP-TO-DATE Skipping task ':LibD:compileDebugJavaWithJavac' as it is up-to-date. :LibD:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 2,5,main]) completed. Took 0.002 secs. ... :LibB:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 2,5,main]) started. > Task :LibB:compileDebugJavaWithJavac UP-TO-DATE Skipping task ':LibB:compileDebugJavaWithJavac' as it is up-to-date. :LibB:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 2,5,main]) completed. Took 0.003 secs. ... :app:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 2,5,main]) started. > Task :app:compileDebugJavaWithJavac UP-TO-DATE Skipping task ':app:compileDebugJavaWithJavac' as it is up-to-date. :app:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 2,5,main]) completed. Took 0.024 secs. 復制代碼

我們發現,對應的compileDebugJavaWithJavac task都直接Skip掉了,因為此時代碼沒有更新,無需重新編譯。

修改:libD中LibDClass類中的代碼,先修改方法內的代碼:

public class LibDClass {
    public static String getName() { return "Library D ... change code"; } } 復制代碼

再次執行build task compileDebugJavaWithJavac:

 ./gradlew :app:compileDebugJavaWithJavac --info
復制代碼

對應關鍵編譯信息為:

Skipping task ':LibC:compileDebugJavaWithJavac' as it is up-to-date. ... Skipping task ':LibA:compileDebugJavaWithJavac' as it is up-to-date. ... :LibB:compileDebugJavaWithJavac (Thread[Task worker for ':',5,main]) started. > Task :LibB:compileDebugJavaWithJavac UP-TO-DATE Skipping task ':LibB:compileDebugJavaWithJavac' as it is up-to-date. :LibB:compileDebugJavaWithJavac (Thread[Task worker for ':',5,main]) completed. Took 0.004 secs. ... Skipping task ':LibB:compileDebugJavaWithJavac' as it is up-to-date. ... Skipping task ':app:compileDebugJavaWithJavac' as it is up-to-date. 復制代碼

我們發現,修改:LibD中的方法中的代碼,task compileDebugJavaWithJavac只是重新編譯了:LibD。其他模塊,包括依賴此模塊的各上層模塊,都沒有重新執行編譯task。

接下來,修改:LibD中的方法名,對應如下:

public class LibDClass {
    public static String getNewName() { return "Library D"; } } 復制代碼

執行:

./gradlew :app:compileDebugJavaWithJavac  --info
復制代碼

關鍵信息輸出為:

Skipping task ':LibC:compileDebugJavaWithJavac' as it is up-to-date. ... Skipping task ':LibA:compileDebugJavaWithJavac' as it is up-to-date. ... :LibD:compileDebugJavaWithJavac (Thread[Task worker for ':',5,main]) started. > Task :LibD:compileDebugJavaWithJavac Task ':LibD:compileDebugJavaWithJavac' is not up-to-date because: Input property 'source' file /Users/corn/AndroidStudioProjects/HappyCorn/LibD/src/main/java/com/happycorn/libraryd/LibDClass.java has changed. Compiling with source level 1.7 and target level 1.7. Created jar classpath snapshot for incremental compilation in 0.0 secs. Compiling with JDK Java compiler API. Incremental compilation of 1 classes completed in 0.008 secs. Class dependency analysis for incremental compilation took 0.001 secs. Written jar classpath snapshot for incremental compilation in 0.0 secs. :LibD:compileDebugJavaWithJavac (Thread[Task worker for ':',5,main]) completed. Took 0.013 secs. ... > Task :LibB:javaPreCompileDebug Task ':LibB:javaPreCompileDebug' is not up-to-date because: Input property 'compileClasspaths' file /Users/corn/AndroidStudioProjects/HappyCorn/LibD/build/intermediates/intermediate-jars/debug/classes.jar has changed. :LibB:javaPreCompileDebug (Thread[Task worker for ':',5,main]) completed. Took 0.002 secs. :LibB:compileDebugJavaWithJavac (Thread[Task worker for ':',5,main]) started. > Task :LibB:compileDebugJavaWithJavac UP-TO-DATE Task ':LibB:compileDebugJavaWithJavac' is not up-to-date because: Input property 'classpath' file /Users/corn/AndroidStudioProjects/HappyCorn/LibD/build/intermediates/intermediate-jars/debug/classes.jar has changed. Compiling with source level 1.7 and target level 1.7. Created jar classpath snapshot for incremental compilation in 0.0 secs. None of the classes needs to be compiled! Analysis took 0.001 secs. Written jar classpath snapshot for incremental compilation in 0.0 secs. :LibB:compileDebugJavaWithJavac (Thread[Task worker for ':',5,main]) completed. Took 0.005 secs. ... > Task :app:javaPreCompileDebug Task ':app:javaPreCompileDebug' is not up-to-date because: Input property 'compileClasspaths' file /Users/corn/AndroidStudioProjects/HappyCorn/LibD/build/intermediates/intermediate-jars/debug/classes.jar has changed. :app:javaPreCompileDebug (Thread[Task worker for ':',5,main]) completed. Took 0.002 secs. :app:compileDebugJavaWithJavac (Thread[Task worker for ':',5,main]) started. > Task :app:compileDebugJavaWithJavac UP-TO-DATE Task ':app:compileDebugJavaWithJavac' is not up-to-date because: Input property 'classpath' file /Users/corn/AndroidStudioProjects/HappyCorn/LibD/build/intermediates/intermediate-jars/debug/classes.jar has changed. Compiling with source level 1.7 and target level 1.7. Created jar classpath snapshot for incremental compilation in 0.0 secs. None of the classes needs to be compiled! Analysis took 0.0 secs. Written jar classpath snapshot for incremental compilation in 0.0 secs. :app:compileDebugJavaWithJavac (Thread[Task worker for ':',5,main]) completed. Took 0.004 secs. 復制代碼

可見,此時,:LiD,:LiB,:app依次都重新進行了編譯task。

新增類,新增或修改非private的對外方法名等,在api引入的方式下,都會使得上層模塊重新編譯,因為上層模塊可能會直接用到此類方法,但在上層模塊的實際編譯過程中,並不會對模塊內的類都進行重新編譯,而是只會編譯確實已經使用了依賴模塊的API的類。

這也正是文檔中提到的:
if an api dependency changes its external API, Gradle recompiles all modules that have access to that dependency at compile time.

同樣的,我們改變:LibC中的getName()方法實現,方便編譯信息跟改變:LibD中的getName()方法實現一樣,其他上層模塊都沒有重新執行編譯task。

同樣的,改變:LibC中的方法名,再次執行:

 ./gradlew :app:compileDebugJavaWithJavac --info
復制代碼

關鍵信息輸出為:

:LibC:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) started. > Task :LibC:compileDebugJavaWithJavac Task ':LibC:compileDebugJavaWithJavac' is not up-to-date because: Input property 'source' file /Users/corn/AndroidStudioProjects/HappyCorn/LibC/src/main/java/com/happycorn/libraryc/LibCClass.java has changed. Compiling with source level 1.7 and target level 1.7. Created jar classpath snapshot for incremental compilation in 0.0 secs. file or directory '/Users/corn/AndroidStudioProjects/HappyCorn/LibC/src/debug/java', not found Compiling with JDK Java compiler API. Incremental compilation of 1 classes completed in 0.009 secs. Class dependency analysis for incremental compilation took 0.004 secs. Written jar classpath snapshot for incremental compilation in 0.0 secs. :LibC:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) completed. Took 0.02 secs. ... > Task :LibA:javaPreCompileDebug Task ':LibA:javaPreCompileDebug' is not up-to-date because: Input property 'compileClasspaths' file /Users/corn/AndroidStudioProjects/HappyCorn/LibC/build/intermediates/intermediate-jars/debug/classes.jar has changed. :LibA:javaPreCompileDebug (Thread[Task worker for ':' Thread 3,5,main]) completed. Took 0.018 secs. :LibA:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) started. > Task :LibA:compileDebugJavaWithJavac UP-TO-DATE Task ':LibA:compileDebugJavaWithJavac' is not up-to-date because: Input property 'classpath' file /Users/corn/AndroidStudioProjects/HappyCorn/LibC/build/intermediates/intermediate-jars/debug/classes.jar has changed. Compiling with source level 1.7 and target level 1.7. Created jar classpath snapshot for incremental compilation in 0.001 secs. None of the classes needs to be compiled! Analysis took 0.001 secs. Written jar classpath snapshot for incremental compilation in 0.0 secs. :LibA:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) completed. Took 0.009 secs. ... > Task :LibD:javaPreCompileDebug UP-TO-DATE Skipping task ':LibD:javaPreCompileDebug' as it is up-to-date. ... > Task :LibB:compileDebugJavaWithJavac UP-TO-DATE Skipping task ':LibB:compileDebugJavaWithJavac' as it is up-to-date. ... :app:javaPreCompileDebug (Thread[Task worker for ':' Thread 3,5,main]) started. > Task :app:javaPreCompileDebug UP-TO-DATE Skipping task ':app:javaPreCompileDebug' as it is up-to-date. :app:javaPreCompileDebug (Thread[Task worker for ':' Thread 3,5,main]) completed. Took 0.008 secs. :app:compileDebugJavaWithJavac (Thread[Task worker for ':' Thread 3,5,main]) started. > Task :app:compileDebugJavaWithJavac UP-TO-DATE Skipping task ':app:compileDebugJavaWithJavac' as it is up-to-date. 復制代碼

可見,此時:LibA重新執行了編譯task,但:LibA的上層模塊:app並沒有重新執行編譯task。因為:app的依賴關系在編譯期並不包含:LibC相吻合。

至此,相信對implementationapi的含義已經有了一定的理解。也已經對上文中的最關鍵的幾個詞有:可見性依賴傳遞編譯期運行時有了一定的認知。

下面繼續闡述下使Gradle意識到具體含義。

實際項目開發中,對於第三方的功能模塊,或者項目中抽取出去的獨立的功能模塊,往往形成獨立的git庫,進行單獨的維護和管理,並生成對應的jar包或aar文件,上傳到marven庫。主工程中的各模塊通過依賴配置去引入對應marven庫上的構件。其引入的構件有時又往往通過引入了其他的marven庫上的構件。此時,通過marven引入的構件內部,不論是通過implementation還是api的依賴配置去依賴了其他的marven構件,效果對於當前模塊來說,都是等同的。因為implementation還是api的依賴傳遞關系也好,可見性也罷,都是針對當前項目的Gradle而言的。引入的marven上的構件,不論是jar包還是aar文件,都已經是通過自身編譯之后的構件,其內部的依賴配置對當前項目的Gradle已經失效。

項目中引入的marven庫中的構件,其內部的依賴配置對當前項目的Gradle是失效的。

例如:

  • :app api 依賴 :LibB
  • :LiB api依賴 :LibD
  • :LibD api依賴了 marven庫中的構件 :LibX
  • :LibX項目內部implementation依賴了marven庫中的另一構件 :LibY

此時,LibD依然可以直接使用LibY中的對外Api,也就是說,此時即使:LibX項目通過implementation引入的:LibY,但:LibY對:LibD 依然具有依賴傳遞,具有可見性。

此即官方文檔中提及的 it's letting Gradle know that的內在含義。

總結:
implementationapi依賴配置主要是控制依賴模塊對上層模塊的依賴關系傳遞及可見性,在實際進行項目構建時,編譯期和運行時,又可能具有不同的依賴傳遞關系。理解不同的依賴配置,對具體的編譯期和運行時的依賴關系具有重要意義,也是解決依賴沖突等問題的關鍵。


作者:HappyCorn
鏈接:https://juejin.im/post/5c35df566fb9a04a01648512
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM