Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Gradle實際應用(三):發布aar包到maven倉庫

Gradle實際應用(三):發布aar包到maven倉庫

編輯:關於Android編程

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;
}
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved