在我們開發(fā)安卓項目的時候,不會所有的功能都自己去造輪子,經(jīng)常要使用到各種的其他包,其中有谷歌給我們提供的各種support包,也有各種第三方的功能庫,有時候我們自己也會將一些功能封裝成包。這些包存在和導入的形式也多種多樣,有遠程倉庫的,有直接拷貝到本地的,jar包、aar包、so包等。所幸我們都可以在主工程和各個Module的build.gradle里進行統(tǒng)一管理。本文將在Android Studio3.0環(huán)境下來匯總下這些用法。
預備知識
先來看下Android Gradle plugin 3.0幾個引入依賴的方法:
Implementation
對于使用了該命令編譯的依賴,對該項目有依賴的項目將無法訪問到使用該命令編譯的依賴中的任何程序,也就是將該依賴隱藏在內部,而不對外部公開。
使用implementation會使編譯速度有所增快:比如我在一個library中使用implementation依賴了gson庫,然后我的主項目依賴了library,那么,我的主項目就無法訪問gson庫中的方法。這樣的好處是編譯速度會加快,我換了一個版本的Gson庫,但只要library的代碼不改動,就不會重新編譯主項目的代碼。
api
等同于compile指令
compileOnly
等同于provided,只在編譯時有效,不會參與打包,不會包含到apk文件中??梢杂脕斫鉀Q重復導入庫的沖突(下文會提到)。
遠程倉庫依賴
我們先來看下主工程下的build.gradle文件
buildscript { ?
? ? ? ?ext.kotlin_version = '1.1.51'? ?
????????repositories {? ? ? ?
????????????????google()? ? ? ?
????????????????jcenter()? ?
????????} ?
????????dependencies { ? ? ??
????????????????classpath 'com.android.tools.build:gradle:3.0.1'? ? ? ?
????????????????classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"? ? ? ?
????????????????// NOTE: Do not place your application dependencies here; they belong? ? ? ?
????????????????// in the individual module build.gradle files? ?
????????}
}
allprojects {? ?
????????repositories {? ? ? ?
????????????????google()? ? ? ?
????????????????jcenter()? ?
????????}
}
引入遠程倉庫依賴是很方便的,但在之前我們需要聲明遠程倉庫的地址。上面有兩個倉庫地址的聲明,一個在buildscript {},另一個在repositories {}??创a中系統(tǒng)給我們的注釋就知道:前者是gradle腳本自身執(zhí)行所需依賴(Gradle插件),后者是項目本身需要的依賴(普通代碼庫)。所以如果你沒有引入遠程的Gradle插件,那么就不用在buildscript {}下的dependencies下添加依賴。
再來看下幾種遠程依賴的添加方式:
implementation 'commons-lang:commons-lang:2.6'
implementation group: 'com.google.code.guice', name: 'guice', version: '1.0'
implementation('org.hibernate:hibernate:3.1'){? ? ? ?
????????//不同版本同時被依賴時,那么強制依賴這個版本的,默認false? ? ? ?
????????force = true ? ? ? ? ? ? ? ?
????????//exclude可以設置不編譯指定的模塊,有三種寫法:? ? ? ?
????????exclude module: 'cglib'? ? ? ?
????????exclude group: 'org.jmock'? ? ? ?
????????exclude group: 'org.unwanted', module: 'iAmBuggy'? ? ? ?
????????//禁止依賴的傳遞,gradle自動添加子依賴項(依賴包所需的依賴),設置為false,則 ? 需要手動添加每個子依賴項,默認為true。? ? ? ?
????????transitive = false? ?
}
同樣的配置下的版本沖突,會自動使用最新版;而不同配置下的版本沖突,gradle同步時會直接報錯??墒褂胑xclude、force解決沖突。
比如你同時依賴了兩個版本的v7包:
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support:appcompat-v7:23.1.1'
最終只會使用26.1.0版本。但是如implementation 'com.android.support:appcompat-v7:23.1.1',和androidTestImplementation 'com.android.support.test.espresso:espresso-core:2.1',所依賴的com.android.support:support-annotations版本不同,就會導致沖突。除了可以用exclude、force解決外,也可以自己統(tǒng)一為所有依賴指定support包的版本,不需要為每個依賴單獨排除了:
configurations.all {? ?
????????resolutionStrategy.eachDependency { DependencyResolveDetails details ->? ? ? ?
????????????????def requested = details.requested? ? ? ?
????????????????if (requested.group == 'com.android.support') {? ? ? ? ? ?
????????????????????????if (!requested.name.startsWith("multidex")) {? ? ? ? ? ? ? ?
????????????????????????????????details.useVersion '26.1.0'? ? ? ? ? ?
????????????????????????????}? ? ? ?
????????????????????}? ?
? ? ? ? ?}
}
編譯期注解的依賴--annotationProcessor
用過butterknife或者Dagger的同學可能對這種annotationProcessor引入方式有所印象,這種方式是只在編譯的時候執(zhí)行依賴的庫,但是庫最終不打包到apk中。結合編譯期注解的作用,他是用來生成代碼的,本身在運行時是不需要的。
本地依賴
jar包
jar包依賴的導入還是比較簡單的:
implementation files('hibernate.jar', 'libs/spring.jar')//列出每個jar包的相對路徑implementation fileTree(dir: 'libs', include: ['*.jar'])//列出包含jar包的文件夾路徑
但和遠程倉庫依賴引入方式不同,如果本地同時存在兩個不同的jar包,或者本地已有jar包,再去遠程依賴不同版本的jar包,就會報錯。
解決方式:將其中的一個采用compileOnly替換implementation。顧名思義,compileOnly只在編譯時起作用,不會包含到APK里面,在運行時也就避免找到重復的類了。
aar包
和jar包不同,aar包存放的路徑聲明和依賴引入是分開的:
repositories {? ?
????????flatDir {? ? ? ?
????????????????dir "../${project.name}/libs"? ?
????????}
}
dependencies {?? ?
????????implementation(name: 'aar名字', ext: 'aar')?
}
如果aar包有很多,也可以一樣象jar包統(tǒng)一添加一個文件夾下的所有包:
?def dir = new File('app/libs')? ?
dir.traverse(? ? ? ? ? ?
????????nameFilter: ~/.*\.aar/? ?
) {file ->? ? ? ?
????????def name = file.getName().replace('.aar', '')? ? ? ?
????????implementation(name: name, ext: 'aar')? ?
}
當一個library類型的module需要引用aar文件時,也要在所在模塊的build.gradle文件中加入上面的話,但是當其他 Module引用此library的module時,也需要在他的build.gradle中加入如下配置,否則會提示找不到文件:
repositories {?? ?
????????flatDir {?? ? ? ?
????????????????dirs 'libs', '../包含aar包的模塊名/libs'?? ?
????????}?
}?
即如果當前Module需要一個aar包內容,不論aar包是不是在當前Module中,都需要在build.gradle中聲明它所在的路徑。如果項目中這樣的Module比較多,每個都需要聲明路徑,不便于管理的話,推薦在項目的根build.gradle中統(tǒng)一添加,將所有包含aar包的模塊名列出,這樣不論是本Module或其他Module都不需要單獨配置路徑了:
allprojects {? ?
????????repositories {? ? ? ?
????????????????jcenter()? ? ? ?
????????????????google()? ? ? ?
????????????????flatDir {? ? ? ? ? ?
????????????????????????dirs "../moudle-A/libs,../moudle-B/libs,../moudle-C/libs".split(",")? ? ? ?
????????????????}? ?
????????}
}
so文件
這個和jar包差不多,聲明下so文件的存放路徑就行了:
sourceSets {? ? ? ? main {? ? ? ? ? ? jniLibs.srcDirs = ['libs']? ? ? ? }? ? }
或者直接在main目錄下新建jniLibs目錄,這是so文件默認的放置目錄,不過不常用。值得一提的是aar包里面也可以包含so文件,但依賴這種包含so文件的aar包時不需要做特定的配置,編譯時so文件會自動包含到引用AAR壓縮包的APK中。
但比較特殊的一點是,so文件需要放到具體的ABI目錄下,不能直接放libs目錄下所以你見到的結果可能是這樣的:

所有的x86/x86_64/armeabi-v7a/arm64-v8a設備都支持armeabi架構的so文件。所以為了減小包體積,為了減小 apk 體積,可以只保留 armeabi 一個文件夾。但如果你想引入多個平臺的,那么需要保持 so 文件的數(shù)量一致,就是說 armeabi 文件下的每個so文件都要在armeabi-v7a下找到對應的so文件,但這樣apk包的體積就會增大。
還有一種做法是生成指定ABI版本的APK,然后按需上傳到應用商店,讓用戶自己選擇下載適合自己手機的版本,這個可能更多的用在安卓游戲APP上,build.gradle配置如下:
android {? ? ...? ? splits {? ? ? ? abi {? ? ? ? ? ?
????????enable true? //啟用ABI拆分機制? ? ? ? ? ?
????????reset()? //重置ABI列表為只包含一個空字符串? ? ? ? ? ?
????????include 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
????????//與include一起使用來可以表示要使用哪一個ABI? ? ? ? ? ?
????????universalApk? ? ? ? ? ?
????????true//是否打包一個通用版本(包含所有的ABI)。默認值為 false。? ? ? ?
????????}? ? }? ?
????????// ABI的code碼? ?
project.ext.versionCodes = ['armeabi': 1, 'armeabi-v7a': 2, 'arm64-v8a': 3, 'mips': 5, 'mips64': 6, 'x86': 8, 'x86_64': 9]? ?
android.applicationVariants.all { variant ->? ? ? ?
????????// 最終標記? ? ? ?
????????variant.outputs.each { output ->? ? ? ? ? ?
????????output.versionCodeOverride =? ? ? ? ? ? ? ? ? ? ????????project.ext.versionCodes.get(output.getFilter(com.android.build.OutputFile.ABI), 0) * 1000000 + android.defaultConfig.versionCode? ? ? ?
}? ? }}
問題和小結
1.aar包中的資源文件重復了
資源文件重復了,主工程的資源文件會直接覆蓋aar包中的文件,并且不會有任何報錯或者提示,最終aar包中也會直接用主工程的資源文件,所以需要注意命名方式。暫時沒有更好的解決方法。
2.AndroidManifest合并錯誤
同樣也是發(fā)生在aar包上, Android Studio 項目每個module中都可以有一個AndroidManifest.xml文件,但最終的APK 文件只能包含一個 AndroidManifest.xml 文件。在構建應用時,Gradle 構建會將所有清單文件合并到一個封裝到 APK 的清單文件中。aar包的清單文件和我們的app清單文件屬性沖突時:用tools:replace="屬性名"解決。
3.annotationProcessor與compileOnly的區(qū)別
上文說了annotationProcessor與compileOnly都是只編譯并不打入apk中,他倆到底有什么區(qū)別呢?扮演的角色不一樣,annotationProcessor作用是編譯時生成代碼,編譯完真的就不需要了,compileOnly是有重復的庫,為的是剃除只保留一個庫,最終還是需要的。