史上最全App瘦身實踐

內容目錄

目標

業務方和開發都希望app盡量的小,本文會給出多個實用性的技巧來幫助開發者進行app的瘦身工作。瘦身和減負雖好,但需要注意瘦身對於項目可維護性的影響,建議根據自身的項目進行技巧的選取。

分析app組成結構

做瘦身之前一定要了解自己app的組成結構,然後要有針對性的進行優化,並且要逐步記錄比對,這樣才能更好的完成此項工作。目前as的2.2預覽版中已經有了apk分析器,功能相當強大,此外你還可以利用nimbledroid來分析apk。nimbledroid是一個強大的工具,推薦一試。

我們都知道apk是由:

  • asserts

  • lib

  • res

  • dex

  • META-INF

  • androidManifest

這幾個部分構成的。下面我會利用as的分析工具,以微信、微博、淘寶為例進行講述。

開始分析後幾秒鐘就能輸出結果,你還可以看到具體類目占的百分比,清晰明了。旁邊的「對比」按鈕提供了diff的功能,讓你可以方便的進行apk優化前後的對比,簡直利器。

assets

assets目錄可以存放一些配置文件或資源文件,比如webview的本地html,react native的jsbundle等,微信的整個assets占用了13.4M。如果你的應用對本地資源要求很少的話,這個文件應該不會太大。

lib

lib目錄下會有各種so文件,分析器會檢查出項目自己的so和各種庫的so。微博和微信一樣只支持了arm一個平台,淘寶支持了arm和x86兩個平台。

淘寶:

resources.arsc:

這個文件是編譯後的二進制資源文件,里面是id-name-value的一個map。因為微信做了資源的混淆,所以這里可以看到資源名稱都是不可讀的。

索性放個微博的圖,易於大家理解:

META-INF

META-INF目錄下存放的是簽名信息,用來保證apk包的完整性和系統的安全性,幫助用戶避免安裝來歷不明的盜版apk。

res

res目錄存放的是資源文件。包括圖片、字符串。raw文件夾下面是音頻文件,各種xml文件等等。因為微信做了資源混淆,圖片名字都不可讀了。

微博就沒有做資源混淆,所以可讀性較好:

dex

dex文件是java代碼打包後的字節碼,一個dex文件最多只支持65536個方法,這也是為什麼微信有了三個dex文件的原因。

因為dex分包是不均勻的,你可以理解為裝箱,一個箱子的大小是固定的,但你代碼的量是不確定的,微信把前兩個箱子裝滿了,最後還剩了2m多的代碼,這些代碼也占用了一個箱子,最終產生了上圖不均勻的結果。現在,我們已經知道了apk中各個文件的大小和它們占的比例,下面就可以開始針對性的進行優化了。

優化assets

assets中會存放資源文件,這個目錄中各個app存放的內容都有所不同,所以優化也比較難。自從引入RN以來,這個目錄下還會有jsbundle的信息。如果你有地址選擇的功能,這里還會存放地址的映射文件(可參考全名k歌)。對於這塊的資源,as是不會進行主動的刪減的,所以一切都是需要靠開發者進行手動管理的。全名k歌中的bundle文件:

刪除無用字體

中文字體是相當大的,我一直不建議將字體文件隨意丟棄到assets中。有時候一個小功能急著上,開發者為了追求速度,可以先放在這里圖省事。但一定要知道這個隱患,並且一定要多和產品核對功能的必要性。此外,對於有些只會用在logo中的字體,我推薦將字體文件進行刪減處理。FontZip是一個字體提取工具,readme中寫到:

經過測試,已經把項目5MB的藝術字體,按需求提取後,占用只有20KB,並且可正常使用。

減少icon-font的使用

icon-font和svg都能完成一些icon的展示,但因為icon-font在assets中難以管理,並且功能和svg有所重疊,所以我建議減少icon-font的使用,利用svg進行代替,畢竟一個很小的icon-font也比svg大呢。我給出一個提供各種格式icon的網站,方便大家進行測試:https://icomoon.io/app/

svg:549字節png:375字節(單一分辨率)ion-font:1.1kb

動態下載資源

字體、js代碼這樣的資源能動態下載的就做動態下載,雖然這樣會有出錯的可能性,複雜度也會提升,但這個對於app的瘦身和用戶來說是有長遠的好處的。如果你用了RN,你就可以在app運行時動態去拉取最新的代碼,將圖片和js代碼一並下載後解壓使用。

壓縮資源文件

有些資源文件是必須要隨著app一並發布的,對於這樣的文件,可以採用壓縮存儲的方式,在需要資源的時候將其解壓使用,下面就是解壓zip文件的代碼:

publicstaticvoidunzipFile(FilezipFile,Stringdestination)throwsIOException{FileInputStreamfileStream=null;BufferedInputStreambufferedStream=null;ZipInputStreamzipStream=null;try{fileStream=newFileInputStream(zipFile);bufferedStream=newBufferedInputStream(fileStream);zipStream=newZipInputStream(bufferedStream);ZipEntryentry;FiledestinationFolder=newFile(destination);if(destinationFolder.exists()){deleteDirectory(destinationFolder);}destinationFolder.mkdirs();byte[]buffer=newbyte[WRITE_BUFFER_SIZE];while((entry=zipStream.getNextEntry())!=null){StringfileName=entry.getName();Filefile=newFile(destinationFolder,fileName);if(entry.isDirectory()){file.mkdirs();}else{Fileparent=file.getParentFile();if(!parent.exists()){parent.mkdirs();}FileOutputStreamfout=newFileOutputStream(file);try{intnumBytesRead;while((numBytesRead=zipStream.read(buffer))!=-1){fout.write(buffer,0,numBytesRead);}}finally{fout.close();}}longtime=entry.getTime();if(time>0){file.setLastModified(time);}}}finally{try{if(zipStream!=null){zipStream.close();}if(bufferedStream!=null){bufferedStream.close();}if(fileStream!=null){fileStream.close();}}catch(IOExceptione){e.printStackTrace();}}}

全名k歌中的assets目錄下我就發現了大量的zip文件:

android上也有一個7z庫幫助我們方便的使用7z。這個庫我目前沒用到,有需求的同學可以嘗試一下。

優化lib

配置abiFilters

一個硬件設備對應一個架構(mips、arm或者x86),只保留與設備架構相關的庫文件夾(主流的架構都是arm的,mips屬於小眾,默認也是支持arm的so的,但x86的不支持)可以大大降低lib文件夾的大小。配置方式也十分簡單,直接配置abiFilters即可:

defaultConfig{versionCode1versionName'1.0.0'renderscriptTargetApi23renderscriptSupportModeEnabledtrue//http://stackoverflow.com/questions/30794584/exclude-jnilibs-folder-from-production-apkndk{abiFilters"armeabi","armeabi-v7a","x86"}}

之後生成的apk中就會排出多餘的平台文件了。armeabi就不用說了,這個是必須包含的,v7是一個圖形加強版本,x86是英特爾平台的支持庫。官方例子:

分析用戶手機的cpu

我們在捨棄so之前需要進行用戶cpu型號的統計,這樣你才能放心大膽的進行操作。我先是花了幾個版本的時間統計了用戶的cpu型號,然後排除了沒有或少量用戶才會用到的so,以達到瘦身的目的。

@NonNullpublicstaticStringgetCpuName(){Stringname=getCpuName1();if(TextUtils.isEmpty(name)){name=getCpuName2();if(TextUtils.isEmpty(name)){name="unknown";}}returnname;}privatestaticStringgetCpuName1(){String[]abiArr;if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.LOLLIPOP){abiArr=Build.SUPPORTED_ABIS;}else{abiArr=newString[]{Build.CPU_ABI,Build.CPU_ABI2};}StringBuilderabiStr=newStringBuilder();for(Stringabi:abiArr){abiStr.append(abi);abiStr.append(',');}returnabiStr.toString();}privatestaticStringgetCpuName2(){try{FileReadere=newFileReader("/proc/cpuinfo");BufferedReaderbr=newBufferedReader(e);Stringtext=br.readLine();String[]array=text.split(":\\s+",2);e.close();br.close();returnarray[1];}catch(IOExceptionvar4){var4.printStackTrace();returnnull;}}

注意:

如果你和我一樣用到了renderscript那麼你必須包含v7,否則會出現模糊異常的問題。如果你用了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安裝在用戶手機中的大小有關,因此我們也是需要多多留意的。

在6.0+中,可以通過如下的方式進行申明:

<applicationandroid:extractNativeLibs=」false」...>

如果想了解更多信息或者想知道這種配置的限制,可以瀏覽下SmallerAPK(8)。

優化resources.arsc

resources.arsc中存放了一個對應關係:

我們在程序運行的時候肯定要經常用到id,因此它在安裝之後仍需要被頻繁的讀取。如果將這個文件進行壓縮,在每次讀取前系統都必須進行解壓的操作,這就會有一些性能和內存的開銷,綜合考慮下這是得不償失的。

刪除無用的資源映射

resources.arsc的正確瘦身方式是刪除不必要的string entry,你可以借助 android-arscblamer 來檢查出可以優化的部分,比如一些空的引用。

進行資源名稱混淆

微信團隊開源了一個資源混淆工具,AndResGuard。它將資源的名稱進行了混淆,所以可以用它對resources.arsc進行優化,只是具體優化效果與編碼方式、id數量、平均減少命名長度有關。表1:

表2:

我們一眼就可以知道表2肯定比表1存儲的字符要小,所以整個文件的大小肯定也要小一些。

關於AndResGuard

這個壓縮工具其實就是一個task,使用也十分簡單,具體的用法請參考中文文檔。原理介紹:安裝包立減1M–微信Android資源混淆打包工具

andResGuard{mappingFile=nulluse7zip=trueuseSign=truekeepRoot=falsewhiteList=[//foryouricon"R.drawable.icon",//forfabric"R.string.com.crashlytics.*",//forumengupdate"R.string.umeng*","R.string.UM*","R.layout.umeng*","R.drawable.umeng*",//umengshareforsina"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

每一個資源文件(res開頭)下面都有一個SHA1-Digest的值。這個值為該文件SHA-1值進行base64編碼後的結果。如果要探究原理,可以看下SignApk.java。這個類中有一段main方法:

publicstaticvoidmain(String[]args){//...//MANIFEST.MFManifestmanifest=addDigestsToManifest(inputJar);je=newJarEntry(JarFile.MANIFEST_NAME);je.setTime(timestamp);outputJar.putNextEntry(je);manifest.write(outputJar);//...}privatestaticvoidwriteSignatureFile(Manifestmanifest,OutputStreamout)throwsIOException,GeneralSecurityException{Manifestsf=newManifest();Attributesmain=sf.getMainAttributes();main.putValue("Signature-Version","1.0");main.putValue("Created-By","1.0(AndroidSignApk)");BASE64Encoderbase64=newBASE64Encoder();MessageDigestmd=MessageDigest.getInstance("SHA1");PrintStreamprint=newPrintStream(newDigestOutputStream(newByteArrayOutputStream(),md),true,"UTF-8");//Digestoftheentiremanifestmanifest.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()){//Digestofthemanifeststanzaforthisentry.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();AttributessfAttr=newAttributes();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

這里有一項SHA1-Digest-Manifest的值,這個值就是MANIFEST.MF文件的SHA-1並base64編碼後的值。後面幾項的值是對MANIFEST.MF文件中的每項再次SHA1並base64編碼後的值。所以你會看到在manifest.mf中的資源名稱在這里也出現了,比如abc_btn_check_material這個系統資源文件就出現了兩次。MANIFEST.MF:

CERT.SF

前者是:4XHnecusACTIgtImUjC7bQ9HNM8=,後者是YFDDnTUd6St4932sE/Xk6H0HMoc=。如果你把前一個文件打開在後面加上\n\r,然後進行編碼,你就會得到CERT.SF中的值。

Map<String,Attributes>entries=manifest.getEntries();for(Map.Entry<String,Attributes>entry:entries.entrySet()){//Digestofthemanifeststanzaforthisentry.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();AttributessfAttr=newAttributes();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沒有壓縮機會外,其餘的兩個文件都可以通過混淆資源名稱的方式進行壓縮。

優化res

資源文件的優化一直是我們的重頭戲。如果要和它進行對比,上文的META-INF文件的優化簡直可以忽略不計。這里的優化會分為兩塊,一個是文本資源(shape、layout等)優化,還有一個就是圖片資源優化。

說明:上圖中有-v4,-v21這樣的文件有些是app開發者自己寫的,但大多都是系統在打包的時候自動生成的,所以你只需要考慮自己項目中的drawable-mdpi、drawable-hdpi、drawable-xhdpi、drawable-xxhdpi即可。

通過as刪除無用資源

在as的任何文件中右擊,選擇清除無用資源即可刪除沒有用到的資源文件。

不要勾選清除id!如果清除了id,會影響databinding等庫的使用(id絕對占不了多少空間)

Tips:

做此操作之前,請務必產生一次commit,操作完成後一定要通過git看下diff。這樣既方便查看被刪除的文件,又可以利用git進行誤刪恢復。

打包時剔除無用資源

shrinkResources顧名思義————收縮資源。將它設置為true後,每次打包的時候就會自動排除無用的資源(不僅僅是圖片)。有了它的幫忙,即使你忘記手動刪除無用的資源文件也沒事。

buildTypes{release{zipAlignEnabledtrueminifyEnabledtrueshrinkResourcestrue//是否去除無效的資源文件proguardFilesgetDefaultProguardFile('proguard-android.txt'),'proguard-rules.txt'signingConfigsigningConfigs.release}rtm.initWith(buildTypes.release)rtm{}debug{multiDexEnabledtrue}}

刪除無用的語言

大部分應用其實並不需要支持幾十種語言的,微信也做了根據地區選擇性下載語言包的功能。作為國內應用,我們可以只支持中文。推薦在項目的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也是一種有損壓縮格式,如果感興趣的話也可以嘗試一下。

統一應用風格,減少shape文件

一個應用的界面風格是必須要統一的,這個越早做越好,最基本的就是統一顏色和按鈕的按壓效果。無UI設計和扁平化風格流行後,倒是給應用瘦身帶來了極大的的福利。界面變得越樸實,我們可以用shape畫的東西就越多。

當你的app統一過每種顏色對應的按下顏色後,接下來就需要統一按鈕的形狀、按鈕的圓角角度、有無陰影的樣子、陰影投射角度,陰影範圍等等,最後還要考慮是否支持水波紋效果。我簡單將按鈕分為下列元素:

上面的各個元素會產生大量的組合,shape和layer-list當然可以做到各種組合,但這樣的話光按鈕的背景文件就有多個,很不好維護。一般為了開發方便,都會把需要用到的各種selector圖片事先定義好,做業務的時候只需要去調用就行。但這大量的selector文件對於業務開發者來說也是有記憶難度的,所以我推薦使用SelectorInjection這個庫,它可以將上面的每個元素進行各種組合,用最少的資源文件來做到大量的按壓效果。用庫雖然好,但庫也會帶來學習成本,所以引入者可以將上述的組合定義為按鈕的一個個的style。因為style本身是支持繼承的,對於這樣的組合形態來說,繼承簡直是一大利器。當你的style有良好的命名後,調用者只需要知道引入什麼style就行,至於你用了什麼屬性別人才不希望管呢。如果業務開發中有一些特別特殊的按壓狀態,沒有任何復用的價值,那你就可以利用庫提供的豐富屬性在layout文件中進行做到,再也不用手忙腳亂的到處定義selector文件了。

我將不能繼承和不靈活的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的便利性。

限制靈活性,減少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的重構功能自動完成,絕不拖泥帶水。

<!--使用include--><includeandroid:id="@+id/bottom_ub"layout="@layout/demo_uiblock"android:layout_width="match_parent"android:layout_height="100dp"/>

  1. ListHeader

publicvoidaddHeaderToListView(ListViewlistView,Viewheader){if(header==null){thrownewIllegalArgumentException("Can'taddanullheaderviewtoListView");}ViewGroupviewParent=(ViewGroup)header.getParent();viewParent.removeView(header);AbsListView.LayoutParamsparams=newAbsListView.LayoutParams(header.getLayoutParams().width,header.getLayoutParams().height);header.setLayoutParams(params);listView.addHeaderView(header);//add}

我將listView和它的沒有復用價值的header放到了同一個layout中,然後在activity中利用上述代碼進行了操作,最終完成了用一個layout文件給listView加頭的工作。這段代碼我很久沒動過了,有利有弊,放在這里我也僅僅是舉個例子,希望可以幫助大家擴展下思路。

動態下載圖片

做過濾鏡和貼紙的同學應該會注意到貼紙、表情這類的東西是相當大的,對於這類的圖片資源我強烈建議通過在線商店進行獲取。這樣既可以讓你踏踏實實的賣貼紙,又可以減小應用的大小。這麼做雖然有一定的複雜度和出錯概率,但投入產出比還是很不錯的。

分門別類放置不同分辨率的圖片這個雖然不算是app大小的優化,但是如果你放錯了圖片,對於app啟動時的內存大小會有一定的影響:

思考一下,如果把一個本來應該放在drawable-xxhdpi里面的圖片放在了drawable文件夾中會出現什麼問題呢?在xxhdpi設備上,圖片會被放大3倍,圖片內存占用就會變為原來的9倍!

國內也有很多人說可以用一套圖片來做,不用出多套圖,借此來達到app瘦身和給設計減負的目的。Google官方是建議為不同分辨率出不同的圖片,為此國內也有不少文章討論過這件事情,這篇總結的不錯推薦一讀。每次說到這個話題的時候總有很多人有不同的看法,況且很多人還不知道.9圖也是需要切多份的,所以這里我還是先分析一下大廠的放圖策略,最後咱們再討論下較優的方案。

1. 淘寶

mdpi:

mdpi中存留了一些android原始的icon,這個從命名和前綴就能看出來。通過圖片大小分析,這個目錄下面都是一些很小的icon,還有一些沒有用到的icon(這個launcher圖片也很好的說明了淘寶的歷史)。hdp:

hdp中分為兩部分:表情和其他圖片。f+數字的圖片都是表情圖片,淘寶僅僅有一套表情圖片,並且都放在這個目錄下。除了少量的圖片和mdip的圖片一致(比如用戶頭像的place_holder)外,其餘的圖片和mdpi的圖片完全不同。順便說一下,此目錄下除了表情之外,其餘的都是一些小icon,絕對屬於尺寸很小的那類。xhdpi:

xhdpi又和hdpi不同了,它里面有大量的國家icon。除此之外就是一些對清晰度要求較高的icon。xxhdpi:

xxhdpi就沒什麼東西了,幾張圖而已。其他:

有後綴的文件夾中除了5張左右的淘寶自己的icon外,其餘都是系統的圖片,均以abc開頭。我不清楚淘寶到底有沒有使用到這些圖片,但我可以肯定地說其中有著冗餘圖片,或許有著進一步優化的方案。

總結:

淘寶的放置圖片策略是大量的圖片在hdpi,xhdpi中,比如表情圖在hdpi中,國家圖在xhdpi中,大多數圖片都僅有一套,少數全局的icon是會有多張的情況(極少,可能只有十幾張)。xxhdpi和mhdpi僅僅作為補充,沒有太大的作用。淘寶最令人好奇的點在於它的資源文件很小,但是so文件相當大:

2. 微博

微博是一個典型的android風格的app,它的drawable全都是有後綴的,完全符合安卓標準的默認打包策略,它還有根據像素密度的圖片,甚至有ldpi的目錄。

mdpi-v4

mdpi中有大量的小icon,里面有個叫做share_wx的,從名字一下子就知道是微信分享的icon,但實際是微博的logo,比較有趣。其餘的都是一些邊邊角角的圖標,量不大,所以主力圖肯定不在這里。hdpi-v4

這是微博圖片存放的主要目錄,有很多大背景和表情,微博的表情圖片和淘寶一樣都是在hdpi中的,它以lxh,emoji等前綴開頭,用來區分不同風格的表情。xhdpi-v4和xxhdpi-v4*

這里放了一些背景大圖,我也發現了大量和hdpi中一樣的圖片,所以可以大膽的假設微博是做了不同像素的圖片的。這也證實了我的想法——微博是很標準的android應用。ldpi-v4

這個目錄的確沒什麼用,微博自身也不會維護這個目錄,這全都是第三方庫和應用商店給的圖片,微博開發者只需要放進來就好。sw400dp和sw32dp-400dp

這些目錄放了一些為不同分辨率準備的長得相同的icon,當然還有微博自己的logo。

3. 微信

通過上面的分析,我們是不是可以得出一些經驗了呢?

  • 大量的圖片都在hdpi和xdpi中

  • 表情圖片在hdpi中

  • anim目錄中都是xml文件

  • drawable目錄中有大量的xml和少量的png和.9圖

  • layout文件中是全部的xml

  • raw中放置音頻

  • svg圖片在raw、drawable或assets中

  • 最大的文件夾是圖片文件

  • layout文件較小

  • 相同的圖片,高分屏的肯定比低分屏的大

ok,現在咱們就可以來看微信的資源了,混淆怕什麼,友盟混淆的abcd代碼都能看懂,微信的adcd資源也應該不難。raw(a9)

這個目錄中放置了大量的svg圖片和mp3文件,從專業的角度來想,drawable目錄下肯定不會放mp3,所以這個肯定是raw文件了。這個目錄下有大量的svg,所以可以看出微信已經做到了全svg化,並且已經在線上穩定運行了。這點在微信早期的公眾號上也可以得到佐證,詳細請看Android微信上的SVG。文中提到了:

第一步,拿到.svg後綴的資源文件(UI很容導出這種圖片),放在raw目錄下而不是drawable目錄。第二步,把 R.drawable.xxx 換成 R.raw.xxx;把 @drawable/xxx 換成 @raw/xxx。layout(f)

現在有了兩個線索,那麼初步可能上面的三個目錄肯定是我們常見的目錄,否則不會那麼大。

打開f後發現這個就是layout文件,所以其餘的肯定就是圖片文件了。hdpi(y)

y是2.9m,里面有大量的表情,所以我判斷它是hdpi,為了更加證實這個猜測,我找到了兩張相同的圖片。a2中:

y中:

y中圖片是11kb,a2中圖片是76kb,這明顯說明y是hdpi,a2是xhdpi。xhdpi(a2):

xhdpi中的圖片和hdpi中的圖片相同的不多,微信在這里放的是一些大圖。drawable(k)

drawable本身沒什麼可以說的,但是微信的drawable中.9圖份額很少,所以我在想是否svg可以在一定的程度上完成一些.9的工作呢?總結:

微信的表情都在hdpi中,僅有一套圖片,這點幾乎已經成為了標準微信已經做到了svg化,svg圖片在raw中微信傾向於把較大的圖片放在xhdpi中,僅出一套圖微信和淘寶一樣都是盡量選擇一套圖來完成需求

優化思路

通過分析得出,傳統的出多個分辨率圖片的做法在大廠中已經發生了改變,阿里系、騰訊系的產品都採用了一套圖走天下的路子。這樣的做法還是有利有弊的,權衡之下我給出如下建議:

  • 聊天表情就出一套圖,放在hdpi中

  • 純色小icon用svg做

  • 背景等大圖,出一套放在xhdpi中

  • logo等權重較大的圖片可針對hdpi,xhdpi做兩套圖

  • 如果某些圖在真機中確實展示異常,那就用多套圖

  • 如果遇到奇葩機型,可針對性的補圖

成年人不看對錯,只看利弊,所以還請大家權衡一二。

優化圖片

對於圖片的優化應該是放在優化res一節中進行講解的,但是因為圖片這塊比重太大了,所以我讓其獨立成為一節。本節主要會從圖片格式、復用圖片和壓縮圖片三個方面進行講解。

使用VectorDrawable

想要做好圖片的優化工作最重要的一點是知道應該選擇什麼樣的圖片格式,對於這點我推薦一個視頻,方便大家進行深入的了解。

這是Google給出的建議,簡單來說就是:VD->WebP->Png->JPG

如果是純色的icon,那麼用svg如果是兩種以上顏色的icon,用webp如果webp無法達到效果,選擇png如果圖片沒有alpha通道,可以考慮jpg

VD即VectorDrawable,android上的svg做到類。在經歷了長達半年的緩慢兼容之路後,現在終於被support庫兼容了,官方文檔中給出了這樣一個例子:

//GradlePlugin2.0+android{defaultConfig{vectorDrawables.useSupportLibrary=true}}<ImageViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"app:srcCompat="@drawable/ic_add"/>

配置好後,我們就可以利用強大的svg來替換純色icon了。

svg轉VectorDrawable

先去這里下載svg圖片:https://icomoon.io/app/#/select

然後利用這個在線工具轉換成VectorDrawable。

svg的兼容性

support庫的代碼質量還是不錯的,但是svg畢竟是一個圖片格式,所以使用svg前還是需要格外慎重的。我寫了一個demo,把用到的所有屬性都做了示例,然後利用雲測服務進行兼容性測試。

測試svg也挺簡單的,首先看會不會崩潰,然後看各個分辨率、各個api下是否會有顯示不正常的情況,如果都ok,那麼就可以準備引入到項目里面了。具體的測試代碼在SelectorInjection,我測試下來100%通過。

svg的使用技巧
設置恰當的寬高

svg圖片是有默認寬高的,設計也會給出一個默認寬高,設置一個合適的默認寬高對以後的圖片復用會有很大幫助。

TextView中drawableLeft等屬性是不能設置圖片的寬高的,但ImageView可以。如果你的圖片會被復用,建議將圖片的寬高設置為TextView中的drawable寬高。

利用padding和scaleType屬性

ImageView中的svg默認情況下是會隨著控件的大小而改變的,它不會像png那樣保持自己的原始大小。我們可以利用這一特性,再配合padding和scaleType屬性來完成各種效果。

圖1:60×60,svg自動鋪滿控件圖2:30×30,svg被壓縮到原始大小以下圖3:60×60,使用scaleType,讓svg保持原始寬度圖4:60×60,使用padding,對svg進行任意比例的壓縮

svg的問題和解決方案

svg有很多好處,但也免不了一些問題,本小節中寫代碼的猴子提出了一些很實用的建議,感謝他的幫助。

容易寫錯屬性

svg的支持是要通過app:srcCompat這個屬性來做的,如果稍微一不注意寫成了src,那麼就會出現低版本手機上不兼容的問題。你可以嘗試通過配置Lint規則或是利用腳本進行文件的遍歷等方式來防止出現因開發寫錯屬性而崩潰的問題。

不兼容selector

將svg放入selector中的時候可能會出現一些問題,stackoverflow上也給出了解決方案,就是下面這段代碼放在Activity中。

static{AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);}

開啟這個flag後,你就可以正常的使用Selector這樣的DrawableContainers了。

不支持自定義控件

AppCompatActivity會自動將xml文件中的ImageView替換為AppCompatImageView,但是如果用了你的自定義控件,那麼這種機制就無效了,所以自定義控件中盡量使用AppCompatImageView來代替ImageView,使用 setImageResource()來設置資源。如果你的自定義控件中需要獲得drawable或者是有自定義需求,那麼可以參考AppCompatImageView中的svg的helper類來編寫。

不兼容第三方庫

市面上有很多優秀的圖片加載庫,它們一般都會支持多種圖片路徑的加載,比如磁盤圖片,網路圖片,res圖片等等,對於svg這樣的圖片格式,它們是否支持就要大家結合自己的圖片框架進行調研了。像glide目前(2016.09.10)就不支持加載本地的svg圖片,詳見: Vector drawable can’t be used as error drawable 。PS:因為第三方庫更新十分頻繁,建議在用到svg的時候再調研。

性能問題

關於動畫和性能方面的問題,《Android Vector曲折的兼容之路》中給出了具體的示例和建議:

Bitmap的繪制效率並不一定會比Vector高,它們有一定的平衡點,當Vector比較簡單時,其效率是一定比Bitmap高的,所以為了保證Vector的高效率,Vector需要更加簡單,PathData更加標準、精簡,當Vector圖像變得非常複雜時,就需要使用Bitmap來代替了。

Vector適用於icon、Button、ImageView的圖標等小的icon,或者是需要的動畫效果,由於Bitmap在GPU中有緩存功能,而Vector並沒有,所以Vector圖像不能做頻繁的重繪。Vector圖像過於複雜時,不僅僅要注意繪制效率,初始化效率也是需要考慮的重要因素。這點可以參考微信中的svg。SVG加載速度會快於PNG,但渲染速度會慢於PNG,畢竟PNG有硬件加速,但平均下來,加載速度的提升彌補了繪制的速度缺陷。

不便於管理

svg是沒辦法在文件目錄下進行預覽的,其放置的目錄也和其他圖片不同,如果沒有做好管理工作,未來的drawable目錄就會變得越發的混亂。其實,對於目錄或者包內文件的管理有個很簡單的原則:同目錄多類型文件,以前綴區分;不同目錄,同類型文件,以意義區分。drawable目錄下有多種類型的文件,我們利用英文排序的原則將這些文件簡單分為svg、.9圖、shape、layer-list這幾類。

通過規範特定的前綴,就可以形成一個便於查找和理解的目錄樹,以達到分類的目的。

在特定前綴的規範下,次級分類的命名就可以按照功能可能用途來做區分,比如button或share的icon就可以用不同的前綴來標識。強烈建議開發和設計定一個命名標準,這樣開發就不用對設計出的圖片進行重命名了,而且還可以保證兩個部門有一致的認知。這個僅僅是一個簡單的分類方法,在實際中需要靈活使用。如果一些文件都是用於某種特定類型的,那麼可以自定義前綴。比如我對於按鈕使用的形狀就用了btn作為前綴,而忽略了它們本身的文件類型。

不便於預覽

svg是一個特殊格式的文件,可預覽性大大低於png等常用的圖片格式,但幸好win下可以直接在文件目錄下預覽svg圖像,效果十分不錯。

但是vd是一個xml文件,預覽器很難識別出這是什麼格式,所以對於vd的預覽才是一個難點。我之後準備寫一個as插件來解決這個問題。

使用WebP

webp作為一種新的圖片格式,從Android4.0+開始原生支持,但是不支持包含透明度,直到4.2.1+才支持顯示含透明度的webp,使用的時候要特別注意。webp相比於png最明顯的問題是加載稍慢,不過現在的智能設備硬件配置越來越高,這點差異越來越小。騰訊之前有一篇對於webp的分析文十分不錯,如果你準備要用webp了,那麼它絕對值得一看。注意:如果你的項目最低支持到4.2.1,那麼你可以繼續閱讀了,如果項目還需要支持到4.0版本,我建議暫時不要上webp,成本太高。

png轉webp

我們可以通過智圖或者isparta將其它格式的圖片轉換成webP格式。

webp的問題
兼容性不好

官方文檔中說只有在4.2.1+以上的機型,才能解析無損或者有透明度調整的webp圖片,4.0+才開始支持無透明度的webp圖片。我通過雲測發現,在4.0~4.2.1的系統中,帶有透明度的webp圖片雖然不會崩潰,但是完全無法顯示。

《APK瘦身記,如何做到高達53%的壓縮效果》一文中也提到有alpha值的jpg圖片,經過webp轉換後,無法在4.0,4.1的Android系統上運行的問題,具體原因見官方文檔:

除了兼容性問題外,webp在某些機型和rom上可能會出現一些「神奇」的問題。在三星的部分機型上,部分有alpha通道的圖中會有一條很明顯的黑線(三星的rom對於shape的alpha的支持也有問題,是紅線)。在小米2刷成4.xx的手機上,系統未能正確識別xml文件中描述的webp圖片,也會導致加載webp失敗。

不便於預覽

因為webp的圖片格式是很難預覽的,as也沒有辦法直接預覽webp格式,我一般是通過chrome瀏覽器打開webp,十分不方便。

我們知道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可以復用到很多地方。

上圖中我通過組合的方式將長得一樣的icon(facebook、renren等)復用到了不同的界面中,不僅做到了效果,可維護性也不錯。

使用Tint

著色器(tint)是一個強大的工具,我將其和shape、svg等結合後產生了化學反應。TintMode共有6種,分別是:add,multiply,screen,srcatop,srcin(默認),src_over。下圖是一篇文章中的總結,說明了其靈活性

一般用默認的模式就可以搞定大多數需求了,使用到的控件主要是TextView和ImageButton。ImageButton官方已經給出了支持方案,TextView因為有四個Drawable,官方的tint屬性在低版本又不可用,所以我讓SelectorTextView支持了一下。如果你想要了解具體的兼容方法,可以參考庫代碼或《Drawable 著色的後向兼容方案》。

ImageButtonandroid:tint="@color/blue"SelectorTextViewapp:drawableLeftTint="@color/orange"app:drawableRightTint="@color/green"app:drawableTopTint="@color/green"app:drawableBottomTint="@color/green"

因為我用了SelectorTextView和SelectorImageButton,所以我對於背景的tint沒有什麼需求,也就沒做兼容性測試,有興趣的同學可以嘗試一下。如果你決定要採用tint,一定要通過雲測等手段做下兼容性測試,下圖是我對於上述屬性的測試結果:

復用按壓效果

一個應用中的list頁面都應該做一定程度的統一,對於有限長度的list,我們可能偏向於用ScrollView做,對於無限長的list用RecyclerView做,但對於它們的按壓效果我強烈建議採用同一個樣式。

以微信為例,它的所有列表都是白色的item,我的優化思路如下:

  • 列表由LinearLayout、RecyclerView組成

  • 分割線用統一的shape進行繪制,不用切圖

  • 整個列表背景設置為白色

  • item的背景是一個selector文件,正常時顏色是透明,按下後出現灰色

通過旋轉來復用

如果一個icon可以通過另一個icon的旋轉變換來得到,那麼我們就可以通過如下方法來做到:

<?xmlversion="1.0"encoding="utf-8"?><rotatexmlns:android="http://schemas.android.com/apk/res/android"android:drawable="@drawable/blue_btn_icon"//原始iconandroid:fromDegrees="180"//旋轉角度android:pivotX="50%"android:pivotY="50%"android:toDegrees="180"/>

這種方法雖好,但是不要濫用。需要看代碼的人知道這種思路,否則會出現不好維護的情況。當設計真的是認為兩個圖有如此的關係的時候才可這樣做到,萬不可耍小聰明。

壓縮圖片

圖片的壓縮策略是:

  • 優先壓大圖,後小圖

  • 不壓.9圖(svg在俠義上不算圖)

  • 對於開屏大圖片的壓縮需注意力度,要和設計確認後再做

  • 對於體積特別大(超過50k)的圖片資源可以考慮有損壓縮

關於如何量化兩張圖片在視覺上的差別,Google 提供了一個叫butteraugli的工具,有興趣的同學可以嘗試一下。

ImageOptim

mac上超好用的圖片壓縮工具是ImageOptim,它集成了很多好用圖片壓縮庫,很多blog中的圖片也是用它來壓縮的。

值得一提的是,借助Zopfli,它可以在不改變png圖像質量的情況下使圖片大小明顯變小。

pngquant

pngquant也是一款著名的壓縮工具,對於png的療效還不錯,但它不一定就適合app中那種背景透明的小icon,所以對比起tinypng來說,優勢不明顯。

tinypng

tinypng是一款相當著名的商用壓縮工具,tinypng提供了開放接口供開發者開發屬於自己的壓縮工具(付費服務)。tinypng對於免費用戶也算友好,每月可以免費壓縮幾百張圖片。我用gradle插件來使用tinypng,更加簡單方便。我一般的做法是發版本前才做一次圖片壓縮,每次debug的時候是直接跳過這個task的,完全不影響日常的debug。

tinyinfo{apiKey='xxxxxxxxx'//編譯時是否跳過此taskskip=true//是否列印日志isShowLog=true}

有人說tinypng的缺點是在壓縮某些帶有過渡效果(帶alpha值)的圖片時,圖片可能會失真,對於這種圖片你可以將png圖片轉換為webP格式。

注意事項

aapt默認會在打包時進行圖片的壓縮工作(無論你知不知道,它一直在默默的工作),如果你已經做了圖片壓縮了,那麼建議手動禁止這個功能,否則「可能會」出現圖片二次壓縮後反而變大的情況,原因請看:Smaller PNGs, and Android’s AAPT tool。

android{defaultConfig{//...}aaptOptions{cruncherEnabled=false}}

優化dex

dex本身的體積還是很可觀的,雖說代碼這東西不占用多少存儲空間,但是微信這樣的大廠的dex已經達到了20多M。我大概可能了一下,如果你沒有達到方法數上限,那麼你的dex的大小大約是10M。縱觀應用市場,沒有用multiDex的又有幾家呢?

記錄方法數和代碼行數

dexcout

要優化這部分,首先需要對公司的、android庫的、第三方庫的代碼進行深入的了解,我用了dexcount來記錄項目的方法數:

dexcount{format="list"includeClasses=falseincludeFieldCount=trueincludeTotalMethodCount=falseorderByMethodCount=falseverbose=falsemaxTreeDepth=Integer.MAX_VALUEteamCityIntegration=false}

通過分析你可以知道代碼的具體情況了,比如某個第三方庫是否已經不用了、自己項目的哪個包的方法數最多、目前代碼情況是否合理等等。

statistic

我是通過Statistic這個as插件來評估項目中開發人員寫的代碼量的,它生成的報表也不錯:

現在我可以知道:

  • 哪些類空行數太多,是不是沒有按照代碼規範來;

  • 哪些類的代碼量很少,是否有存在的必要;

  • 哪些類行數過多,是否沒有遵守單一職責原則,是否可以進行進一步的拆分

apk method

你還可以用apk-method-count這個工具來查看項目中各個包中的方法數,它會生成樹形結構的文檔,十分直觀。

利用Lint分析無用代碼

如果你想刪掉沒有用到的代碼,可以借助as中的Inspect Code對工程做靜態代碼檢查。

Lint是一個相當強大的工具,它能做的事情自然不限於檢查無用資源和代碼,它還能檢測丟失的屬性、寫錯的單位(dp/sp)、放錯像素目錄的圖片、會引起內存溢出的代碼等等。從eclipse時代發展到現在,lint真的是越來越方便了,我們現在只需要點一點就行。Lint的強大也會帶來相應的缺點,缺點就是生成的信息量過多,不適合快速定位無用的代碼。我推薦的流程是到下圖中的結果中直接看無用的代碼和方法。

注意:這種刪除無用代碼的工作需要反復多次進行(比如一月一次)。當你刪除了無用代碼後,這些代碼中用到的資源也會被標記為無用,這時就可以通過上文提到的Remove Unused Resources來刪除。

通過proguard來刪除無用代碼

手動刪除無用代碼的流程太繁瑣了,如果是一兩次倒還會帶來刪除代碼的爽快感,但如果是專人機械性持續工作,那個人肯定要瘋的。為了保證每次打包後的apk都包含盡可能少的無用代碼,我們可以在build.gradle中進行如下配置:

android{buildTypes{release{minifyEnabledtrue//是否混淆}}}

雖然這種方式成果顯著,但也需要配合正確的proguard配置才能起作用,推薦看下讀懂 Android 中的代碼混淆一文。這種利用混淆來刪除代碼的方式是一種保險措施,真正治本的方法還是在開發過程中隨手刪除無用的代碼,畢竟開發者才是最清楚一段代碼該不該被刪的。我之前就是隨手清理了下沒用的代碼,然後就莫名其妙的不用使用mulitdex了。

剔除測試代碼

我們在測試的時候可能會隨便寫點測試方法,比如main方法之類的,並且還會引入一些測試庫。對於測試環境的代碼gradle提供了很方便的androidTest和test目錄來隔離生產環境。對於測試時用到的大量庫,可以進行test依賴,這樣就可以保證測試代碼不會污染線上代碼,也可以防止把測試工具、代碼等發布到線上等錯誤(微博就幹過這樣的事情)。

//DependenciesforlocalunitteststestCompile'junit:junit:4.12'testCompile'org.hamcrest:hamcrest-junit:2.0.0.0'//AndroidTestingSupportLibrary'srunnerandrulesandroidTestCompile'com.android.support:support-annotations:24.1.1'androidTestCompile'com.android.support.test:runner:0.5'androidTestCompile'com.android.support.test:rules:0.5'//EspressoUITestingandroidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2',{excludegroup:'com.android.support',module:'support-annotations'})

PS:在layout中利用tools也是為了達到上述目的。

區分debug/rtm/release模式

debug模式是開發者的調試模式,這個模式下log全開,並且會有一些幫助調試的工具(比如:leakcanary,stetho),我們可以通過debugCompile和releaseCompile來做不同的依賴,有時候也會需要no-op(關於no-op的內容可以參考下開發第三方庫最佳實踐)。

debug和release是android本身自帶的兩種生產環境,在實際中我們可能需要有多個環境,比如提測環境、預發環境等,我以rtm(Release to Manufacturing 或者 Release to Marketing的簡稱)環境做例子。首先在目錄下創建rtm文件:

復刻release的配置:

buildTypes{release{zipAlignEnabledtrueminifyEnabledtrueshrinkResourcestrue//是否去除無效的資源文件proguardFilesgetDefaultProguardFile('proguard-android.txt'),'proguard-rules.txt'signingConfigsigningConfigs.release}rtm.initWith(buildTypes.release)rtm{}debug{multiDexEnabledtrue}}

配置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的替換。

<?xmlversion="1.0"encoding="utf-8"?><manifestpackage="com.kale.example"xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"><applicationandroid:name=".RtmApplication"android:allowBackup="true"android:icon="@drawable/rtm_icon"tools:replace="android:name,android:icon"/></manifest>

現在我可以將環境真正需要的代碼打包,不需要的代碼全部剔除,以達到瘦身的目的。

使用拆分後的support庫

Google最近有意將support-v4庫進行拆分,但是無奈v4被引用的地方太多了,但這不失為一個好的開始。目前來看使用拆分後的support庫是沒有什麼優點的,所以我也不建議現在就開始動手,當Google和第三方庫作者都開始真的往這方面想的時候,你再開始吧。

減少方法數,不用mulitdex

mulitdex會進行分包,分包的結果自然比原始的包要大一些些,能不用mulitdex則不用。但如果方法數超了,除了插件化和RN動態發包等奇淫巧技外我也沒什麼好辦法了。

使用更小庫或合併現有庫

同一功能就用一個庫,禁止一個app中有多個網路庫、多個圖片庫的情況出現。如果一個庫很大,並且申請了各種權限,那麼就去考慮換掉他。

話人人都會說,但如果一個項目是由多個項目成員合作完成的,很難避免重復引用庫的問題,同一個功能用不同的庫,或者一個庫用不同版本的現象比比皆是,這也是很難去解決的。我的解決方案是部門之間多溝通,盡量做base層,base層由少數人進行維護,正如微信在so庫層面的做法:

C++運行時庫統一使用stlport_shared

之前微信中的C++運行庫大多使用靜態編譯方式,使用stlport_shared方式可減小APK包大小,相當於把大家公有的代碼提取出來放一份,減少冗餘。同時也會節省一點內存,加載so的時候動態庫只會加載一次,靜態庫則隨著so的加載被加載多份內存映像。

把公用的C++模塊抽成功能庫

其實與上面的思路是一致的,主要為了減少冗餘模塊。大家都用到的一些基礎功能,應該抽成基礎模塊。

總結

app的瘦身是一個長期並且艱巨的工作,如果是小公司建議一個月做一次。大公司的話一般都會對app的大小進行持續的統計和追蹤,有餘力的小公司也可以多多借鑒一下。希望大家閱讀完本文後可以著手對項目進行優化工作,帶來真正的收益。

原創作者:天之界線2010,原文:https://www.jianshu.com/p/8f14679809b3

分享到Facebook
加入LINE好友

 


不知道如何找適合的對象?歡迎加官方LINE → Line ID:@shesay
戀愛小秘書免費一對一諮詢!
✔追蹤我的YouTube:https://www.youtube.com/@datenami
✔追蹤我的TikTok:https://www.tiktok.com/@datnami

 

配對成功的關鍵:參加實體交友活動

erose主題派對與戀愛小秘書創辦人娜米表示:「透過各種有趣的實體活動,不僅能親眼真實見到異性,也能在活動進行中讓大家很輕鬆自然的認識彼此、聊天互動,能更快速的找到適合的對象。」

結合大數據用心篩選 + 客製化條件配對

戀愛小秘書團隊已經成功替4000位以上的未婚男女配對成功,這個驚人成果背後的秘密在於「高度客製化服務」,跟每位客戶深度訪談,瞭解客戶真正的特質及需求,從「契合度」提高速配率。

訪談結果結合專屬的人格分析測驗與數據配對分析,精緻化的操作,締造高速配率!

除此之外,戀愛小秘書團隊還會定期追蹤客戶的後續狀況,目的是希望協助客戶發展長期且穩定的伴侶關係。

實名認證防造假!隱私保護最安心!

採用「實名認證」的制度,不僅是把關顧客的身份,避免已婚人士或動機不單純者的加入,更對客戶資料嚴格保密,讓客戶們能在安全且有隱私的狀況下認識另一半。

多元有趣的主題活動,豐富你的社交生活

戀愛小秘書團隊每個月都會規劃豐富多元的實體活動,從戶外踏青、娛樂遊戲、手作、料理課程到桌遊活動,希望客戶們能從歡樂的氣氛中認識彼此。

透過實體活動讓大家先有初步的接觸,然後再為會員們做「客製化」的約會安排。

另外針對想提升自身魅力的客戶,也有投資理財、形象穿搭等講座可供選擇。

追求脫單,先勇敢跨出你的第一步

許多單身者為了心中理想的對象條件,在還沒認識新朋友時,就先限制了自己。建議以認識新朋友的心態,積極參與活動,並適當的設限,才能真正為自己帶來戀愛的機會!勇敢跨出第一步吧!

♡ 現在就和戀愛小秘書娜米聊聊吧Line ID:@shesay

♡ 追蹤娜米的臉書粉絲團

她來報好康

 

SheSay 專注在 兩性、愛情等領域
建立專屬女生觀點的品牌形象
堅持「在第一時間掌握男女的時事議題」
將時下最流行的話題網羅、呈現。

馬上測算你的戀愛密碼

戀愛小秘書-娜米

單身很久?一直被分手?
從生日就看出你的戀愛疑難雜症!
娜米的戀愛數字密碼來幫你了。