天天看點

Gradle實戰:釋出aar包到maven倉庫

檢視原文:http://blog.csdn.net/u010818425/article/details/52441711

    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;
    }
           

深入學習

    同步aar到jCenter與maven central

    Android Studio使用Gradle上傳AAR至Maven

    aar無法設定debug問題解決參考

檢視原文:http://blog.csdn.net/u010818425/article/details/52441711

繼續閱讀