Android Gradle 依賴配置:implementation & api

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

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

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

 

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

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

2, api
等同於原有的compile,此依賴配置,使Gradle意識到,其引入的依賴模塊,不管在編譯期仍是在運行時,都對其餘上層模塊可見,即經過api配置引入的依賴模塊,在依賴關係上具備傳遞性。這將會使得其餘上層模塊能夠直接使用依賴模塊的Api,但這同時意味着一旦依賴模塊發生Api的改動,將致使全部已經使用了依賴模塊改動了的Api的上層模塊都須要從新執行編譯。所以,通常會加大整個項目的編譯時間,如非必要,通常建議優先使用implementation依賴配置。api

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

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

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

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

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中:gradle

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來源:掘金著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。

相關文章
相關標籤/搜索