Android 開發過程中,我們不可避免地需要引入其他人的工作成果。減少重復“造輪子”的時間,投入到更有意義的核心任務當中。
Android 庫模塊在結構上與 Android 應用模塊相同。提供構建應用所需的一切內容,包括源代碼(src
)、資源文件(res
)和 Android 清單文件(AndroidManifest.xml
)。
Android Studio IDE 提供選項創建庫模塊:
- 在項目中創建一個新的庫模塊(New Module)
- 將應用模塊轉換為庫模塊(因兩者結構基本相同)
如果現有的應用模塊包含希望重用的所有代碼,可以通過修改 build.gradle
文件:
// apply plugin: 'com.android.application'
apply plugin: 'com.android.library'
- Android 庫模塊編譯產物為 AAR,需要作為其他應用模塊依賴項使用。
- Android 應用模塊編譯產物為 APK,設備上可以直接運行。
Android AAR 類似 Java JAR,除了類文件還可以包含 Android 資源和一個清單配置文件(AndroidManifest.xml
)。
導入本地外部模塊
導入本地的外部模塊(e.g. Project B b module)到當前主項目中(e.g. Project A)。
Project B b module 通常為庫模塊,我們需要在另一個 Project A 應用模塊中使用它。
Android Studio IDE 提供選項以依賴項形式來添加庫:
- 添加已編譯的 AAR(或 JAR)文件(Import .JAR/.AAR Package)
- 將庫模塊導入到您的項目中(Import Module)
兩者區別如下:
- 庫模塊導入方式,將會復制代碼到其他項目:
Project A 目錄下出現 Project B b module 的拷貝 - 庫模塊導入之后允許編輯庫代碼,但是修改只對當前項目生效:
Project A 目錄下修改 b module 不會影響到 Project B b module
在現實開發過程中,我們希望維護一個統一版本的庫模塊,這樣一來庫模塊的更新就會同步給所有依賴于它的項目:
Project A、Project C、Project D 都依賴于 Project B b module,庫模塊 b 的修改會同步到各個項目。
- 庫模塊導入方式顯然無法完成任務,因為其是通過拷貝方式導入。
- 添加已編譯的 AAR(或 JAR)文件可以完成任務,但是依然需要人工切換項目點選操作。
解決方案:
配置 gradle 通過本地相對路徑指定庫模塊文件夾,實現本地外部模塊導入。
打開主項目 settings.gradle
文件導入庫:
include ':my-library-module'
project(':my-library-module').projectDir = new File(settingsDir, '../my-library-module')
打開主項目應用模塊的 build.gradle
文件,并向 dependencies 塊中添加依賴:
dependencies {compile project(":my-library-module")
}
庫模塊開發注意事項
將庫模塊引用添加至您的 Android 應用模塊后,庫模塊會根據優先級的順序與應用模塊進行合并。
資源合并沖突
- 當庫模塊與應用模塊均定義了相同資源 ID,默認使用應用模塊的資源,e.g. @string/app_name
- 多個 AAR 庫之間發生資源 ID 沖突,根據依賴項列表順序,優先使用 dependencies 塊頂部模塊的資源
避免常用資源 ID 沖突的有效辦法,是在各個模塊中使用具有唯一性的前綴命名規范。
AndroidManifest 合并沖突
考慮到兼容性問題,應用模塊的 minSdkVersion
必須大于或等于庫定義的版本。
庫模塊中如若使用到僅高版本 SDK 支持的 API,將會導致應用模塊編譯失敗。
Android 在切換到 Gradle 作為構建系統之前,通過 Manifest 設置 minSdkVersion
,之后其值會被 build.gradle
文件中的值覆蓋。
Android 應用的 APK 文件中只能包含一個 AndroidManifest.xml
,不過 Android Studio 項目可以包含多個該文件(來自主應用模塊及各個庫模塊)。因此,在構建應用時,Gradle 構建會將所有清單文件(AndroidManifest.xml
)合并。清單文件按照優先級從低到高合并,遵循特定規則合并各個清單文件中的所有 XML 元素 。
清單文件優先級由高到低的順序:
- 清單文件構建變體
- 應用模塊的主清單文件
- 所包括庫中的清單文件
多個庫存在時,則其清單優先級與依賴順序即 dependencies 塊中的順序匹配。
Manifest merger failed 示例: android:theme
在多個 AndroidManifest.xml
被定義且值不同,造成合并沖突。
Project A 主項目 AndroidManifest.xml
<applicationandroid:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/AppTheme">
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
Project B b Library Module AndroidManifest.xml
<applicationandroid:theme="Theme.AppCompat.Light.DarkActionBar">
遇到 Manifest 沖突參考 Gradle Console 給出的錯誤日志和提示,解決沖突。
例如使用 tools:replace
方式避免屬性沖突,借助 tools 域名空間(xmlns:tools="http://schemas.android.com/tools"
)設置 Manifest 的合并優先級。明確表示合并時移除低優先級 library module 中的相關屬性,使用高優先級 application module 中定義的對應屬性內容。
Project A 主項目 AndroidManifest.xml
<applicationandroid:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/AppTheme"tools:replace="android:theme">
模塊依賴分析
考慮到多重嵌套依賴問題,Gradle 類似 Maven 支持傳遞依賴,即庫本身依賴于其他庫,由此需要解決依賴之間的版本問題。
復雜的依賴關系很可能導致重復引入包,例如:support-v4
、support-v7
包,從而發生沖突。
多個模塊之間存在相同依賴并且發生沖突,可以通過 exclude 語法過濾相同依賴:
// helloworld build.gradle
...
compile ('com.example.helloworld:my-library-module:1.0.0') {exclude group: 'com.android.support', module: 'support-v4'exclude group: 'com.android.support', module: 'support-v7'
}
上述方法是在主項目引入其他庫模塊時進行過濾依賴,作為庫模塊開發者我們也應該考慮到其他用戶的使用情況。
provided 語法在創建 Android 庫模塊時非常有用,將依賴項添加到編譯過程中,但不會添加到編譯輸出中。這樣一來減少最終 APK、AAR 產物大小,同時避免添加不必要依賴項。
注意:需要告知用戶此依賴項存在,由其如何決定引入依賴。
// my-library-module build.gradle
...
ext.supportLibVersion = '26.1.0'dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])provided "com.android.support:appcompat-v7:${supportLibVersion}"
}
Project A 依賴 a、b、c module,同時 a、b module 又依賴于 d module,且 a、b 各自依賴的 d module 版本(version)不一致,不同 version 的 d module 中 API 接口如若發生改變,Project A Build/Sync 將會失敗。 例如 version 1.0 中的方法 method1,在 version 2.0 被移除將會遇到 java.lang.NoSuchMethodError
。
建議盡可能保持依賴項 d module version 一致,或者使用不同 version 但是差異不大,起碼做到 API 能夠通用。
通過 ./gradlew dependencies
命令可以查看依賴關系,附加參數可以查看指定類型、模塊依賴關系:
./gradlew my-library-module:dependencies --configuration archives
archives - Configuration for archive artifacts.
+--- com.android.support:recyclerview-v7:26.1.0
| +--- com.android.support:support-annotations:26.1.0
| +--- com.android.support:support-compat:26.1.0
| | +--- com.android.support:support-annotations:26.1.0
| | \--- android.arch.lifecycle:runtime:1.0.0
| | +--- android.arch.lifecycle:common:1.0.0
| | \--- android.arch.core:common:1.0.0
| \--- com.android.support:support-core-ui:26.1.0
| +--- com.android.support:support-annotations:26.1.0
| \--- com.android.support:support-compat:26.1.0 (*)
另外 Android 項目可以使用 ./gradlew androidDependencies
。
另外,考慮到構建問題,庫模塊使用的 gradle 插件與應用模塊盡量保持一致。
Android Gradle Plugin 版本不一致可能會影響到依賴項配置語法:
New configuration | Deprecated configuration |
---|---|
implementation | compile |
api | compile |
compileOnly | provided |
runtimeOnly | apk |
引用
創建 Android 庫
Add build dependencies
合并多個清單文件