再谈 Application ProvidedAar
前言
去年年底的時候,寫過一篇博客叫《Android-application中使用provided-aar并沒有那么簡單》。當時文章中介紹了android gradle plugin不同版本的實現方式,對android gradle plugin 2.2.0以下版本的實現,是采用使用gradle maven的api實現,利用gradle本地緩存,這種方式不但反射的地方非常多,而且不支持傳遞依賴,局限性非常大。周末有空,又重新拿起來研究了下,最終實現了android gradle plugin [1.3.0,3.2.0+)版本的傳遞依賴,在插件化中起著重要意義,幾乎是全版本兼容了,通過源碼發現使用相同的方式無法進行兼容1.3.0以下的版本,且版本過于久遠,所以直接不支持了。建議閱讀本篇博客前,強烈建議先將之前的那篇博客看一下,否則可能無法理解。沒有特殊說明的情況下,下文將android gradle plugin簡稱為AGP。
回顧
providedAar的重要性就在于在com.android.application插件中我需要引用到對應的類或者資源,但是編譯打包的時候我又不能將其打包進去,場景就在插件化中,一些公共類和資源定義在宿主中,插件中不能再次定義。除了providedAar這一種實現外,還有一種實現是干預編譯過程,在編譯期干掉對應的類和資源,典型的實現有VirtualApk和Small。
從之前那篇文章可以知道,之前對于AGP 2.2.0以下的版本,是使用gradle緩存在本地的aar,將classes.jar提取出來,添加到provided這個scope上,這種實現無法進行傳遞依賴,且添加的只有代碼,沒有資源。當時也說明了為什么不采用AGP 2.2.0以上的實現,原因如下:
2.2.0以下的版本,雖然也可以使用[2.2.0,2.5.0)這個區間的方法將異常進行消除,但是即使消除了,因為其代碼的特殊性,導致即使是provided依賴的aar,最終也會被打包進apk中,這里以2.1.3的代碼為例,如下:
| 1234567891011121314151617181920 | //遍歷過濾后剩余的編譯期的依賴for (LibInfo lib : compiledAndroidLibraries) { if (!copyOfPackagedLibs.contains(lib)) { if (isLibrary || lib.isOptional()) { //如果是com.android.library或者是可選的,則設置該lib為可選 lib.setIsOptional(true);} else { //否則,也就是在com.android.application中,將這個問題記錄了下來,后續的處理就在PrepareDependenciesTask執行的過程中報異常 //noinspection ConstantConditionsvariantDeps.getChecker().addSyncIssue(extraModelInfo.handleSyncError( lib.getResolvedCoordinates().toString(),SyncIssue.TYPE_NON_JAR_PROVIDED_DEP, String.format( "Project %s: provided dependencies can only be jars. %s is an Android Library.",project.getName(), lib.getResolvedCoordinates())));}} else {copyOfPackagedLibs.remove(lib);}} |
對比2.3.3版本的代碼
| 12345678910111213141516 | //獲得maven坐標MavenCoordinates resolvedCoordinates = compileLib.getCoordinates();//如果不是com.android.library,也不是com.android.atom,也不是用于測試的com.android.test,即是com.android.applicationif (variantType != VariantType.LIBRARY&& variantType != VariantType.ATOM&& (testedVariantType != VariantType.LIBRARY || !variantType.isForTesting())) { //就會將這個異常記錄下來,后續的處理就在PrepareDependenciesTask執行的過程中報異常handleIssue(resolvedCoordinates.toString(),SyncIssue.TYPE_NON_JAR_PROVIDED_DEP,SyncIssue.SEVERITY_ERROR, String.format( "Project %s: Provided dependencies can only be jars. %s is an Android Library.",projectName,resolvedCoordinates.toString()));} |
從兩個版本的代碼中可以看出有一處不同,2.2.0以下的版本在com.android.library中會將lib設為可選,但是在com.android.application就會直接拋出異常,具體的區別代碼如下:
| 123456 | if (isLibrary || lib.isOptional()) {//如果是com.android.library或者是可選的,則設置該lib為可選 lib.setIsOptional(true);} else { //拋異常} |
一旦將lib設置為可選,后續就不會打包進apk,那么我們能不能將其設置為可選從而不將其打包進去呢,當時給的答案是不能,但是周末研究的時候,發現從PrepareDependenciesTask這個task入手,是可以拿到對應的lib,將其設置為可選的。那么具體實現流程是怎么樣的呢?
實現
通過查看PrepareDependenciesTask這個類,發現其成員變量checkers(類型是DependencyChecker List)中每一項DependencyChecker持有了VariantDependencies變量,而VariantDependencies這個類中可以拿到當前項目依賴的所有library,該值被存儲在libraries中,具體類型是LibraryDependencyImpl List,而LibraryDependencyImpl中有個isOptional的變量,只要將其設為true,就不會被打包進apk了,除此之外,LibraryDependencyImpl中還有一個dependencies變量(類型是LibraryDependency List) ,里面存儲對應依賴的傳遞依賴,因此處理時需要遞歸處理dependencies變量,將傳遞依賴的isOptional也設為true,值得特別注意的是isOptional這個變量是final的,在反射調用的時候,要特殊處理一下。
因此只要將2.2.0以下的版本特殊處理一下就可以了,詳細代碼如下:
| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758 | try { Class prepareDependenciesTaskClass = Class.forName("com.android.build.gradle.internal.tasks.PrepareDependenciesTask")Field checkersField = prepareDependenciesTaskClass.getDeclaredField('checkers')checkersField.setAccessible(true) def checkers = checkersField.get(prepareDependenciesTask)checkers.iterator().with { checkersIterator ->checkersIterator.each {dependencyChecker -> def syncIssues = dependencyChecker.syncIssuessyncIssues.iterator().with { syncIssuesIterator ->syncIssuesIterator.each {syncIssue -> if (syncIssue.getType() == 7 && syncIssue.getSeverity() == 2) { project.logger.info "[providedAar] WARNING: providedAar has been enabled in com.android.application you can ignore ${syncIssue}"syncIssuesIterator.remove()}}} //兼容1.3.0~2.1.3版本,為了將provided的aar不參與打包,將isOptional設為true if (androidGradlePluginVersion.startsWith("1.3") || androidGradlePluginVersion.startsWith("1.5") || androidGradlePluginVersion.startsWith("2.0") || androidGradlePluginVersion.startsWith("2.1")) { def configurationDependencies = dependencyChecker.configurationDependenciesList libraries = configurationDependencies.librarieslibraries.each {library ->providedAarConfiguration.dependencies.each {providedDependency ->String libName = library.getName() if (libName.contains(providedDependency.group) && libName.contains(providedDependency.name) && libName.contains(providedDependency.version)) { //final字段Field isOptionalField = library.getClass().getDeclaredField("isOptional") //final類型的修改,先修改modifiersField modifiersField = Field.class.getDeclaredField("modifiers")modifiersField.setAccessible(true) //去finalmodifiersField.setInt(isOptionalField, isOptionalField.getModifiers() & ~java.lang.reflect.Modifier.FINAL)isOptionalField.setAccessible(true) //將isOptional設為trueisOptionalField.setBoolean(library, true) //為了遞歸調用可以引用,先聲明再賦值 def fixDependencies = nullfixDependencies = {dependencies -> dependencies.each {dependency -> if (dependency.getClass() == library.getClass()) {isOptionalField.setBoolean(dependency, true) //遞歸dependenciesfixDependencies(dependency.dependencies)}}}fixDependencies(library.dependencies)}}}}}}} catch (Exception e) {e.printStackTrace()}prepareDependenciesTask.configure removeSyncIssues |
修改完成后簡單測試一下,發現成功了,providedAar依賴最終并沒有被打入apk中。但是別高興得太早。成功是在命令行測試可行,但是一旦你點擊Android Studio上的sync按鈕,你會發現還是會出現那個異常
| 1 | Provided dependencies can only be jars |
問題出在哪呢,其實Android Studio在構建的時候,會向AGP注入它的一些實現,導致命令行的實現和Android Studio里點擊按鈕編譯是不一樣的。那么這個問題怎么解決呢,也很簡單,在調用prepareDependenciesTask.configure removeSyncIssues前對2.2.0以下版本做下特殊處理就可以了。
那么要對什么進行處理呢,在BasePlugin中有個taskManager對象,其類型是TaskManager;taskManager中有個叫dependencyManager的對象,其類型是DependencyManager;dependencyManager中有個extraModelInfo,其類型是ExtraModelInfo,這個類的handleSyncIssue函數,會根據當前注入的類型,做不同處理,具體代碼如下:
| 1234567891011121314151617181920212223242526272829303132333435363738 | protected SyncIssue handleSyncIssue( String data,int type, int severity, @NonNull String msg) { SyncIssue issue;switch (getMode()) { case STANDARD: if (severity != SyncIssue.SEVERITY_WARNING && !isDependencyIssue(type)) { throw new GradleException(msg);} // if it's a dependency issue we don't throw right away. we'll // throw during build instead. // but we do log.project.getLogger().warn("WARNING: " + msg);issue = new SyncIssueImpl(type, severity, data, msg); break; case IDE_LEGACY: // compat mode for the only issue supported before the addition of SyncIssue // in the model. if (severity != SyncIssue.SEVERITY_WARNING&& type != SyncIssue.TYPE_UNRESOLVED_DEPENDENCY) { throw new GradleException(msg);} // intended fall-through case IDE: // new IDE, able to support SyncIssue.issue = new SyncIssueImpl(type, severity, data, msg);syncIssues.put(SyncIssueKey.from(issue), issue); break; default: throw new RuntimeException("Unknown SyncIssue type");} return issue;} |
對應命令行調用,它會直接輸出日志,對于Android Studio注入的類型,它會拋異常或者記錄下來后續由Android Studio報告異常,即后續sync失敗的提示內容。這里走的邏輯是存入syncIssues中,后續由Android Studio報告問題。
那么問題就變得很簡單了,反射將syncIssues(Map對象)中類型是7(Provided dependencies can only be jars),錯誤級別是2的問題remove掉就好了。具體實現如下:
| 1234567891011121314151617181920212223 | if (androidGradlePluginVersion.startsWith("1.3") || androidGradlePluginVersion.startsWith("1.5") || androidGradlePluginVersion.startsWith("2.0") || androidGradlePluginVersion.startsWith("2.1")) { //這里不處理as sync的時候會出錯 //獲取AppPlugin def appPlugin = project.getPlugins().findPlugin("com.android.application") //獲取TaskManager def taskManager = appPlugin.getMetaClass().getProperty(appPlugin, "taskManager") //獲取DependencyManager,注意調用getSuperclass(),否則無法找到字段 def dependencyManager = taskManager.getClass().getSuperclass().getMetaClass().getProperty(taskManager, "dependencyManager") //獲取ExtraModelInfo def extraModelInfo = dependencyManager.getMetaClass().getProperty(dependencyManager, "extraModelInfo") //獲取syncIssues,類型是Map<SyncIssueKey, SyncIssue>,只要遍歷這個map,將value中type=7&severity=2的項移除即可Map<?, ?> syncIssues = extraModelInfo.getSyncIssues() //groovy迭代器遍歷syncIssues.iterator().with {syncIssuesIterator ->syncIssuesIterator.each {syncIssuePair -> if (syncIssuePair.getValue().getType() == 7 && syncIssuePair.getValue().getSeverity() == 2) {syncIssuesIterator.remove()}}} //下面同2.2.0+處理prepareDependenciesTask.configure removeSyncIssues} |
修改完成后再次點擊Android Studio中sync按鈕,發現不再報錯了,再次測試,發現一切都正常。
代碼的具體修改可以參見這個提交?支持到android gradle plugin 1.3.0傳遞依賴
總結
時隔3個多月,再次深入研究,收獲頗多。完美解決了AGP 1.3.0+ 的providedAar功能,并支持傳遞依賴。
http://fucknmb.com/2018/03/19/%E5%86%8D%E8%B0%88Application-ProvidedAar/總結
以上是生活随笔為你收集整理的再谈 Application ProvidedAar的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: aapt2 生成资源 public fl
- 下一篇: Application Provided