【Gradle】Android Gradle 高級自定義


個人博客:
http://www.milovetingting.cn

Android Gradle 高級自定義

使用共享庫

Android的包,如android.app,android.content,android.view,android.widget等,是默認包含在Android SDK庫里的,所有應用都可以直接使用它們。還有一些庫,如com.google.android.maps,android.test.runner等,這些庫是獨立的,並不會被系統自動鏈接,所以如果要使用的話,就需要單獨進行生成使用,這類庫我們稱為共享庫。

在AndroidManifest.xml中,我們可以指定要使用的庫:

<uses-library 
android:name="com.google.android.maps"
android:required="true"
/>

這樣我們就聲明了需要使用maps這個共享庫。聲明之后,在安裝生成的apk包的時候,系統會根據我們的定義,幫助檢測手機系統是否有我們需要的共享庫。因為我們設置的android:required="true",如果手機系統不滿足,將不能安裝該應用。

在Android中,除了標准的SDK,還存在兩種庫:一種是add-ons庫,它們位於add-ons目錄下,這些庫大部分是第三方廠商或者公司開發的,一般是為了開發者使用,但又不想暴露具體標准實現;第二類是optional可選庫,它們位於platforms/androi-xx/optional目錄下,一般是為了兼容舊版本的API,比如org.apache.http.legacy。

對於第一類add-ons附件庫來說,Android Gradle會自動解析,幫我們添加到classpath里。第二類optional可選庫就不會,需要自己將這個可選庫添加到classpath中。Android Gradle提供了useLibrary方法,讓我們把一個庫添加到classpath中。

android{
    useLibrary 'org.apache.http.legacy'
}

以上的配置已經可以生成APK,並能安裝運行。但最好也要在AndroidManifest文件中配置一下uses-library標簽,以防出現問題。

批量修改生成的apk文件名

Android對象為我們提供了3個屬性:applicationVariants(僅僅適用於Android應用Gradle插件),libraryVariants(僅僅適用於Android庫Grdle插件),testVariants(以上兩種Gradle插件都適用)。

以上3個屬性返回的都是DomainObjectSet對象集合,訪問它們都會觸發創建所有的任務。

以下為批量修改apk名稱的示例:

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.wangyz.gradle"
        minSdkVersion 21
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        flavorDimensions "default"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    productFlavors {
        baidu {

        }
        huawei {

        }
    }
    applicationVariants.all {
        variant ->
            variant.outputs.all {
                output ->
                    if (output.outputFile != null && output.outputFile.name.endsWith('.apk') && 'release'.equals(variant.buildType.name)) {
                        def flavorName = variant.flavorName.startsWith("_") ? variant.flavorName.substring(1) : variant.flavorName
                        def fileName = "channel_${flavorName}_${variant.versionName}.apk"
                        outputFileName = fileName
                    }
            }
    }
}

動態生成版本信息

一般的版本由3部分組成:major.minor.patch,第一個是主版本號,第二個是副版本號,第三個是補丁號。

最原始的方式

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.wangyz.channel"
        minSdkVersion 21
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
}

分模塊的方式

可以把版本號的配置單獨抽取出來,放在單獨的文件里,供build引用。

新建一個vesion.gradle文件:

ext{
    appVersionCode = 1
    appVersionName = "1.0.0"
}

在build.gradle中引用它:


apply from:'version.gradle'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.wangyz.channel"
        minSdkVersion 21
        targetSdkVersion 28
        versionCode appVersionCode
        versionName appVersionName
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
}

先使用apply from加載version.gradle腳本文件,這樣它里面定義的擴展屬性就可以使用了。

從git的tag中讀取

想獲取當前的tag名稱,在git下非常簡單,使用以下命令即可:

git describe --abbrev=0 --tags

知道了命令,那么如何在Gradle中動態獲取呢?這就需要exec。Gradle提供了執行shell命令非常簡便的方法,即exec。它是一個Task任務。

def getAppVersionName(){
    def stdout = new ByteArrayOutputStream()
    exec{
        commandLine 'git','describe','--abbrev=0','--tags'
        standardOutput = stdout
    }
    return stdout.toString()
}

以上定義了一個獲取版本名稱的方法,通過該方法獲取了git tag的名稱后,就可以把它作為應用的版本名稱,只要把versionName配置成這個方法就好了。

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.wangyz.channel"
        minSdkVersion 21
        targetSdkVersion 28
        versionCode 1
        versionName getAppVersionName()
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
}

從屬性文件中動態獲取和遞增

1、在項目目錄下新建一個version.properties的屬性文件

2、把版本名稱分為3部分major.minor.patch,版本號分為一部分number,然后在properties新增4個KV鍵值對

3、在build.gradle新建一個方法用於讀取該屬性文件

隱藏簽名文件信息

定義一個文件,用來保存簽名的相關信息,如sign.properties,這個文件加入.gitignore,不上傳到git中。通過讀取這個文件來獲取配置信息。

android{
    signingConfigs{
        release{
            def signPropertiesFile = 'sign.properties'
            storeFile file(readSignProperties(signPropertiesFile,'storeFile'))
            storePassword readSignProperties(signPropertiesFile,'storePassword')
            keyAlias readSignProperties(signPropertiesFile,'keyAlias')
            keyPassword readSignProperties(signPropertiesFile,'keyPassword')
        }
    }
}

buildTypes{
    release{
        signingConfig signingConfigs.release
    }
}

def readSignProperties(String filePath,String key){
    File file = file(filePath)
    if(file.exists())
    {
        def Properties properties = new Properties()
        properties.load(new FileInputStream(file))
        return properties[key]
    }
    return "file not exist!"
}

sign.properties內容如下:

storeFile=android.keystore
storePassword=android
keyAlias=android
keyPassword=android

動態配置AndroidManifest文件

Android Gradle 提供了非常便捷的方法讓我們來替換AndroidManifest文件中的內容,它就是manifestPlaceholder,Manifest占位符。

android{
    productFlavors{
        google{
            manifestPlaceholders = [APP_CHANNEL:"google"]
        }
        baidu{
            manifestPlaceholders = [APP_CHANNEL:"baidu"]
        }
    }
}

在AndroidManifest文件中使用

<application>
<meta-data 
android:name="APP_CHANNEL"
android:value="${APP_CHANNEL}"
/>
</application>

如果需要批量修改(假設需要將名稱改為和渠道名一樣),可以通過productFlavors迭代方法:

android{
    productFlavors{
        google{

        }
        baidu{

        }
    }

    productFlavors.all{
        flavor->
        manifestPlaceholder = [APP_CHANNEL:name]
    }
}

自定義BuildConfig

Android Gradle 提供了buildConfigField(String type,String name,String value)讓我們添加自己的常量到BuildConfig中。使用示例:

android{
    productFlavors{
        google{
            buildConfigField 'String','URL','"http://www.google.com"'
        }
        baidu{
            buildConfigField 'String','URL','"http://www.baidu.com"'
        }
    }
}

需要注意,value這個參數,是單引號中間的部分,尤其對於String類型的值,里面的雙引號不能省略。value是什么就寫什么,原封不動地放在單引號里。

上面是渠道,productFlavor,其實不光渠道可以配置自定義字段,構建類型BuildType也可以配置。如:

android{
    buildTypes{
        debug{
            buildConfigField 'String','NAME','"zhangsan"'
        }
    }
}

動態添加自定義的資源

實現這一功能的方法是resValue方法。它在BuildType和ProductFlavor這兩個對象中存在。它會生成一個資源,效果和在res/values文件中定義一個資源是等價的。

resValue方法有三個參數,第一個是type,也就是你要定義資源的類型,比如有string,id,bool等;第二個是name,也就是定義資源的名稱,以便在工程中引用;第三個是value,就是定義資源的值。

android{
    productFlavors{
        google{
            resValue 'string','tip','hello'
        }
        baidu{
            resValue 'string','tip','hi'
        }
    }
}

Java編譯選項

Android對象提供了一個compileOptions方法,接受一個CompileOptions類型的閉包作為參數,來對Java編譯選項進行配置:

android{
    compileOptions{
        encoding 'utf-8'
        sourceCompatibility JavaVersion.VERSION_1_6
        targetCompatibility JavaVersion.VERSION_1_6
    }
}

CompileOptions是編譯配置,它提供三個屬性,分別是encoding,sourceCompatibility,targetCompatibility,通過對它們進行設置來配置Java相關的編譯選項。

sourceCompatibility是配置Java源代碼的編譯級別

targetCompatibility是配置生成的Java字節碼的版本

adb操作選項配置

在Android Gradle 中,為我們預留了對adb的一些選項的控制配置,它就是adbOptions{}閉包。

android{
    adbOptions{
        timeOutInMs 5*1000
        installOptions '-r','-s'
    }
}

DEX選項配置

Android Gradle 提供了dexOptions{}閉包,讓我們可以對dx操作進行一些配置。

android{
    dexOptions{
        incremental true
    }
}

突破65535方法限制

Java源文件被打包成一個DEX文件,這個文件就是優化過的、Dalvik虛擬機可執行的文件,Dalvik虛擬機在執行DEX文件時,使用了short類型來索引DEX文件中的方法,這就意味着單個DEX文件可以被定義的方法最多只有是65535,當定義的方法數量超過時,就會出錯。

Android官方給出的解決方案:Multidex。對於Android5.0以后的版本,使用了ART的運行方式,可以天然支持App有多個DEX文件,ART在安裝App的時候執行預編譯,把多個DEX文件合並成一個oat文件執行。對於Android5.0之前的版本,Dalvik虛擬機限制每個App只能有一個class.dex,要使用它們,就得使用Android提供的Multidex庫。

要在項目中使用Multidex,首先要修改Gradle build配置文件,啟用Multidex,並同時配置Multidex需要的jar依賴。

android{
    defaultConfig{
        multiDexEnabled true
    }
}

dependencies{
    implementation 'com.android.support:multidex:1.0.1'
}

配置好之后,開啟了Multidex,會讓我們的方法多於65535個的時候生成多個DEX文件,名字為classes.dex,classes(...n)這樣的形式。但是對於Android5.0以前的系統虛擬機,它只認識一個DEX,名字還是classes.dex,所以想達到程序可以正常運行的目的,也要讓虛擬機把其它幾個生成的classes加載進來。要做到這步,就必須在App程序啟動的入口控制,這個入口就是Application。

Multidex提供現成的Application,名字是MultiDexApplication,如果我們沒有自定義Applicaiton的話,直接使用MultiDexApplication即可,在Manifest清單文件中配置:

<application 
...
android:name="android.support.multidex.MultiDexApplication"
>
...
</application>

如果有自定義的Application,並且是直接繼承自Application,那么只需要把繼承改為我們的MultiDexApplication即可。

如果自定義的Application是繼承自第三方提供的Application,就不能改繼承了,這個時候可以重寫attachBaseContext方法來實現:

@Override
protected void attachBaseContext(Context base){
    super.attachBaseContext(base);
    MultiDex.install(this);
}

雖然有了解決65535問題的方法,但還是要盡量避免我們工程中的方法超過65535.首先不能濫用第三方庫,如果引用,最好也要自己精簡。精簡后,還要使用ProGuard減小DEX的大小。還有因為Dalvik linearAlloc的限制,尤其在2.2和2.3版本上,只有5MB,到Android 4.0的時候升級到8MB,所以低於4.0的系統上dexopt的時候可能會崩潰。

自動清理未使用的資源

Android Gradle 為我們提供了在構建打包時自動清理未使用的資源的方法,這個就是Resource Shrinking。

Resource Shringking要結合Code Shringking一起使用,即我們開發中經常使用的ProGuard,也就是我們要啟用minifyEnabled,是為了減縮代碼。

android{
    buildTypes{
        release{
            minifyEnabled true
            shringkResources true
            ...
        }
    }
}

自動清理未使用的資源這個功能雖然好用,但是有時候會誤刪有用的程序,因為我們在代碼編寫的時候,可能會使用反射去引用資源,尤其很多的第三方庫會這么做,這個時候Android Gradle就區分不出來,可能會誤認為這些資源不有被使用。針對這種情況,Android Gradle提供了keep方法來讓我們配置哪些資源不被清理。

keep方法使用非常簡單,我們要新建一個xml文件來配置,這個文件是res/raw/keep.xml,然后通過tools:keep屬性來設置。這個tools:keep接受一個以逗號分隔的配置資源列表,並且支持星號*通配符。

<?xml version="1.0" encoding="utf-8">
<resources xmlns:tools="http://schemas.android.com/tools" tools:keep="@layout/layout_a,@layout/layout_b" />

keep.xml還有一個屬性是tools:shrinkMode,用於配置自動清理資源的模式,默認是false,是安全的。

除了shrinkResources之外,Android Gradle 還提供了一個resConfigs,它屬於ProductFlavor的一個方法,可以讓我們配置哪些類型的資源才會被打包進APK中。

android{
    defaultConfig{
        resConfigs 'zh'
    }
}

上面代碼表示,只保留zh資源。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM