1. 程式人生 > >android元件化打包module遇到的問題總結(打包成aar)

android元件化打包module遇到的問題總結(打包成aar)

在一開始接觸APICLoud平臺的時候我是拒絕的,因為對於一個有著熟練的java技能,掌握著老舊設計模式的人來說,這種平臺簡直就是對於程式設計師這個職業的侮辱。第一個原因是APICLoud平臺剝離了原生開發和html+js開發的職責,使得原生開發的職業方向越來越窄,開發中的地位也逐漸下降;第二個原因是,這個平臺完全是無腦式開發,所有的js框架都是封裝好的,只需要像jQuery一樣直接呼叫就好了,APICLoud框架的設計使得前端開發者開發起來特別簡單,前端如果轉apicloud開發成本會很低,完全用不著設計模式。
現在想想之前的想法過於偏激,社會就是越來越現代化,開發語言只是一種工具,人都說君欲善其事,必先利器。我覺得很有道理,這些平臺的發展就是為了減少開發人員的程式碼編寫工作,集中注意力在業務邏輯上,也可以使得企業能夠快速迭代產品,從而減少執行成本,安卓/ios開發一直都飽受著混合開發和RN開發的折磨,現在這個APICLoud平臺的出現我覺得是混合開發平臺成熟的標誌,因為該平臺不僅僅是混合開發的框架,而且包含著混合開發的所有安卓/ios的第三方sdk的商店,也就是api服務商業化模式,這種模式對於開發人員的衝擊是相當大的,當sdk商店中已經集齊了所有滿足日常的sdk的時候,原生人員也就失業了,因為所有的基礎功能商店已經有,前端只需要配置進apicloud框架就可以了,企業的開發人員根本不需要重複造輪子,可以把大量的時間放在業務邏輯上。
在學習安卓原生的過程中,讓我明白了很多道理,那就是這個世界唯一不變的是改變,唯有不斷的改變和學習才能跟的上時代的步伐,授人以魚不如授人以漁,學習過程中也積累了很多學習經驗,學習效率明顯得到了提高,現在我馬上要轉到小程式和網頁編寫了,想想也有點小激動呢!經過一個禮拜的學習,基本上可以做一些頁面了,路漫漫其修遠兮,吾將上下而求索。

在自定義模組的時候出現的問題

APICLoud的平臺自定義模組上傳需要的是aar包,而aar包的特點就是內部會打包jar和jni裡面的so檔案,但是不會把gradle的遠端依賴下載並且打包進aar檔案中,這就需要手動下載complie所引用的遠端庫了。

什麼是gradle?##

  •  Gradle是一個基於Apache Ant和Apache Maven概念的專案自動化構建工具。它使用一種基於Groovy的特定領域語言(DSL)來宣告專案設定,拋棄了基於XML的各種繁瑣配置。面向Java應用為主。當前其支援的語言限於Java、Groovy、Kotlin和Scala。
    
    他的功能是根據編寫的gradle task來完成專案構建任務的,我們android studio中的gradle檔案就是用來編譯我們的安卓專案的,其中包含外掛匯入,利用外掛輔助編譯,多個編譯檔案合併操作,引入遠端倉庫等等,gradle的功能非常強大。
    gradle官網可以檢視用法

利用gradle來完成打包任務

1、下載遠端庫

  • 電腦上安裝一個gradle軟體,配置好環境變數。
    接下來就可以進行下載任務了。
    參考:點選這裡
    使用此方法下載Jar包的前提是已經配置好了Gradle的環境了,配置好的標誌是在終端輸入gradle不提示command not found。
1. 編寫build.gradle檔案程式碼:
apply plugin: 'java'
repositories {
    mavenCentral()
    jcenter()
    google()
}
dependencies {
    compile 'io.reactivex.rxjava2:rxjava:2.1.3'
}

task copyJars(type: Copy) {
  from configurations.runtime
  into 'lib' // 目標位置
}

2. 執行下載的指令
gradle copyJars

如果上述程式碼執行之後,發現不能下載成功,那麼更換倉庫地址,使用國內映象下載:

  		 maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }  //阿里映象
        maven{ url 'http://maven.aliyun.com/nexus/content/repositories/jcenter'}  //阿里映象
        mavenCentral()
        jcenter()
        google()

2、合併依賴包

  • 1> libs下面的包合併
    一般我都是用程式碼編寫一個task,把所有dependencies{}括號包含的語句拆解成一句一句,因為可能引用裡面版本不一致,就會導致下載下來的重名的jar或者aar會相互覆蓋,這個需要自己手動去排查版本,比如說support包需要版本一致,而okhttp和io-socket都引用了oki.jar包,就是用程式碼讓每一個依賴下載下來的檔案都放在單獨的一個資料夾,然後自己手動去合併這些包到libs中,這裡一定要細心和耐心,也要做到版本就低不就高。
    如果出現什麼mutilclass , duplicate resource等等都是重複引用的導致的問題。

  • 2> so檔案的合併問題
    so檔案也需要手動去合併,因為你自定義的module可能會和其他的module的so檔案衝突,這個就需要在android{}標籤內加入

   packagingOptions {
        //jni包重複編譯,那麼在這裡配置包包含 或者不包含
        exclude 'lib/mips/librsjni.so'
        exclude 'lib/armeabi-v7a/libRSSupport.so'
        exclude 'lib/arm64-v8a/librsjni.so'
        exclude 'lib/arm64-v8a/libRSSupport.so'
        exclude 'lib/x86_64/libRSSupport.so'
        exclude 'lib/mips/libRSSupport.so'
        exclude 'lib/x86/librsjni.so'
        exclude 'lib/x86_64/librsjni.so'
        exclude 'lib/armeabi-v7a/librsjni.so'
        exclude 'lib/x86/libRSSupport.so'
    }

還要一個就是其他module有的相容版本,你沒有,那麼就需要手動複製相容版本資料夾,把對應的檔案匯入,比如,APICLOUD平臺只有armeabi和armeabi-v7a cpu版本相容
而你的module有x86 armeabi64 armeabi和armeabi-v7a cpu版本相容,那麼在編譯的時候就會包含四個資料夾,而其他module只有兩個,那麼就會執行時就會缺包,如果是這種情況,那麼就把自己的包x86 armeabi64 刪掉,或者在 defaultConfig{}標籤內加入

  ndk {
            abiFilters  "armeabi-v7a", "armeabi"
        }

這樣編譯的時候就只會保留 “armeabi-v7a”, "armeabi"資料夾。
關於so檔案相容請看:
連結內容

  • 3> 外掛匯入
    外掛是gradle執行的環境,前提條件,比如說
apply plugin: 'com.android.library'
apply plugin: 'com.jakewharton.butterknife'

這裡的意思就是說當前gradle的任務是把這個module當成是library來編譯,在執行gradle之前載入butterknife外掛,至於如何配置butterknife的話,這裡不細說,但是要把module打包成aar的話,載入butterknife外掛必須本地載入,因為aar不會自動下載遠端依賴的,同理使用第一步所說的下載butterknife所需要的jar包將它們放入lib(注意是新建的)資料夾。然後在gradle的根部新增如下程式碼:

//展開libs資料夾裡面的檔案,aar檔案依賴必須的配置
repositories{
    flatDir{
        dirs 'libs'
    }
}
/* buildscript這是gradle環境本身所依賴的庫,一般是連結
maven和git,Jcenter的倉庫載入外掛的,但是這裡必須本地載入,
下面是把所有的lib檔案中的檔案載入進去*/
buildscript {
    dependencies {
        classpath fileTree(include: ['*.jar'], dir: 'lib')
    }
}

2、專案合併

  • 上面只是僅僅把module中所有的jar和aar都合併進去了,接下來還要考慮,不同的module直接依賴衝突的問題,先來了解下gradle依賴的幾個關鍵字:
Android Studio引用第三方庫很方便,只需要一句程式碼就可以搞定,幾種引用第三方庫的方式,總結一下:

方式:1:它就會自動把這個包下載下來,並且引用它。節省git空間,而且修改版本也很方便。
compile 'com.android.support:support-v4:23.3.0'

方式2:引用libs下所有jar包
compile fileTree(dir: 'libs', include: ['*.jar'])

方式3:引用一個jar
compile files('libs/fastjson-1.1.53.android.jar')

方式4:引用一個aar檔案,注意並不能像 方式2 那樣自動引用全部的aar,而需要對每個aar分別進行引用。
compile(name: 'aar_file_name', ext: 'aar')

方式5:引用庫型別的專案
compile project(':xxxsdk')

方式6:僅僅在編譯時使用,但最終不會被編譯到apk或aar裡
provided files('libs/glide-3.7.0.jar')
  • 經過本人實驗,貌似是資原始檔在打包編譯的時候如果重名就會覆蓋,而class檔案重名就是直接編譯報錯,所以資原始檔衝突是隱形的,class衝突比較明顯。
    jar衝突的話,讓主module依賴這個jar,其他次module使用provided 關鍵字依賴這個jar包,這樣編譯的時候就只有一份。
    aar衝突比較麻煩了,因為aar中可能會包含jar檔案,這樣的話就需要檢視官方文件或者自己解壓縮檔案去檢視這個aar裡面究竟包含著那些jar檔案,利用 exclude group:"", module:"" 語句來排除jar被編譯進aar包中。例如:
    排除v4的jar檔案

  • 最後如果是資原始檔衝突,最常見的的錯誤就是:

no resource id found

或者是

java.lang.NoSuchFieldError: No static field xxx of 
type I in class Lcom/XX/R$id; or its superclasses

要麼就是多個module的資原始檔重名,導致相互覆蓋,那麼就找不到指定的資源,要麼就是指定的控制元件ID重名,多個重名控制元件導致找不到控制元件,解決辦法也很簡單就是改檔案或者控制元件名字。

比如說我在APICLOUD雲平臺編譯的時候出現以下錯誤,

 What went wrong: 
 Execution failed for task ':app:transformClassesWithJarMergingForRelease'
 .> com.android.build.api.transform.TransformException: java.util.zip.ZipException: duplicate entry: android/support/v4/app/BaseFragmentActivityDonut.class

意思就是class檔案重複,原因就是apicloud的框架本身自帶v4包,而我的module也有v4包,在引入我的自定義模組後,打包的時候就會出現相同的兩個class檔案,這樣就出現了以上錯誤,解決的辦法,上面已經說了使用exclude語法,將相應的部分排除,這個方法只能排除網路庫,本地的還沒發現能夠有效起到作用,因此,我在想,既然apicloud框架本身自帶v4的class檔案,而我自定義模組打包成aar檔案又不會把v4.aar打包進去,還是要手動將v4.aar放到和module.aar同目錄下,那麼,我可以不將v4.aar放入,而是使用apicloud框架自帶的v4.aar,這樣就巧妙的將重複問題解決了。記錄下這個問題。

新版gradle的新語法:

Gradle 3.4 引入了新的依賴配置,

新增
api 和 implementation 來代替 compile 依賴配置。
其中 api 和以前的 compile 依賴配置是一樣的。
使用 implementation 依賴配置,會顯著提升構建時間,因為implementation只會依賴直接引用的資源,而資源本身所依賴的資源是不會去深度依賴,這樣就減少了,依賴編譯所消耗的時間。
比如A依賴B ,B依賴C 。A implementation B,只會將B加入編譯。而 A api B或者A compile B就不僅僅只編譯B也會將C加入編譯。
所以在使用implementation 依賴的時候,不要驚訝找不到類,找不到資源之類的事情,因為他的原理就是不深度依賴。要麼使用api(在高版本建議使用這個)和complie(低版本)。要麼自己手動在A中將所需要的資源都依賴過來。
implementation和api的區別還有就是在,moduleB中如果使用implementation來引用網路或者本地資源,moduleA 依賴 moduleB這樣寫的話,還是找不到,如果moduleB api來依賴網路或者本地資源,那麼這些資源就可以被moduleA找到,並使用。

新增
compileOnly provided gradle 新增依賴到編譯路徑,編譯時使用。(不會打包到APK)
runtimeOnly apk gradle 新增依賴只打包到 APK,執行時使用。(不會新增到編譯路徑)

經常會出現,merge task失敗的問題 ,Task [App] not found in root project [project_name] 這種問題,一般可以去查詢aar資源包內是否有重複的jar依賴,導致合併編譯出來的jar失敗,但是aar重複並不會導致這個問題,不知道谷歌是哪根經出了問題。