檢視原文:http://blog.csdn.net/u0108184…
Gradle實戰系列文章:
《Gradle基本知識點與常用配置》
《Gradle實戰:Android多渠道打包方案彙總》
《Gradle實戰:不同編譯型別的包同裝置共存》
《Gradle實戰:執行sql操作hive資料庫》
aar簡介
aar檔案是Google為Android開發所設計的一種library格式,全名為Android Archive Library,與Java Jar Library不同的是,aar除了java code之外還包含資原始檔,即xml檔案、圖片、文字等。
本文著重介紹釋出過程和遇到的一些坑及其解決方案,文中的maven倉庫是指公司搭建的maven倉庫,如果要釋出到jCenter或maven central,可以參考文章最後的“深入學習“。
1. 準備工作
-
開發工具:Android Studio;
-
複習《Gradle基本知識點與常用配置》,本文會用到gradle中全域性屬性設定、檔案讀取、shell指令執行等相關知識點;
-
工程必須是lib工程,即該工程對應的build.gradle檔案中要引用:
apply plugin: `com.android.library`
-
在根目錄的build.gradle檔案中新增
allprojects {
apply plugin: `idea` apply plugin: `maven` configurations { deployerJars }
}
configurations.all {
resolutionStrategy.cacheChangingModulesFor 0, `seconds`//不使用快取,使用倉庫中最新的包
}
subprojects { //表示除主工程外所有子模組
dependencies { deployerJars "org.apache.maven.wagon:wagon-http:2.2" }
}
ext { //倉庫選擇標記
repoType = "remote" //釋出到遠端倉庫(下文中會用到)
// repoType = “local” //釋出到本地倉庫,方便除錯,避免除錯期間頻繁上傳到maven倉庫(下文中會用到)
} -
在gradle.properties檔案中新增:
releaseRepositoryUrl=xxx //正式包倉庫地址(下文中會用到)
snapshotRepositoryUrl=xxx //測試包倉庫地址(下文中會用到)
repositoryGroup=com.company.appname // 定義要上傳的aar所在倉庫的Group,可自定義,但後續引用處要與此一致 -
在工程根目錄下新建一個名為“mavenAccount.properties”檔案,並將該檔案加入到ignore 中,該檔案用於存放訪問maven倉庫的賬戶和密碼以及本地倉庫地址,只有該模組的開發者才有權釋出該aar包。
repositoryUserName=xxx
repositoryPassword=xxx
localRepositoryUrl=file:///Users/admin/Documents/Android/repo/
2. 編寫上傳指令碼
-
生成aar包
> 在工程根目錄下新建一個名為“release-as-aar.gradle”的檔案,其中指令碼如下:
uploadArchives() {
repositories { mavenDeployer { configuration = configurations.deployerJars println `repoType : ` + rootProject.ext.repoType if ((rootProject.ext.repoType).equals("remote")) { //釋出到遠端倉庫 snapshotRepository(url: snapshotRepositoryUrl) { // 測試包 //從本地檔案讀取倉庫賬號和密碼 def File propFile = new File(`../mavenAccount.properties`) if (propFile.canRead()) { def Properties props = new Properties() props.load(new FileInputStream(propFile)) if (props != null && props.containsKey(`repositoryUserName`) && props.containsKey(`repositoryPassword`)) { def repositoryUserName = props[`repositoryUserName`] def repositoryPassword = props[`repositoryPassword`] authentication(userName: repositoryUserName, password: repositoryPassword) println `上傳到遠端倉庫` } else { println `沒有釋出許可權` } } else { println `沒有釋出許可權` } } repository(url: releaseRepositoryUrl) { // 正式包 def File propFile = new File(`../mavenAccount.properties`) if (propFile.canRead()) { def Properties props = new Properties() props.load(new FileInputStream(propFile)) if (props != null && props.containsKey(`repositoryUserName`) && props.containsKey(`repositoryPassword`)) { def repositoryUserName = props[`repositoryUserName`] def repositoryPassword = props[`repositoryPassword`] authentication(userName: repositoryUserName, password: repositoryPassword) println `上傳到遠端倉庫` } else { println `沒有釋出許可權` } } else { println `沒有釋出許可權` } } } else { // 釋出到本地倉庫 def localRepositoryUrl def File propFile = new File(`../mavenAccount.properties`) if (propFile.canRead()) { def Properties props = new Properties() props.load(new FileInputStream(propFile)) if (props != null && props.containsKey(`localRepositoryUrl`)) { localRepositoryUrl = props[`localRepositoryUrl`] snapshotRepository(url: localRepositoryUrl) repository(url: localRepositoryUrl) println `上傳到本地倉庫` } else { println `沒有釋出許可權` } } else { println `沒有釋出許可權` } } } }
}
-
生成jar包
> 在工程根目錄下新建一個名為“release-as-jar.gradle”的檔案,其中指令碼如下:
task androidJavadocs(type: Javadoc) {
failOnError = false source = android.sourceSets.main.java.srcDirs ext.androidJar = "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar" classpath += files(ext.androidJar)
}
task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
classifier = `javadoc` from androidJavadocs.destinationDir
}
task androidSourcesJar(type: Jar) {
classifier = `sources` from android.sourceSets.main.java.srcDirs
}
uploadArchives {
repositories { mavenDeployer { configuration = configurations.deployerJars println `repoType : ` + rootProject.ext.repoType if ((rootProject.ext.repoType).equals("remote")) { //釋出到遠端倉庫 snapshotRepository(url: snapshotRepositoryUrl) { def File propFile = new File(`../mavenAccount.properties`) if (propFile.canRead()) { def Properties props = new Properties() props.load(new FileInputStream(propFile)) if (props != null && props.containsKey(`repositoryUserName`) && props.containsKey(`repositoryPassword`)) { def repositoryUserName = props[`repositoryUserName`] def repositoryPassword = props[`repositoryPassword`] authentication(userName: repositoryUserName, password: repositoryPassword) println `上傳到遠端倉庫` } else { println `sorry,你沒有上傳aar包的許可權` } } else { println `sorry,你沒有上傳aar包的許可權` } } repository(url: releaseRepositoryUrl) { def File propFile = new File(`../mavenAccount.properties`) if (propFile.canRead()) { def Properties props = new Properties() props.load(new FileInputStream(propFile)) if (props != null && props.containsKey(`repositoryUserName`) && props.containsKey(`repositoryPassword`)) { def repositoryUserName = props[`repositoryUserName`] def repositoryPassword = props[`repositoryPassword`] authentication(userName: repositoryUserName, password: repositoryPassword) println `上傳到遠端倉庫` } else { println `sorry,你沒有上傳aar包的許可權` } } else { println `sorry,你沒有上傳aar包的許可權` } } } else {//釋出到本地倉庫 def localRepositoryUrl def File propFile = new File(`../mavenAccount.properties`) if (propFile.canRead()) { def Properties props = new Properties() props.load(new FileInputStream(propFile)) if (props != null && props.containsKey(`localRepositoryUrl`)) { localRepositoryUrl = props[`localRepositoryUrl`] snapshotRepository(url: localRepositoryUrl) repository(url: localRepositoryUrl) println `上傳到本地倉庫` } else { println `sorry,本地倉庫路徑不存在` } } else { println `sorry,本地倉庫路徑不存在` } } } }
}
artifacts {
archives androidSourcesJar archives androidJavadocsJar
}
3. 子模組中相關配置
-
在子模組的build.gradle檔案中新增:
group repositoryGroup
//version `0.0.1`
version `0.0.1-SNAPSHOT` //表示測試版,正式發版時去掉“-SNAPSHOT”//打成aar格式
apply from: `../release-as-aar.gradle` //引用上傳外掛//打成jar格式
//apply from: `../release-as-jar.gradle`
4. 打包上傳
-
編譯通過後,開啟android studio自帶的終端,進入相應的module目錄下,輸入:
gradle uploadArchives
5. 使用aar
-
在需要引用aar包的工程中,根目錄的build.gradle檔案中進行如下配置:
allprojects {
repositories {
// jcenter(); //註釋jcenter,表示不直接從jcenter倉庫獲取,而是通過公司私服倉庫去獲取
maven { name `xxx` //key與value之間有空格 url `xxx` //key與value之間有空格 } mavenLocal(); }
}
-
在子模組的build.gradle檔案中進行如下引用:
dependencies {
compile group: repositoryGroup, name: `xxx`, version: `0.0.1`, ext: `aar`, changing: true
}
6. 踩到的坑
-
問題一:上傳時找不到伺服器
上傳時需關閉android studio的翻牆代理設定,且註釋settings.gradle中自動生成的代理伺服器相關配置,否則上傳時會報找不到倉庫伺服器的錯誤。
-
問題二:aar包無法更新
有時上傳了最新的snapshot包,引用的地方也sync、clean了,但引用的還是舊的包,此時需要刪除“~/.gradle”中的相關記錄。為方便執行,我們可以在應用工程根目錄的build.gradle檔案中,採用shell命令刪除,該命令會在你執行clean操作時先執行:
task deleteDescriptors(type: Exec) { //執行shell命令 executable "sh" args "-c", "rm -rf ~/.gradle/caches/modules-2/metadata-2.16/descriptors/com.company.appname" //此處的“com.company.appname“就是之前定義的“repositoryGroup“。 } task clean(type: Delete, dependsOn: deleteDescriptors) { //clean工程時順帶執行上述任務 delete rootProject.buildDir }
此時,再clean一下,引用的就是最新的aar包了。
-
問題三:無法設定debug編譯型別
在lib工程中無論怎麼設定編譯型別,最後生成的aar包中始終都是release版本,該問題見google反饋。既然不可設定編譯型別,我們可以在aar包程式碼中通過反射來獲取應用的編譯型別:
private Object getBuildConfigValue(Context context, String fieldName) { try { Class<?> clazz = Class.forName(context.getPackageName() + ".BuildConfig"); Field field = clazz.getField(fieldName); return field.get(null); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } return null; } //使用 String buildType = getBuildConfigValue(ctx,"BUILD_TYPE").toString(); if (!TextUtils.isEmpty(buildType) && buildType.equals("debug")) { // debug ... } else { // release ... }
但是,這裡面還有一個坑,系統版本在4.4以下的裝置中,該方法無法獲得包名,會拋空指標錯誤。以下我們給出完整的解決方案:
public class BuildConfigProvider { private static Context sContext; private static String packageName; public static String getBuildType() { String buildType = (String) getBuildConfigValue("BUILD_TYPE"); if ("debug".equals(buildType)) { buildType = "debug"; } if ("release".equals(buildType)) { buildType = "release"; } return buildType; } public static final boolean isDebug() { return BuildConfig.DEBUG; } /** * 通過反射獲取ApplicationContext * * @return */ private static Context getContext() { if (sContext == null) { try { final Class<?> activityThreadClass = Class.forName("android.app.ActivityThread"); final Method currentActivityThread = activityThreadClass.getDeclaredMethod("currentActivityThread"); final Object activityThread = currentActivityThread.invoke(null); final Method getApplication = activityThreadClass.getDeclaredMethod("getApplication"); final Application application = (Application) getApplication.invoke(activityThread); sContext = application.getApplicationContext(); } catch (Exception e) { e.printStackTrace(); } } return sContext; } /** * 通過反射獲取包名 * * @return */ private static String getPackageName() { if (packageName == null) { try { final Class<?> activityThreadClass = Class.forName("android.app.ActivityThread"); final Method currentPackageName = activityThreadClass.getDeclaredMethod("currentPackageName"); packageName = (String) currentPackageName.invoke(null); } catch (Exception e) { packageName = getContext().getPackageName(); } } return packageName; } public static Object getBuildConfigValue(String fieldName) { try { Class<?> clazz = Class.forName(packageName + ".BuildConfig"); Field field = clazz.getField(fieldName); return field.get(null); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IndexOutOfBoundsException e) { e.printStackTrace(); } return ""; } }
當然,有人可能會說,既然可以通過反射得到
ApplicationContext
,就沒必要再去反射獲得包名了,這裡只是提供不同的解決方案以作參考。
-
問題四:多包共存模式下獲得編譯型別為空
在上一篇部落格《 Gradle實際應用(二):同名包共存》中,我們可以在一個裝置中安裝同一個應用不同編譯型別的包。但是,非release包中我們獲得的包名是帶有編譯型別字尾的(如“com.company.appname.debug“),而編譯型別我們是通過反射獲取,“BuildConfig“所在的包名還是原始的、不加字尾的包名(如“com.company.appname“),此時我們拿到的編譯型別為空,那麼我們可以在獲取包名後做一個檢查:
private static String checkPackageName(String packageName) { String[] temp = packageName.split("\."); String sub = temp[temp.length - 1]; //如果多包共存模式,剔除包名中的字尾 if (sub.equals("debug")) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < temp.length - 1; i++) { sb.append(temp[i]); if (i != temp.length - 2) { sb.append("."); } } packageName = sb.toString(); } return packageName; }