此章教隱藏證書
批量修改生成的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 ,分辨率問題。