背景:
Android Gradle plugin 3.0開始(對應Gradle版本 4.1及以上),原有的依賴配置類型compile已經被廢棄,開始使用implementation
、api
和annotationProcessor
類型分別替代。對應的,這三種替代配置類型針對具體的使用場景,具備不一樣的依賴行爲。其中,implementation
和api
依賴又相對最爲經常使用,對其具體含義也須要理解清,在實際項目中選擇依賴配置時,也才能遊刃有餘。java
首先看一下Android官方文檔中關於依賴配置的詳細介紹:Add build dependencies git
依賴模塊
當前模塊
其餘上層模塊
因而,官方文檔中的描述翻譯後對應的含義爲:
1,implementation
:
此依賴配置,使Gradle意識到,當前模塊
引入的依賴模塊
,在編譯期間對其餘上層模塊
不可見,僅在運行時對其餘上層模塊
可見。這將會加快多模塊依賴的項目總體編譯速度,由於經過implementation
引入的依賴模塊
,若是依賴模塊
內部有進行過Api的改動,因爲其對其餘上層模塊
不可見,所以只需從新編譯依賴模塊
自身以及使用到此改動的Api的當前模塊
便可。web
2, api
:
等同於原有的compile
,此依賴配置,使Gradle意識到,其引入的依賴模塊
,不管在編譯期仍是在運行時,都對其餘上層模塊
可見,即經過api
配置引入的依賴模塊
,在依賴關係上具備傳遞性。這將會使得其餘上層模塊
能夠直接使用依賴模塊
的Api,但這同時意味着一旦依賴模塊
發生Api的改動,將致使全部已經使用了此依賴模塊
改動了的Api的上層模塊都須要從新執行編譯。所以,通常會加大整個項目的編譯時間,如非必要,通常建議優先使用implementation
依賴配置。api
如此描述通常狀況下還不是很容易理解。描述中最關鍵的幾個詞有:可見性
,依賴傳遞
,編譯期
,運行時
,和 使Gradle意識到
。bash
下面先經過一個具體的例子感性認識下implementation
和 api
二者的區別。 新建一個項目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"; } } 複製代碼
進行依賴配置,使得項目總體依賴相似於樹形結構:
對應的依賴配置分別以下:
implementation
或api
):LibA和:LibBimplementation
依賴:LibCapi
依賴: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相吻合。
至此,相信對implementation
和api
的含義已經有了必定的理解。也已經對上文中的最關鍵的幾個詞有:可見性
,依賴傳遞
,編譯期
,運行時
有了必定的認知。
下面繼續闡述下使Gradle意識到
具體含義。
實際項目開發中,對於第三方的功能模塊,或者項目中抽取出去的獨立的功能模塊,每每造成獨立的git
庫,進行單獨的維護和管理,並生成對應的jar
包或aar
文件,上傳到marven
庫。主工程中的各模塊經過依賴配置去引入對應marven
庫上的構件。其引入的構件有時又每每經過引入了其餘的marven
庫上的構件。此時,通過marven
引入的構件內部,不管是經過implementation
仍是api
的依賴配置去依賴了其餘的marven
構件,效果對於當前模塊
來講,都是等同的。由於implementation
仍是api
的依賴傳遞關係也好,可見性也罷,都是針對當前項目的Gradle
而言的。引入的marven上
的構件,不管是jar
包仍是aar
文件,都已是經過自身編譯以後的構件,其內部的依賴配置對當前項目的Gradle
已經失效。
項目中引入的marven
庫中的構件,其內部的依賴配置對當前項目的Gradle
是失效的。
例如:
api
依賴 :LibBapi
依賴 :LibDapi
依賴了 marven
庫中的構件 :LibXimplementation
依賴了marven
庫中的另外一構件 :LibY此時,LibD依然能夠直接使用LibY中的對外Api
,也就是說,此時即便:LibX項目經過implementation
引入的:LibY,但:LibY對:LibD 依然具備依賴傳遞,具備可見性。
此即官方文檔中說起的 it's letting Gradle know that
的內在含義。
總結:
implementation
和api
依賴配置主要是控制依賴模塊
對上層模塊的依賴關係傳遞及可見性,在實際進行項目構建時,編譯期和運行時,又可能具備不一樣的依賴傳遞關係。理解不一樣的依賴配置,對具體的編譯期和運行時的依賴關係具備重要意義,也是解決依賴衝突等問題的關鍵。
做者:HappyCorn連接:https://juejin.im/post/5c35df566fb9a04a01648512來源:掘金著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。