《Gradle 權威指南》讀書筆記——第九章 Android Gradle 高級自定義

此章教隱藏證書
批量修改生成的apk文件名
突破65535方法限制

使用共享庫
Android的包(比如android.app android.content android.view android.widge等)是默認就包含在Android SDK中的,系統會幫我們自動鏈接它們;
但有些庫是需要我們去AndroidManifest.xml中配置後才能使用(如com.google.android.maps android.test.runner)等,需要單獨去生成,這些庫被稱爲共享庫

//聲明需要使用共享庫後,在安裝時需要手機系統沒有該共享庫,那麼該應用不能被安裝
<uses-library
    android:name="com.google.android.maps"
    android:required="true"/>

三種共享庫:

  • 標準的AndroidSDK,

  • add-ons庫:
    位於add-ons目錄下,一般是第三方公司開發的,爲了讓開發者們使用但又不想暴露具體實現;
    AndroidGradle會自動解析,添加到classpath中,
    add-ons文件夾該目錄中存放 Android 的擴展庫,比如 Google Maps,但若未選擇安裝 Google API,則該目錄爲空。

  • optional庫
    位於platforms/android-xx/optional目錄下,一般是爲了兼容舊版本.(如org.apache.http.legacy是httpClient庫,api23後sdk移除了該庫,如需要則必須使用可選庫)
    不會自動解析並添加到classpath中,所以需要我們手動解析

    //僅僅是爲了保證編譯通過
          //最好在AndroidManifest.xml中也要配置
          //PackageManager().getSystemSharedLibraryNames();
          android{
              useLibrary 'org.apache.http.legacy'
          }
    

批量修改生成的apk文件名稱
Andoird工程相對Java工程來說,要複雜的多,因爲它有很多相同的任務,這些任務的名稱是通過BuildTypes和ProductFlavors動態創建和生成的(通過project.tasks無法獲取任務,因爲還無生成).

爲了解決這個問題,Android對象提供了三個屬性,這三個屬性都是DomainObjectSet對象集合

1.applicationVariants 僅適用於Android應用插件

2.libraryVariants 僅適用於Android庫Gradle插件

3.testVariants 以上兩種都適用

注意這三種集合都會觸發創建所有的任務,這以爲着訪問這些集合後不需要重新配置就會產生

public DomainObjectSet<ApplicationVariant> getApplicationVariants(){
    return applicationVariantList;
}

實現修改apk文件的需求

android{
    ...
    useLibrary 'org.apache.http.legacy'
    buildTypes{
        realeas{
        }
    }
    productFlavors{
        google{
        }
    }
    applicationVariants.all{
        variant->
        variant.outputs.each{
            output->
            if(output.outputFile!=null && output.outputFile.name.endsWith('.apk')
                && 'release'.equals(variant.buildType.name)){
                    println "variant:${variant.name}___output:${output.name}"
                    def file = new File(output.outputFile.parent,"my_${variant.name}.apk")
                    output.outputFile=file
                }
        }
    }
}


applicationVariants是一個DomainObjectCollection集合,通過all()遍歷,遍歷的每個variant是一個生成的產物,
生成數量爲 productFlavor * buildType 個.
applicationVariant具有一個outputs作爲它的輸出,outputs是一個List集合

動態生成版本信息
在build中配置,但是不方便修改,一般格式 major.minor(.patch)

分模塊設置版本信息

//version.gradle
ext{
    appVersion=1
    appVersionName="1.0.0"
}
​
//build.gradle
apply from:'version.gradle'
android{
    ...
    defaultConfig{
        ...
        versionCode appVersion
        appVersionName appVersionName
    }
}

從Git的tag中獲取

//git 中獲取tag的命令
git describe --abbrev=0 --tags

在Gradle中執行Shell命令

//推薦
ExecResult exec(Closure closure);
ExecResult exec(Action<? super ExecSpec> action);
//閉包委託給ExecSpec
public interface ExecSpec extends BaseExecSpec {
    void setCommandLine(Object... args);
    void setCommandLine(Iterable<?> args);
    ExecSpec commandLine(Object... args);
    ExecSpec commandLine(Iterable<?> args);
    ExecSpec args(Object... args);
    ExecSpec args(Iterable<?> args);
    ExecSpec setArgs(Iterable<?> args);
    List<String> getArgs();
}

//定義一個方法
def getAppversion(){
        def os = new ByteArrayOutputStream()
    exec{
        //貌似親測不行,找不到名稱,但其他命令可以
//        commandLine 'git','describe','--abbrev=0','--tags'
//        commandLine 'git','status'
        standardOutput=os
    }
    return "mytask:"+os.toString()
}
​
//使用該方法
android{
    defaultConfig{
        versionName getAppversion()
    }
}
​
​


task mytask {
    def os = new ByteArrayOutputStream()
    exec{
        //貌似親測不行,找不到名稱
//        commandLine 'git','describe','--abbrev=0','--tags'
//        commandLine 'git','status'
        standardOutput=os
    }
    println "mytask:"+os.toString()
}
​

隱藏簽名文件信息
保存到服務器中,以環境變量的方式讀取
首先,你得有一個專門打包發版的服務器
並配置對應的環境變量

android{
    ...
    signingConfigs{
        def appStoreFile=System.getenv("STORE_FILE")
        def appStorePassword=System.getenv("STORE_PASSWORT")
        def appKeyAlias=System.getenv("KEY_ALIAS")
        def appKeyPassword=System.getenv("KEY_PASSWORD")
​
        //當不能從當前環境變量中獲取時則使用Debug簽名
        //從AndroidSdk(${Home}/.android/)中複製Debug簽名到工程目錄中
        if(!appStoreFile||!appStorePassword||!appKeyAlias||!appKeyPassword){
            appStoreFile="debug.keystore"
            appStorePassword="android"
            appKeyAlias="androiddebugkey"
            appKeyPassword="android"
        }
        release{
            storeFile file(appStoreFile)
            storePassword appStorePassword
            keyAlias appkeyAlias
            keyPassword appKeyPassword
        }
    }
    buildTypes{
        release{
            signingConfig signConfigs.release
            zipAlignEnabled true
        }
    }
}

動態配置AndroidManifest.xml
在構建過程中動態的修改配置文件

,如 友盟第三方分析統計的時候會要求我們

//AndroidManifest.xml
<meta-data android:value="Channel ID" android:name="UMENG_CHANNEL"/>

但配置文件只有一個.

爲了解決這個問題,AndroidGradle提供了非常便捷的manifestPlaceholder Manifest佔位符.

ManifestPlaceholder是ProductFlavor的一個屬性:Map,所以我們可以同時配置多個佔位符

android{
    ...
    productFlavor{
        google{
            manifestPlaceholder.put("UMENG_CHANNEL","google")
        }
        baidu{
            manifestPlaceholder.put("UMENG_CHANNEL","baidu")
        }
    }
    //也可以一次性修改
    productFlavor.all{
        flavor->
        manifestPlaceholder.put("UMENG_CHANNEL",name)
    }
}

//在配置文件中是,未驗證,但應該不需要在配置文件中寫這行,${UMENG_CHANNEL}就是佔位符,到時 baidu ,google會替代${UMENG_CHANNEL}內容
<meta-data android:value="${UMENG_CHANNEL}" android:name="UMENG_CHANNEL"/>

自定義BuildConfig
BuildConfig是由AndroidGradle編譯自動生成的

public final class buildConfig{
    //是否是debug模式
    public static final boolean DEBUG=Boolean.parseBoolean("true")
    //包名
    public static final String APPLICATION_ID="org.flysnow.app.projectName"
    //構建類型
    public static final String BUILD_TYPE="debug"
    //產品風格
    public static final String FLAVOR="baidu"
    //版本號和版本名稱
    public static final int VERSION_CODE=1
    public static final String VERSION_NAME="xx.1.0"
}
​

自定義BuildConfig

android{
    ...
    productFlavors{
        google{
            //注意'""'中的""不能省略,否則生成的類型是String WEB_URL=http://www.google.com
            buildConfigField 'String','WEB_URL','"http://www.google.com"'
        }
        baidu{
            buildConfigField 'String','WEB_URL','"http://www.baidu.com"'
        }
    }
    //因爲BuildType也是一種productFlavor,所以... debug ,release版本跟衍生版本都可以用  buildConfigField!
    buildType{
        debug{
            buildConfigField 'String','NAME','"value"'
        }
    }
}

動態添加自定義的資源
僅針對res/values資源
它們不光可以在res/values.xml中定義,還可以在AndroidGradle中定義. 也可以在 BuildType 使用

//product.Flavor.resValue源碼
//由註釋可知它會生成一個資源,其效果和在res/values文件中定義是一樣的
    public void resValue(
            @NonNull String type,
            @NonNull String name,
            @NonNull String value) {
        ClassField alreadyPresent = getResValues().get(name);
        if (alreadyPresent != null) {
            logger.info("BuildType({}): resValue '{}' value is being replaced: {} -> {}",
                    getName(), name, alreadyPresent.getValue(), value);
        }
        addResValue(new ClassFieldImpl(type, name, value));
    }
​
​
//demo
android {
    ...
    buildTypes {
        debug {
            zipAlignEnabled true
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt')
            , 'proguard-rules.pro'
            //string id bool dimen integer color
            resValue 'string','BaseUrl','http://www.baidu.com'
        }
    }
}
//會在build/generated/res/resValues/baidu/debug/values/generated.xml
<resources>
    <string name="BaseUrl" translatable="false">http://www.baidu.com</string>
</resources>  

Java編譯選項
在AndroidGradle中對Java源文件的編碼 源文件使用的JDK版本進行修改

android{
    ...
    compileOptions{
        encoding='utf-8'
        sourceCompatibility=JavaVersion.VERSION_1_6   //配置Java源代碼的編譯級別  可用的值 1."1.6" ,2.1.6, 3. JavaVersion.Version_1_6 4."Version_1_6"
        targetCompatibility=JavaVersion.VERSION_1_6   // 配置生成的 Java字節碼的版本 
    }
}
​

Adb操作選項配置
adb,Android Debug Bridge,用於連接電腦和設備的進行一些調試操作.
在Shell中我們可以通過輸入adb來查看其功能和使用說明.
在Gradle中我們也可以有一些配置

android{
    ...
    adbOptions{
        //超時則拋出CommandRejectException
        timeOutInMs 5*1000
        //詳情見下圖
        setInstallOptions '-r','-s'
    }
}
​

setInstallOptions

-l:鎖定該應用程序
-r:替換已經存在的程序,也就是強制安裝
-t:允許測試包
-s:把應用安裝到sd卡上
-d:允許進行降級安裝
-g:給該應用授權所有運行時的權限

DEX選項配置
Android中的源碼被編譯成class文件後,在打包成apk文件時又被dx命令優化成Android虛擬機可執行的dex文件.
對於這些dex文件的生成和處理,AndroidGradle會自動調用android SDK的dx命令.

但是有時候也會出現內存不足的異常(java.lang.OutOfMemoryError),因爲該命令其實就是一個腳本(dx.jar),由Java程序執行的.
由錯誤信息可知,默認分配的是G8(1024MB)
我們也可以通過 -j 參數配置

dexOptions{
    //是否開啓增量模式,增量模式速度會更快,但可能會出現很多問題,一般不開啓 慎用
    incremental true
    //分配dx命令的堆棧內存
    javaMaxHeapSize '1024mb'
    //65536後能構建成功
    jumboMode true
    //配置是否預執行dex Library庫工程,開啓後會大大加快增量構建的速度,不過clean構建的速度
    //默認true,但有時需要關閉這個選項(如mutil dex)
    preDexLibraries false
    //dx命令時的線程數量,適當的線程數量可以提高dx 的效率
    threadCount 2
}

//源碼
public interface DexOptions {
    boolean getPreDexLibraries();
    boolean getJumboMode();
    boolean getDexInProcess();
    boolean getKeepRuntimeAnnotatedClasses();
    String getJavaMaxHeapSize();
    Integer getThreadCount();
    Integer getMaxProcessCount();
    List<String> getAdditionalParameters();
}

解決64K異常
隨着業務越來越複雜,特別是集成第三方jar包
因爲Dalvik虛擬機使用了short類型做作爲dex文件中方法的索引,也就意味着單個dex文件只能擁有65536個方法

首先使用的Android Build Tools和Android Support Repository到21.1
其次在Gradle中開啓

//沒超過只會有一個dex文件
//開啓後會生成class.dex .. calssn.dex
android{
    defaultConfig{
        ...
        multiDexEnabled true
    }
}

之後得這樣設置

//但在5.0前只認一個dex,所以需要在入口中配置
//沒有自定義applcation時
<application
    android:name="android.support.multidex.MultiDexApplication"
//自定義時則extends MutilDexApplication
//或
public MyApplication extends Application{
    protected void attachBaseContext(Context base){
        super.attachBaseContext(base);
        //MultiDexApplication也是這麼實現的
        MutilDex.install(this);
    }
}   

自動清理未使用的資源的Gradle配置
使用Android Lint檢測沒有使用的資源手動刪除
Resource Shrinking
在構建時,會檢測所有資源,看看是否被引用(不管是不是第三方),沒有被引用的資源則不會被打包的apk中.
一般Resource Shrinking要配合混淆使用,混淆時會清理無用代碼,這樣無用代碼引用的資源也會被移除

android{
  ...
  buildTypes{
        release{
        //通過日誌輸出可以看到哪些文件被清理了
            minifyEnabled true
            shrinkResource true
        }
    }
}
​

但有時候通過反射引用資源文件的時候,使用到的資源文件也會被刪除,所以我們需要保存某些資源文件

//res/raw/keep.xml,該文件不會被打包進apk
<?xml version="1.0" encoding="utf-8">
<resource xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@layout/l_used*_c,@layout/l_used*_b"/>
//keep.xml還有一個屬性是tools:shrinkMode,用於配置清理模式
默認safe是安全的,可以識別getResource().getIdentifier("unused","drawable",getPackageName())
如果改成strict則會被刪除

resConfigs中配置,resConfigs 屬於 ProductFlavor 的一個方法
使用GoogleMaps時因爲國際化的問題,我們可能並不需要其中的某些文件,我們只需要其中一些語言就行了
resConfigs是ProductFlavor的一個方法,它的參數就是我們常用的資源限定符

android{
  ...
  defaultConfig{
        ...
        //打包時僅保留中文
        resConfig 'zh'
        //一次配置多個
        resConfigs{
​
        }
    }
}
​

resConfig 參數不止語言,還有Api Level ,分辨率問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章