App 瘦身最佳實踐
業務方和開發都希望app盡量的小,本文會給出多個實用性的技巧來幫助開發者進行app的瘦身工作。瘦身和減負雖好,但需要注意瘦身對于項目可維護性的影響,建議根據自身的項目進行技巧的選取。
一、背景
目前app的大小越來越大,使用者對于過大的app接受度不高,是以除了插件化和RN的方案外,我們隻能老老實實的進行app的瘦身工作。二、需求
- 我要利用混淆來讓我的代碼盡可能少
- 最好能用最少的切圖完成功能
- layout檔案不要太多,太多了亂
- 能動态下載下傳的就做動态
- 我希望能用大小最小的圖檔
- 如果能用svg,我就用svg
- 對于無用的資源,我要as能自動删除掉
- 中國文字博大精深,而我隻要我需要的字的字型
- 最好能根據下載下傳使用者手機的cpu和分辨率來引入不同的資源
三、實作
分析app組成結構
做瘦身之前一定要了解自己app的組成結構,然後要有針對性的進行優化,并且要逐漸記錄比對,這樣才能更好的完成此項工作。關于apk的大小,我推薦google的這個視訊。目前as的2.2預覽版中已經有了apk分析器,功能相當強大,此外你還可以利用nimbledroid來分析apk。nimbledroid是一個強大的工具,推薦一試。image_1ar7h642m1v52g0p1r5c4fo1idc4e.png-79.1kB
我們都知道apk是由:
- asserts
- lib
- res
- dex
- META-INF
- androidManifest
image_1ar5097i31eqm12kp1ndlbilos59.png-36.3kB
開始分析後幾秒鐘就能輸出結果,你還可以看到具體類目占的百分比,清晰明了。旁邊的“對比”按鈕提供了diff的功能,讓你可以友善的進行apk優化前後的對比,簡直利器。
image_1ar50k0hl4ek1b8k8fd14bjg9d13.png-63.8kB image_1ar50n9r11tha15la1gpi1uqs19d81t.png-90.8kBassets
assets目錄可以存放一些配置檔案或資源檔案,比如webview的本地html,react native的jsbundle等,微信的整個assets占用了13.4M。如果你的應用對本地資源要求很少的話,這個檔案應該不會太大。 image_1ar50vjva119s1abt9it64n1pbfm.png-120.2kBlib
lib目錄下會有各種so檔案,分析器會檢查出項目自己的so和各種庫的so。微網誌和微信一樣隻支援了arm一個平台,淘寶支援了arm和x86兩個平台。 image_1ar8g77ucuj7ffo14hs15a1lknm.png-36.4kBresources.arsc
這個檔案是編譯後的二進制資源檔案,裡面是id-name-value的一個map。因為微信做了資源的混淆,是以這裡可以看到資源名稱都是不可讀的。image_1ar50ru80tkj165u1v5g90j1tte9.png-133.3kB
索性放個微網誌的圖,易于大家了解:
image_1ar8ggv1q4ac6m100pd4r7uo1g.png-80.1kBMETA-INF
META-INF目錄下存放的是簽名資訊,用來保證apk包的完整性和系統的安全性,幫助使用者避免安裝來曆不明的盜版apk。 image_1ar8gijsiudo1h99lc89leiic1t.png-28kBres
res目錄存放的是資源檔案。包括圖檔、字元串。raw檔案夾下面是音頻檔案,各種xml檔案等等。因為微信做了資源混淆,圖檔名字都不可讀了。image_1ar5143g519urnco1tbt2f3158k1j.png-35.6kB
微網誌就沒有做資源混淆,是以可讀性較好:
image_1ar8gefrd1anu1q5b5h170d1rie13.png-57.3kBdex
dex檔案是java代碼打包後的位元組碼,一個dex檔案最多隻支援65536個方法,這也是為什麼微信有了三個dex檔案的原因。image_1ar8g2r9keg21dn1s8grcur7v9.png-5.1kB
因為dex分包是不均勻的,你可以了解為裝箱,一個箱子的大小是固定的,但你代碼的量是不确定的,微信把前兩個箱子裝滿了,最後還剩了2m多的代碼,這些代碼也占用了一個箱子,最終産生了上圖不均勻的結果。
現在,我們已經知道了apk中各個檔案的大小和它們占的比例,下面就可以開始針對性的進行優化了。
優化assets
assets中會存放資源檔案,這個目錄中各個app存放的内容都有所不同,是以優化也比較難。自從引入RN以來,這個目錄下還會有jsbundle的資訊。如果你有位址選擇的功能,這裡還會存放位址的映射檔案(可參考全名k歌)。對于這塊的資源,as是不會進行主動的删減的,是以一切都是需要靠開發者進行手動管理的。
全民k歌中的bundle檔案:
删除無用字型
中文字型是相當大的,我一直不建議将字型檔案随意丢棄到assets中。有時候一個小功能急着上,開發者為了追求速度,可以先放在這裡圖省事。但一定要知道這個隐患,并且一定要多和産品核對功能的必要性。此外,對于有些隻會用在logo中的字型,我推薦将字型檔案進行删減處理。
FontZip是一個字型提取工具,readme中寫到:
經過測試,已經把項目5MB的藝術字型,按需求提取後,占用隻有20KB,并且可正常使用。gif2 (1).gif-204.5kB
減少icon-font的使用
icon-font和svg都能完成一些icon的展示,但因為icon-font在assets中難以管理,并且功能和svg有所重疊,是以我建議減少icon-font的使用,利用svg進行代替,畢竟一個很小的icon-font也比svg大呢。我給出一個提供各種格式icon的網站,友善大家進行測試:https://icomoon.io/app/ image_1ara1tf2ucmeetq14v510a21fob1d.png-16.1kB image_1ara1mtufm781bbs1khag1k21ag.png-22.2kB- svg:549位元組
- png:375位元組(單一分辨率)
- ion-font:1.1kb
動态下載下傳資源
字型、js代碼這樣的資源能動态下載下傳的就做動态下載下傳,雖然這樣會有出錯的可能性,複雜度也會提升,但這個對于app的瘦身和使用者來說是有長遠的好處的。如果你用了RN,你就可以在app運作時動态去拉取最新的代碼,将圖檔和js代碼一并下載下傳後解壓使用。壓縮資源檔案
有些資源檔案是必須要随着app一并釋出的,對于這樣的檔案,可以采用壓縮存儲的方式,在需要資源的時候将其解壓使用,下面就是解壓zip檔案的代碼:public static void unzipFile(File zipFile, String destination) throws IOException {
FileInputStream fileStream = null;
BufferedInputStream bufferedStream = null;
ZipInputStream zipStream = null;
try {
fileStream = new FileInputStream(zipFile);
bufferedStream = new BufferedInputStream(fileStream);
zipStream = new ZipInputStream(bufferedStream);
ZipEntry entry;
File destinationFolder = new File(destination);
if (destinationFolder.exists()) {
deleteDirectory(destinationFolder);
}
destinationFolder.mkdirs();
byte[] buffer = new byte[WRITE_BUFFER_SIZE];
while ((entry = zipStream.getNextEntry()) != null) {
String fileName = entry.getName();
File file = new File(destinationFolder, fileName);
if (entry.isDirectory()) {
file.mkdirs();
} else {
File parent = file.getParentFile();
if (!parent.exists()) {
parent.mkdirs();
}
FileOutputStream fout = new FileOutputStream(file);
try {
int numBytesRead;
while ((numBytesRead = zipStream.read(buffer)) != ) {
fout.write(buffer, , numBytesRead);
}
} finally {
fout.close();
}
}
long time = entry.getTime();
if (time > ) {
file.setLastModified(time);
}
}
} finally {
// ...
}
}
全民k歌中的assets目錄下我就發現了大量的zip檔案: image_1arn9mkkmm061j631b3f1ag7a3i3h.png-26.5kB
android上也有一個7z庫幫助我們友善的使用7z,這個庫我目前沒用到,有需求的同學可以嘗試一下。
優化lib
配置abiFilters
一個硬體裝置對應一個架構(mips、arm或者x86),隻保留與裝置架構相關的庫檔案夾(主流的架構都是arm的,mips屬于小衆,預設也是支援arm的so的,但x86的不支援)可以大大降低lib檔案夾的大小。配置方式也十分簡單,直接配置abiFilters即可:defaultConfig {
versionCode
versionName '1.0.0'
renderscriptTargetApi
renderscriptSupportModeEnabled true
// http://stackoverflow.com/questions/30794584/exclude-jnilibs-folder-from-production-apk
ndk {
abiFilters "armeabi", "armeabi-v7a" ,"x86"
}
}
之後生成的apk中就會排出多餘的平台檔案了。armeabi就不用說了,這個是必須包含的,v7是一個圖形加強版本,x86是英特爾平台的支援庫。
官方例子:
按 ABI 拆分
android {
...
splits {
abi {
enable true
reset()
include 'x86', 'armeabi-v7a', 'mips'
universalApk true
}
}
}
- enable: 啟用ABI拆分機制
- exclude: 預設情況下所有ABI都包括在内,你可以移除一些ABI
- include:指明要包含哪些ABI
- reset():重置ABI清單為隻包含一個空字元串(這也是允許的,在與include一起使用來可以表示要使用哪一個ABI)
- universalApk:訓示是否打包一個通用版本(包含所有的ABI)。預設值為 false。
根據使用者cpu來引入so
我們在舍棄so之前需要進行使用者cpu型号的統計,這樣你才能放心大膽的進行操作。我先是花了幾個版本的時間統計了使用者的cpu型号,然後排除了沒有或少量使用者才會用到的so,以達到瘦身的目的。@NonNull
public static String getCpuName() {
String name = getCpuName1();
if (TextUtils.isEmpty(name)) {
name = getCpuName2();
if (TextUtils.isEmpty(name)) {
name = "unknown";
}
}
return name;
}
private static String getCpuName1() {
String[] abiArr;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
abiArr = Build.SUPPORTED_ABIS;
} else {
abiArr = new String[]{Build.CPU_ABI, Build.CPU_ABI2};
}
StringBuilder abiStr = new StringBuilder();
for (String abi : abiArr) {
abiStr.append(abi);
abiStr.append(',');
}
return abiStr.toString();
}
private static String getCpuName2() {
try {
FileReader e = new FileReader("/proc/cpuinfo");
BufferedReader br = new BufferedReader(e);
String text = br.readLine();
String[] array = text.split(":\\s+", );
e.close();
br.close();
return array[];
} catch (IOException var4) {
var4.printStackTrace();
return null;
}
}
注意: - 如果你和我一樣用到了
那麼你必須包含v7,否則會出現模糊異常的問題。renderscript
- 如果你用了RN,那麼對于x86需要謹慎的保留,否則可能會出現使用者找不到so而崩潰的情況。畢竟rn是一個全局的東西,稍有不慎就可能會出現開機崩的情況。
- so這個東西還是比較危險的,我們雖然可以通過統計cpu型号來降低風險,但我還是推薦釋出app前走一遍大量機型的雲測,通過雲測平台把風險進一步降低。
- 小廠的項目可能會舍棄一些so,但随着公司規模的增大,你未來仍舊要重複考慮這個問題。是以我推薦在崩潰系統中上傳使用者cpu型号的資訊,這樣我們就可以在第一時間知道因找不到so引起的崩潰量,至于是否需要增加so就看問題的嚴重程度了。
避免複制so
so有個常年大坑。在Android 6.0之前,so檔案會壓縮到apk中。系統在安裝應用的時候,會把so檔案解壓到data分區,這樣同一個so檔案會有兩份存在,一個在apk裡,一個在data中。這也導緻多占用了一倍的空間,而且會出現各種詭異的錯誤。這個政策雖然和apk的瘦身無關,但它和app安裝在使用者手機中的大小有關,是以我們也是需要多多留意的。Starting from Android Studio 2.2 Preview 2 and newest build tools, the build process will automatically store native libraries uncompressed and page aligned in the APK.在6.0+中,可以通過如下的方式進行申明:
<application
android:extractNativeLibs=”false”
...
>
如果想了解更多資訊或者想知道這種配置的限制,可以浏覽下SmallerAPK(8)。 優化resources.arsc
resources.arsc中存放了一個對應關系:id | name | default | v11 |
---|---|---|---|
0x7f090002 | PopupAnimation | @ref/0x7f040042, @ref/0x7f040041 | … |
我們在程式運作的時候肯定要經常用到id,是以它在安裝之後仍需要被頻繁的讀取。如果将這個檔案進行壓縮,在每次讀取前系統都必須進行解壓的操作,這就會有一些性能和記憶體的開銷,綜合考慮下這是得不償失的。
删除無用的資源映射
resources.arsc的正确瘦身方式是删除不必要的
string entry
,你可以借助 android-arscblamer來檢查出可以優化的部分,比如一些空的引用。
進行資源名稱混淆
微信團隊開源了一個資源混淆工具,AndResGuard。它将資源的名稱進行了混淆,是以可以用它對resources.arsc進行優化,隻是具體優化效果與編碼方式、id數量、平均減少命名長度有關。
表1:
id | name | default | v11 |
---|---|---|---|
0x7f090001 | Android | @ref/0x7f040042, @ref/0x7f040041 | … |
0x7f090002 | ios | @ref/0x7f040042, @ref/0x7f040041 | … |
0x7f090003 | Windows Phone | @ref/0x7f040042, @ref/0x7f040041 | … |
表2:
id | name | default | v11 |
---|---|---|---|
0x7f090001 | a | @ref/0x7f040042, @ref/0x7f040041 | … |
0x7f090002 | b | @ref/0x7f040042, @ref/0x7f040041 | … |
0x7f090003 | c | @ref/0x7f040042, @ref/0x7f040041 | … |
我們一眼就可以知道表2肯定比表1存儲的字元要小,是以整個檔案的大小肯定也要小一些。
谷歌提供了一個工具來友善我們審查
resources.arsc
檔案,它叫ArscBlamer。
詳細資訊請參考:smallerapk-part-3-removing-unused-resources
關于AndResGuard
這個壓縮工具其實就是一個task,使用也十分簡單,具體的用法請參考中文文檔。
原理介紹:安裝包立減1M--微信Android資源混淆打包工具
andResGuard {
mappingFile = null
use7zip = true
useSign = true
keepRoot = false
whiteList = [
//for your icon
"R.drawable.icon",
//for fabric
"R.string.com.crashlytics.*",
//for umeng update
"R.string.umeng*",
"R.string.UM*",
"R.layout.umeng*",
"R.drawable.umeng*",
//umeng share for sina
"R.drawable.sina*"
]
compressFilePattern = [
"*.png",
"*.jpg",
"*.jpeg",
"*.gif",
"resources.arsc"
]
sevenzip {
artifact = 'com.tencent.mm:SevenZip:1.1.9'
//path = "/usr/local/bin/7za"
}
}
使用這個工具的時候需要注意一些坑,像友盟這種喜歡用反射擷取資源的SDK就是一個坑(友盟的SDK就是坑王)!對于app啟動圖示這樣的icon可以不做混淆,推薦将其放入白名單裡。
優化META-INF
META-INF檔案夾中有三個檔案,分别是MANIFEST.MF、CERT.SF、CERT.RSA。下面我将會列出簡要的分析,如果你希望更詳盡的了解原理,可以檢視《Android APK 簽名檔案MANIFEST.MF、CERT.SF、CERT.RSA分析》。
MANIFEST.MF
image_1arn2sdp8ip3cj31j9m1vofcajm.png-81.6kB
每一個資源檔案(res開頭)下面都有一個SHA1-Digest的值。這個值為該檔案SHA-1值進行base64編碼後的結果。
如果要探究原理,可以看下SignApk.java。這個類中有一段main方法:
public static void main(String[] args) {
//...
// MANIFEST.MF
Manifest manifest = addDigestsToManifest(inputJar);
je = new JarEntry(JarFile.MANIFEST_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
manifest.write(outputJar);
//...
}
private static void writeSignatureFile(Manifest manifest, OutputStream out)
throws IOException, GeneralSecurityException {
Manifest sf = new Manifest();
Attributes main = sf.getMainAttributes();
main.putValue("Signature-Version", "1.0");
main.putValue("Created-By", "1.0 (Android SignApk)");
BASE64Encoder base64 = new BASE64Encoder();
MessageDigest md = MessageDigest.getInstance("SHA1");
PrintStream print = new PrintStream(
new DigestOutputStream(new ByteArrayOutputStream(), md),
true, "UTF-8");
// Digest of the entire manifest
manifest.write(print);
print.flush();
main.putValue("SHA1-Digest-Manifest", base64.encode(md.digest()));
Map<String, Attributes> entries = manifest.getEntries();
for (Map.Entry<String, Attributes> entry : entries.entrySet()) {
// Digest of the manifest stanza for this entry.
print.print("Name: " + entry.getKey() + "\r\n");
for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {
print.print(att.getKey() + ": " + att.getValue() + "\r\n");
}
print.print("\r\n");
print.flush();
Attributes sfAttr = new Attributes();
sfAttr.putValue("SHA1-Digest", base64.encode(md.digest()));
sf.getEntries().put(entry.getKey(), sfAttr);
}
sf.write(out);
}
通過代碼我們可以發現SHA1-Digest-Manifest是MANIFEST.MF檔案的SHA1并base64編碼的結果。
CERT.SF
image_1arn41udjrb91fku103b193a2qe13.png-68.3kB
這裡有一項SHA1-Digest-Manifest的值,這個值就是MANIFEST.MF檔案的SHA-1并base64編碼後的值。後面幾項的值是對MANIFEST.MF檔案中的每項再次SHA1并base64編碼後的值。是以你會看到在manifest.mf中的資源名稱在這裡也出現了,比如
abc_btn_check_material
這個系統資源檔案就出現了兩次。
MANIFEST.MF:
image_1arn47aeq15qc40910rt1u4cvm11g.png-39.3kB
CERT.SF
image_1arn4bje01q921ih1plv15te1n871t.png-44kB
前者是:4XHnecusACTIgtImUjC7bQ9HNM8=,後者是YFDDnTUd6St4932sE/Xk6H0HMoc=。如果你把前一個檔案打開在後面加上\n\r,然後進行編碼,你就會得到CERT.SF中的值。
Map<String, Attributes> entries = manifest.getEntries();
for (Map.Entry<String, Attributes> entry : entries.entrySet()) {
// Digest of the manifest stanza for this entry.
print.print("Name: " + entry.getKey() + "\r\n");
for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {
print.print(att.getKey() + ": " + att.getValue() + "\r\n");
}
print.print("\r\n");
print.flush();
Attributes sfAttr = new Attributes();
sfAttr.putValue("SHA1-Digest", base64.encode(md.digest()));
sf.getEntries().put(entry.getKey(), sfAttr);
}
sf.write(out);
CERT.RSA
CERT.RSA包含了公鑰、所采用的加密算法等資訊。它對前一步生成的MANIFEST.MF使用了SHA1-RSA算法,用開發者的私鑰進行簽名,在安裝時使用公鑰解密它。解密之後,将它與未加密的摘要資訊(即,MANIFEST.MF檔案)進行對比,如果相符,則表明内容沒有被修改。這點和app瘦身就完全無關了,就是android的apk簽名機制。這塊我平時也沒有仔細研究過,就不誤人子弟了。具體的簽名過程可以參考:http://blog.csdn.net/asmcvc/article/details/9312123
優化建議
通過分析得出,除了CERT.RSA沒有壓縮機會外,其餘的兩個檔案都可以通過混淆資源名稱的方式進行壓縮。
image_1arn6q7ip1p71c1q1kv3s9rigi2a.png-49.6kB
優化res
資源檔案的優化一直是我們的重頭戲。如果要和它進行對比,上文的META-INF檔案的優化簡直可以忽略不計。這裡的優化會分為兩塊,一個是文本資源(shape、layout等)優化,還有一個就是圖檔資源優化。
image_1arn8pmpn1ofkdnk14o11hfp15es34.png-116.3kB
說明:
上圖中有-v4,-v21這樣的檔案有些是app開發者自己寫的,但大多都是系統在打包的時候自動生成的,是以你隻需要考慮自己項目中的drawable-mdpi、drawable-hdpi、drawable-xhdpi、drawable-xxhdpi即可。
通過as删除無用資源
在as的任何檔案中右擊,選擇清除無用資源即可删除沒有用到的資源檔案。
image_1arni457b1j7o1q3pu6f1gn62ju4b.png-57.2kB
不要勾選清除id!如果清除了id,會影響databinding等庫的使用(id絕對占不了多少空間)
image_1arni765omt7bv01qrm1h2d19us4o.png-14.6kB
Tips:
做此操作之前,請務必産生一次commit,操作完成後一定要通過git看下diff。這樣既友善檢視被删除的檔案,又可以利用git進行誤删恢複。
打包時剔除無用資源
shrinkResources顧名思義————收縮資源。将它設定為true後,每次打包的時候就會自動排除無用的資源(不僅僅是圖檔)。有了它的幫忙,即使你忘記手動删除無用的資源檔案也沒事。
buildTypes {
release {
zipAlignEnabled true
minifyEnabled true
shrinkResources true // 是否去除無效的資源檔案
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
signingConfig signingConfigs.release
}
rtm.initWith(buildTypes.release)
rtm {}
debug {
multiDexEnabled true
}
}
删除無用的語言
大部分應用其實并不需要支援幾十種語言的,微信也做了根據地區選擇性下載下傳語言包的功能。作為國内應用,我們可以隻支援中文。推薦在項目的build.gradle中進行如下配置:
android {
//...
defaultConfig {
resConfigs "zh"
}
}
這樣在打包的時候就會排除私有項目、android系統庫和第三方庫中非中文的資源檔案了,效果還是比較顯著的。
控制raw中資源的大小
- assets目錄允許下面有多級子目錄,而raw下不允許存在目錄結構
- assets中的檔案不會産生R檔案映射,但raw會
- 如果你app最低支援的版本不是2.3的話,assets和raw應該都不會對資源檔案的大小進行限制
- raw檔案會生成R檔案映射,可以被as的lint分析,而assets則不能
- raw缺少子目錄的缺點讓其無法成為存放大量檔案的目錄
一般raw檔案下會放音頻檔案。如果raw檔案夾下有音頻檔案,盡量不要使用無損(如:wav)的音頻格式,可以考慮同等品質但檔案更小的音頻格式。
ogg是一種較适合做音效的音頻格式。當年我國中做遊戲的時候,我全都是用的mp3和png,最終遊戲達到了2G。在換為ogg和jpg後,遊戲縮小到了1G以内(因為遊戲中音頻和大圖較多,是以效果比較誇張)。移動端的音頻主要是音效和短小的音頻,是以淘寶大量選擇了ogg格式,微網誌的選擇格式比較多,有wav、mp3、ogg,我更加推薦淘寶的做法。當然,你仍舊不要忘記opus格式,opus也是一種有損壓縮格式,如果感興趣的話也可以嘗試一下。
image_1arn8i7lq1ub56r41a022mj11n32n.png-38.8kB
統一應用風格,減少shape檔案
一個應用的界面風格是必須要統一的,這個越早做越好,最基本的就是統一顔色和按鈕的按壓效果。無UI設計和扁平化風格流行後,倒是給應用瘦身帶來了極大的的福利。界面變得越樸實,我們可以用shape畫的東西就越多。
image_1arnj7vjtn1mr74eql8781qjg5v.png-44.5kB
當你的app統一過每種顔色對應的按下顔色後,接下來就需要統一按鈕的形狀、按鈕的圓角角度、有無陰影的樣子、陰影投射角度,陰影範圍等等,最後還要考慮是否支援水波紋效果。
我簡單将按鈕分為下列元素:
元素 | 屬性01 | 屬性02 | 屬性03 | 屬性04 |
---|---|---|---|---|
形狀 | 正方形 | 三角形 | 圓角矩形 | 圓形 |
顔色 | 紅 | 黃 | 藍 | 綠 |
有無陰影 | 有 | 無 | ||
陰影大小 | 3dp | 5dp | ||
陰影角度 | 90° | 120° | 180° | |
水波紋效果 | 有 | 無 |
上面的各個元素會産生大量的組合,shape和layer-list當然可以實作各種組合,但這樣的話光按鈕的背景檔案就有多個,很不好維護。
一般為了開發友善,都會把需要用到的各種selector圖檔事先定義好,做業務的時候隻需要去調用就行。但這大量的selector檔案對于業務開發者來說也是有記憶難度的,是以我推薦使用SelectorInjection這個庫,它可以将上面的每個元素進行各種組合,用最少的資源檔案來實作大量的按壓效果。
用庫雖然好,但庫也會帶來學習成本,是以引入者可以将上述的組合定義為按鈕的一個個的style。因為style本身是支援繼承的,對于這樣的組合形态來說,繼承簡直是一大利器。當你的style有良好的命名後,調用者隻需要知道引入什麼style就行,至于你用了什麼屬性别人才不希望管呢。如果業務開發中有一些特别特殊的按壓狀态,沒有任何複用的價值,那你就可以利用庫提供的豐富屬性在layout檔案中進行實作,再也不用手忙腳亂的到處定義selector檔案了。
image_1arnkegaj1eug1dvsafj1ao71e7p6p.png-37.7kB
我将不能繼承和不靈活的shape變成了一個個單一的屬性,通過庫将多個屬性進行組合,接着利用支援繼承的style來将多個屬性固定成一個配置檔案,最後對外形成強制的規範性限制,至此便完成了減少selector檔案的工作。
使用toolbar,減少menu檔案
menu檔案是ActionBar時代的産物,as雖然對于menu的支援做的還不錯,但我很難愛上它。menu的設計初衷是解耦和抽象,但因為過度的解耦和定制的不友善,很多項目已經不再使用menu.xml作為actionbar的菜單了。
就目前的形勢來看,toolbar是android未來的方向。我雖然作為一個對actionbar和actionbar的相容處理相當了解的人,但我還是不得不承認actionbar的時代過去了。如果你不信,我可以告訴你淘寶的menu檔案就3個,微網誌的menu檔案就9個,如果你還是苦苦依戀着actionbar的配置模式,我推薦一個庫AppBar,它可以讓你在用靈活的toolbar的同時也享受到配置menu的便利性。
image_1ars0g9partf41i1g1lnt8fogm.png-58.4kB
限制靈活性,減少layout檔案
減少layout檔案有兩個方法:複用和融合(include)。
複用layout檔案
把一些頁面共用的布局抽出來,這無論是對layout檔案的管理還是瘦身都是極為有用的。就比如說任何一個app的list頁面是相當多的,從布局層面來說就是一個ListView或者RecyclerView,其背後還可能會有loading的view,空狀态的view等等,是以我的建議是建立一個list_layout.xml,其餘的list頁面可以複用或者include它,這樣會從很大程度上減少layout檔案的數目。
融合layout代碼
對于可以被複用的layout我們可以做統一管理,但是對于不會被複用的layout怎麼辦呢?假設一個頁面是由兩個區域組合而成的,fragment的做法是一個頁面中放兩個container,然後再寫兩個layout,但實際上這兩個layout經常是沒有任何複用價值的。我希望找到一種方式,在view區塊還沒有複用需求的時候用一個layout搞定,需要被複用的時候也可以快速、無痛的拆分出來。
1. UiBlock
UiBlock是一個類似于fragment的解耦庫,它可以為同一個layout中不同區域的view進行邏輯解耦(因為layout可預覽的特性,ui定位方面不是難題),它能幫我們盡可能少的建立layout檔案。
如果未來需求發生了變動,layout檔案中的一塊view需要抽出成獨立的layout檔案的時候,UiBlock的邏輯代碼幾乎不用改動,你隻需要把抽出的layout檔案include進來,然後在
include
标簽上定義一個id即可。而這個工作可以通過as的重構功能自動完成,絕不拖泥帶水。
image_1asesb6fq1dfu1df71iu0117ps8c9.png-142.6kB
<!-- 使用include -->
<include
android:id="@+id/bottom_ub"
layout="@layout/demo_uiblock"
android:layout_width="match_parent"
android:layout_height="100dp"
/>
2. ListHeader
image_1arsqihk3bdg1p281ks0phricam.png-149.8kB
public void addHeaderToListView(ListView listView, View header) {
if (header == null) {
throw new IllegalArgumentException("Can't add a null header view to ListView");
}
ViewGroup viewParent = (ViewGroup) header.getParent();
viewParent.removeView(header);
AbsListView.LayoutParams params = new AbsListView.LayoutParams(
header.getLayoutParams().width,
header.getLayoutParams().height);
header.setLayoutParams(params);
listView.addHeaderView(header); // add
}
我将listView和它的沒有複用價值的header放到了同一個layout中,然後在activity中利用上述代碼進行了操作,最終完成了用一個layout檔案給listView加頭的工作。這段代碼我很久沒動過了,有利有弊,放在這裡我也僅僅是舉個例子,希望可以幫助大家擴充下思路。
動态下載下傳圖檔
做過濾鏡和貼紙的同學應該會注意到貼紙、表情這類的東西是相當大的,對于這類的圖檔資源我強烈建議通過線上商店進行擷取。這樣既可以讓你踏踏實實的賣貼紙,又可以減小應用的大小。這麼做雖然有一定的複雜度和出錯機率,但投入産出比還是很不錯的。
image_1arnj0n951qd7ua1pl44uk16ak5i.png-103kB
準确放置不同分辨率的圖檔
這個雖然不算是app大小的優化,但是如果你放錯了圖檔,對于app啟動時的記憶體大小會有一定的影響:
思考一下,如果把一個本來應該放在drawable-xxhdpi裡面的圖檔放在了drawable檔案夾中會出現什麼問題呢?
在xxhdpi裝置上,圖檔會被放大3倍,圖檔記憶體占用就會變為原來的9倍!
國内也有很多人說可以用一套圖檔來做,不用出多套圖,借此來達到app瘦身和給設計減負的目的。谷歌官方是建議為不同分辨率出不同的圖檔,為此國内也有不少文章讨論過這件事情,這篇總結的不錯推薦一讀。
每次說到這個話題的時候總有很多人有不同的看法,況且很多人還不知道.9圖也是需要切多份的,是以這裡我還是先分析一下大廠的放圖政策,最後咱們再讨論下較優的方案。
減少出圖的分辨率種類
分析過程:《淘寶、微網誌、微信的 Android 圖檔放置政策》
廠商 | mdpi | hdpi | xhdpi | xxhdpi |
---|---|---|---|---|
淘寶 | 小icon | 表情 | 國家icon | 棄用 |
微網誌 | 小icon | 背景圖&表情 | 背景圖 | 背景圖 |
微信 | 棄用 | 表情 | 大圖 | 棄用 |
通過分析得出,傳統的出多個分辨率圖檔的做法在大廠中已經發生了改變,阿裡系、騰訊系的産品都采用了一套圖走天下的路子。這樣的做法還是有利有弊的,權衡之下我給出如下建議:
- 聊天表情就出一套圖,放在hdpi中
- 純色小icon用svg做
- 背景等大圖,出一套放在xhdpi中
- logo等權重較大的圖檔可針對hdpi,xhdpi做兩套圖
- 如果某些圖在真機中确實展示異常,那就用多套圖
- 如果遇到奇葩機型,可針對性的補圖
成年人不看對錯,隻看利弊,是以還請大家權衡一二。
丢棄特定資源
如果我們希望保留或丢棄特定的資源,需要在項目中建立一個
res/raw/keep.xml
檔案,這裡可以使用
tools:keep
和
tools:discard
兩個屬性來保留或丢棄資源。兩個屬性都可以使用逗号(,)分隔符聲明資源名稱清單。也可以使用* 作為比對符。
android {
...
buildTypes {
release {
shrinkResources true // 開啟
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
}
keep:
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:discard="@layout/unused2" />
discard:
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:shrinkMode="safe"
tools:discard="@layout/unused2" />
注意:開啟嚴格模式後,可能對于編譯會産生一些問題,警告也會增多。
開啟嚴格模式
res/raw/keep.xml
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:shrinkMode="strict" />
在resource檔案中指定shrinkMode,你可以指定Gradle在處理該資源檔案時候的方式,預設的值為
safe
,你也可以将它指定為
strict
,它就隻會保留确定引用到的資源。
當你Gradle resource shrinker,Gradle Console 輸出日志,移除APK資源的資訊。例如:
:android:shrinkDebugResources
Removed unused resources: Binary resource data reduced from KB to KB: Removed %
:android:validateDebugSigning
APK建構完成後會Gradle會在/build/outputs/mapping/release/下生成
resource.txt
,這個檔案包括詳細資訊,如資源參考其他資源和使用或删除資源的詳細資訊等。
例如:找出為什麼
@drawable/ic_plus_anim_016
,仍然包含在你的APK中,在
resource.txt
搜尋該檔案名,你可能會發現它是被另一個資源引用,如下:
:: [QUIET] [system.out] @drawable/add_schedule_fab_icon_anim : reachable=true
:: [QUIET] [system.out] @drawable/ic_plus_anim_016
現在需要知道為什麼add_schedule_fab_icon_anim 仍然在使用,搜尋我們可以知道應該有代碼引用着add_schedule_fab_icon_anim。
移除第三方庫中的配置檔案
有時候引用的三方庫會帶有一些配置檔案xxxx.properties,或者license資訊,打包的時候想去掉這些資訊,就可以這樣做
android {
packagingOptions {
exclude 'proguard-project.txt'
exclude 'project.properties'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/NOTICE'
exclude 'META-INF/DEPENDENCIES.txt'
exclude 'META-INF/DEPENDENCIES'
}
}
優化圖檔
對于圖檔的優化應該是放在優化res一節中進行講解的,但是因為圖檔這塊比重太大了,是以我讓其獨立成為一節。本節主要會從圖檔格式、複用圖檔和壓縮圖檔三個方面進行講解。
想要做好圖檔的優化工作首先要知道應該選擇什麼樣的圖檔格式,對于這點我推薦一個視訊,友善大家進行深入的了解。
image_1ar7a260ogr2cldradmbl132o20.png-154.3kB
這是谷歌給出的建議,簡單來說就是:VD->WebP->Png->JPG
- 如果是純色的icon,那麼用svg
- 如果是兩種以上顔色的icon,用webp
- 如果webp無法達到效果,選擇png
- 如果圖檔沒有alpha通道,可以考慮jpg
使用VectorDrawable
VD即VectorDrawable,android上的svg實作類。在經曆了長達半年的緩慢相容之路後,現在終于被support庫相容了,官方文檔中給出了這樣一個例子:
// Gradle Plugin 2.0+
android {
defaultConfig {
vectorDrawables.useSupportLibrary = true
}
}
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_add"
/>
配置好後,我們就可以利用強大的svg來替換純色icon了。
因為svg矢量圖的優勢,終于可以通過一套圖适配多個機型了。svg的好處有很多,缺點也不少,關于svg的優缺點和實踐方案,建議移步:http://todo
使用WebP
webp作為一種新的圖檔格式,但千萬不要用它做app的logo。從Android4.0+開始原生支援,但是不支援包含透明度,直到4.2.1+才支援顯示含透明度的webp,使用的時候要特别注意。
Lossy WebP support (suitable for replacing most JPEGs and some PNGs) is guaranteed on Android 4.0+ devices. Newer WebP features (transparency, lossless, suitable for PNGs) are supported since Android 4.2.1+
webp相比于png最明顯的問題是加載稍慢,不過現在的智能裝置硬體配置越來越高,這點差異越來越小。騰訊之前有一篇對于webp的分析文十分不錯,如果你準備要用webp了,那麼它絕對值得一看。
WebP的編碼時間較長,是PNG的5倍以上,但解碼速度與PNG差不多,甚至很多時候比PNG快。而WebP在編碼時占用記憶體比PNG高25%,解碼時比PNG低30%。——摘自WebP原理和Android支援現狀介紹
注意:如果你的項目最低支援到4.2.1,那麼你可以繼續閱讀了,如果項目還需要支援到4.0版本,我建議暫時不要上webp,成本太高。
####png轉webp
我們可以通過智圖或者isparta将其它格式的圖檔轉換成webP格式。
image_1asbgag1erb11i6b1pqg1gga18is9.png-1045.2kB
webp的問題
相容性不好
官方文檔中說隻有在4.2.1+以上的機型,才能解析無損或者有透明度調整的webp圖檔,4.0+才開始支援無透明度的webp圖檔。我通過雲測發現,在4.0~4.2.1的系統中,帶有透明度的webp圖檔雖然不會崩潰,但是完全無法顯示。
image_1asbo6vvd1o9roku1keqa9r4om1t.png-33.5kB
《APK瘦身記,如何實作高達53%的壓縮效果》一文中也提到有alpha值的jpg圖檔,經過webp轉換後,無法在4.0,4.1的Android系統上運作的問題,具體原因見官方文檔:
image_1asbq6bejqva1mtuvaj3kc1al22n.png-14kB
除了相容性問題外,webp在某些機型和rom上可能會出現一些詭異的問題。在三星的部分機型上,部分有alpha通道的圖中會有一條很明顯的黑線(三星的rom對于shape的alpha的支援也有問題,是紅線)。在小米2刷成4.xx的手機上,系統未能正确識别xml檔案中描述的webp圖檔,也會導緻加載webp失敗。
不便于預覽
因為webp的圖檔格式是很難預覽的,as也沒有辦法直接預覽webp格式,我一般是通過chrome浏覽器打開webp,十分不友善。
image_1asbq487rk9b1ff514p7nrefe02a.png-115.1kB
我們知道gradle在build時,有一個mergeXXXResource Task,它将項目的各個aar中所有的res資源統一整合到
/build/intermediates/res/flavorName/{buildType}
目錄下。
webpConvertPlugin這個gradle插件可以在mergeXXXResource Task和processXXXResource Task之間插入一個task,這個task會将上述目錄下的drawable進行統一處理,将項目目錄裡的png、jpg圖檔(不包含.9圖檔,webp轉換後顯示效果不佳)批量處理成webp圖檔,這樣可以讓我們在日常開發時用png、jpg,正式發包時用webp。
複用圖檔
複用相同的icon
我們通過svg可以讓一張圖檔适用于不同大小的容器中,以達到複用的目的。最常見的例子就是“叉”,除非你的x是有多種顔色的,那麼這種表示關閉的icon可以複用到很多地方。
image_1arst9t8p1tq61k8117n31489eg22a.png-12.5kB
image_1as1sslhf1r0a10mr18rnccg1m6o9.png-24.5kB
上圖中我通過組合的方式将長得一樣的icon(facebook、renren等)複用到了不同的界面中,不僅實作了效果,可維護性也不錯。
使用Tint
着色器(tint)是一個強大的工具,我将其和shape、svg等結合後産生了化學反應。TintMode共有6種,分别是:add,multiply,screen,srcatop,srcin(預設),src_over。下圖是一篇文章中的總結,說明了其靈活性
image_1as22gvi1jdc16ji1p67a3dqu613.png-67.8kB
一般用預設的模式就可以搞定大多數需求了,使用到的控件主要是TextView和ImageButton。ImageButton官方已經給出了支援方案,TextView因為有四個Drawable,官方的tint屬性在低版本又不可用,是以我讓SelectorTextView支援了一下。如果你想要了解具體的相容方法,可以參考庫代碼或《Drawable 着色的後向相容方案》。
ImageButton
SelectorTextView
app:drawableLeftTint="@color/orange"
app:drawableRightTint="@color/green"
app:drawableTopTint="@color/green"
app:drawableBottomTint="@color/green"
因為我用了SelectorTextView和SelectorImageButton,是以我對于背景的tint沒有什麼需求,也就沒做相容性測試,有興趣的同學可以嘗試一下。如果你決定要采用tint,一定要通過雲測等手段做下相容性測試,下圖是我對于上述屬性的測試結果:
image_1as2382s1sfleismn21q991h8b1t.png-218.6kB
複用按壓效果
一個應用中的list頁面都應該做一定程度的統一,對于有限長度的list,我們可能偏向于用ScrollView做,對于無限長的list用RecyclerView做,但對于它們的按壓效果我強烈建議采用同一個樣式。
image_1arsu35djprokc73jpp3c1cj22n.png-75.1kB
以微信為例,它的所有清單都是白色的item,我的優化思路如下:
- 清單由LinearLayout、RecyclerView組成
- 分割線用統一的shape進行繪制,不用切圖
- 整個清單背景設定為白色
- item的背景是一個selector檔案,正常時顔色是透明,按下後出現灰色
通過旋轉來複用
如果一個icon可以通過另一個icon的旋轉變換來得到,那麼我們就可以通過如下方法來實作:
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/blue_btn_icon" // 原始icon
android:fromDegrees="180" // 旋轉角度
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="180" />
image_1art1llie1j2j1hac1hmtndbe1g9.png-28.3kB
這種方法雖好,但是不要濫用。需要看代碼的人知道這種思路,否則會出現不好維護的情況。當設計真的是認為兩個圖有如此的關系的時候才可這樣實作,萬不可耍小聰明。
壓縮圖檔
圖檔的壓縮政策是:
- 優先壓大圖,後小圖
- 不壓.9圖(svg在俠義上不算圖)
- 對于開屏大圖檔的壓縮需注意力度,要和設計确認後再做
- 對于體積特别大(超過50k)的圖檔資源可以考慮有損壓縮
關于如何量化兩張圖檔在視覺上的差别,Google 提供了一個叫butteraugli的工具,有興趣的同學可以嘗試一下。
關于更加詳細的内容可以參考smallerapk-part-6-image-optimization一節。
ImageOptim
mac上超好用的圖檔壓縮工具是ImageOptim,它內建了很多好用圖檔壓縮庫,很多blog中的圖檔也是用它來壓縮的。
image_1ar7a83kr1lrns861di8q3mm6p2q.png-639.7kB
值得一提的是,借助Zopfli,它可以在不改變png圖像品質的情況下使圖檔大小明顯變小。
pngquant
pngquant也是一款著名的壓縮工具,對于png的療效還不錯,但它不一定就适合app中那種背景透明的小icon,是以對比起tinypng來說,優勢不明顯。
image_1art6qlf812hk1c19l0h1te413u6m.png-56.6kB
上圖的資料來自:http://www.jianshu.com/p/a721fbaa62ab
tinypng
tinypng是一款相當著名的商用壓縮工具,tinypng提供了開放接口供開發者開發屬于自己的壓縮工具(付費服務)。tinypng對于免費使用者也算友好,每月可以免費壓縮幾百張圖檔。
我用gradle插件來使用tinypng,更加簡單友善。我一般的做法是發版本前才做一次圖檔壓縮,每次debug的時候是直接跳過這個task的,完全不影響日常的debug。
tinyinfo {
apiKey = 'xxxxxxxxx'
//編譯時是否跳過此task
skip = true
//是否列印日志
isShowLog = true
}
有人說tinypng的缺點是在壓縮某些帶有過渡效果(帶alpha值)的圖檔時,圖檔可能會失真,對于這種圖檔你可以将png圖檔轉換為webP格式。
注意事項
aapt預設會在打包時進行圖檔的壓縮工作(無論你知不知道,它一直在默默的工作),如果你已經做了圖檔壓縮了,那麼建議手動禁止這個功能,否則“可能會”出現圖檔二次壓縮後反而變大的情況,原因請看:Smaller PNGs, and Android’s AAPT tool。
image_1art7sk821fugmvh172i1tnkhi713.png-404.5kB
android {
defaultConfig {
//...
}
aaptOptions {
cruncherEnabled = false
}
}
優化dex
dex本身的體積還是很可觀的,雖說代碼這東西不占用多少存儲空間,但是微信這樣的大廠的dex已經達到了20多M。我大概估計了一下,如果你沒有達到方法數上限,那麼你的dex的大小大約是10M。縱觀應用市場,沒有用multiDex的又有幾家呢?
記錄方法數和代碼行數
dexcout
要優化這部分,首先需要對公司的、android庫的、第三方庫的代碼進行深入的了解,我用了dexcount來記錄項目的方法數:
dexcount {
format = "list"
includeClasses = false
includeFieldCount = true
includeTotalMethodCount = false
orderByMethodCount = false
verbose = false
maxTreeDepth = Integer.MAX_VALUE
teamCityIntegration = false
}
image_1arpdkoh618hc5ss1ud51buhf79ho.png-172.9kB
通過分析你可以知道代碼的具體情況了,比如某個第三方庫是否已經不用了、自己項目的哪個包的方法數最多、目前代碼情況是否合理等等。
statistic
我是通過
Statistic
這個as插件來評估項目中開發人員寫的代碼量的,它生成的報表也不錯:
image_1arpdsqips6jp323969fivcqi5.png-123.4kB
image_1arpduoqa107u8vn8cv1thm1nn0ii.png-145.2kB
現在我可以知道:
哪些類空行數太多,是不是沒有按照代碼規範來;
哪些類的代碼量很少,是否有存在的必要;
哪些類行數過多,是否沒有遵守單一職責原則,是否可以進行進一步的拆分
apk method
你還可以用apk-method-count這個工具來檢視項目中各個包中的方法數,它會生成樹形結構的文檔,十分直覺。
image_1arpedp5l1vrn191t1j7b1gkm7q2iv.png-258.2kB
利用Lint分析無用代碼
如果你想删掉沒有用到的代碼,可以借助as中的
Inspect Code
對工程做靜态代碼檢查。
image_1arpbjnn0gq41604v1p82g1p4dgh.png-52.8kB
image_1arpbrhdp2221n1k7ob15ae1ijrgu.png-141.6kB
Lint是一個相當強大的工具,它能做的事情自然不限于檢查無用資源和代碼,它還能檢測丢失的屬性、寫錯的機關(dp/sp)、放錯像素目錄的圖檔、會引起記憶體溢出的代碼等等。從eclipse時代發展到現在,lint真的是越來越友善了,我們現在隻需要點一點就行。
Lint的強大也會帶來相應的缺點,缺點就是生成的資訊量過多,不适合快速定位無用的代碼。我推薦的流程是到下圖中的結果中直接看無用的代碼和方法。
image_1arpccgij2hjtgi1s7nuob8o5hb.png-160.3kB
注意:
這種删除無用代碼的工作需要反複多次進行(比如一月一次)。當你删除了無用代碼後,這些代碼中用到的資源也會被标記為無用,這時就可以通過上文提到的
Remove Unused Resources
來删除。
通過proguard來删除無用代碼
手動删除無用代碼的流程太繁瑣了,如果是一兩次倒還會帶來删除代碼的爽快感,但如果是專人機械性持續工作,那個人肯定要瘋的。為了保證每次打包後的apk都包含盡可能少的無用代碼,我們可以在build.gradle中進行如下配置:
android {
buildTypes {
release {
minifyEnabled true // 是否混淆
}
}
}
雖然這種方式成果顯著,但也需要配合正确的proguard配置才能起作用,推薦看下讀懂 Android 中的代碼混淆一文。
這種利用混淆來删除代碼的方式是一種保險措施,真正治本的方法還是在開發過程中随手删除無用的代碼,畢竟開發者才是最清楚一段代碼該不該被删的。我之前就是随手清理了下沒用的代碼,然後就莫名其妙的不用使用mulitdex了。
剔除測試代碼
我們在測試的時候可能會随便寫點測試方法,比如main方法之類的,并且還會引入一些測試庫。對于測試環境的代碼gradle提供了很友善的androidTest和test目錄來隔離生産環境。
對于測試時用到的大量庫,可以進行test依賴,這樣就可以保證測試代碼不會污染線上代碼,也可以防止把測試工具、代碼等釋出到線上等錯誤(微網誌就幹過這樣的事情)。
// Dependencies for local unit tests
testCompile 'junit:junit:4.12'
testCompile 'org.hamcrest:hamcrest-junit:2.0.0.0'
// Android Testing Support Library's runner and rules
androidTestCompile 'com.android.support:support-annotations:24.1.1'
androidTestCompile 'com.android.support.test:runner:0.5'
androidTestCompile 'com.android.support.test:rules:0.5'
// Espresso UI Testing
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
PS:在layout中利用
tools
也是為了達到上述目的。
區分debug/rtm/release模式
debug模式是開發者的調試模式,這個模式下log全開,并且會有一些幫助調試的工具(比如:leakcanary,stetho),我們可以通過
debugCompile
和
releaseCompile
來做不同的依賴,有時候也會需要no-op(關于no-op的内容可以參考下開發第三方庫最佳實踐)。
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
}
debug和release是android本身自帶的兩種生産環境,在實際中我們可能需要有多個環境,比如提測環境、預發環境等,我以rtm(Release to Manufacturing 或者 Release to Marketing的簡稱)環境做例子。
首先在目錄下建立rtm檔案:
image_1arpi1tfq1dd5pga35j10ntuhjjc.png-12.3kB
複刻release的配置:
buildTypes {
release {
zipAlignEnabled true
minifyEnabled true
shrinkResources true // 是否去除無效的資源檔案
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
signingConfig signingConfigs.release
}
rtm.initWith(buildTypes.release)
rtm {}
debug {
multiDexEnabled true
}
}
配置rtm依賴:
ext {
leakcanaryVersion = '1.3.1'
}
dependencies {
debugCompile "com.squareup.leakcanary:leakcanary-android:$leakcanaryVersion"
rtmCompile "com.squareup.leakcanary:leakcanary-android-no-op:$leakcanaryVersion"
releaseCompile "com.squareup.leakcanary:leakcanary-android-no-op:$leakcanaryVersion"
}
rtm環境自然也有動态替換application檔案的能力,我為了友善非開發者區分app類别,我做了啟動icon的替換。
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.kale.example"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
>
<application
android:name=".RtmApplication"
android:allowBackup="true"
android:icon="@drawable/rtm_icon"
tools:replace="android:name,android:icon"
/>
</manifest>
現在我可以将環境真正需要的代碼打包,不需要的代碼全部剔除,以達到瘦身的目的。
使用拆分後的support庫
谷歌最近有意将support-v4庫進行拆分,但是無奈v4被引用的地方太多了,但這不失為一個好的開始。目前來看使用拆分後的support庫是沒有什麼優點的,是以我也不建議現在就開始動手,當谷歌和第三方庫作者都開始真的往這方面想的時候,你再開始吧。
減少方法數,不用mulitdex
mulitdex會進行分包,分包的結果自然比原始的包要大一些些,能不用mulitdex則不用。但如果方法數超了,除了插件化和RN動态發包等奇淫巧技外我也沒什麼好辦法了。
使用更小庫或合并現有庫
同一功能就用一個庫,禁止一個app中有多個網絡庫、多個圖檔庫的情況出現。如果一個庫很大,并且申請了各種權限,那麼就去考慮換掉他。
話人人都會說,但如果一個項目是由多個項目成員合作完成的,很難避免重複引用庫的問題,同一個功能用不同的庫,或者一個庫用不同版本的現象比比皆是,這也是很難去解決的。我的解決方案是部門之間多溝通,盡量做base層,base層由少數人進行維護,正如微信在so庫層面的做法:
C++運作時庫統一使用stlport_shared
之前微信中的C++運作庫大多使用靜态編譯方式,使用stlport_shared方式可減小APK包大小,相當于把大家公有的代碼提取出來放一份,減少備援。同時也會節省一點記憶體,加載so的時候動态庫隻會加載一次,靜态庫則随着so的加載被加載多份記憶體映像。
把公用的C++子產品抽成功能庫
其實與上面的思路是一緻的,主要為了減少備援子產品。大家都用到的一些基礎功能,應該抽成基礎子產品。
轉自 : https://gold.xitu.io/post/5851109aac502e0067cfb080