最近大半個月都在做app的優(yōu)化泛烙,最主要是從apk包大小理卑、界面過度繪制、掉幀胶惰、內(nèi)存抖動傻工、主線程IO這幾個方面來入手的。相比開發(fā)新功能孵滞,做優(yōu)化真的是更費腦力和心神中捆,因為也許你做了大量的修改和優(yōu)化操作,能看見的效果卻微乎其微坊饶,但又不得不做泄伪。我想對于大多數(shù)Android開發(fā)者來說,開發(fā)出一款app并不難匿级,但是開發(fā)出一款高性能體驗棒的app卻并不是每個開發(fā)者都能做到的蟋滴。在開發(fā)過程中染厅,可能會因為各種各樣的原因會使你開發(fā)出來的app性能不佳,體驗很差津函,開發(fā)者寫代碼的水平肯定是最重要的因素肖粮,另外還有一些第三方SDK也可以能會引起你的app出現(xiàn)問題。
今天抽空把這大半個月以來優(yōu)化的過程和心得記錄一下尔苦。
apk包大小
無論是個人開發(fā)者自己開發(fā)的產(chǎn)品還是公司開發(fā)的產(chǎn)品涩馆,盡可能減小apk包的大小可以大大提高app的下載轉化率,所以優(yōu)化過程中apk體積是開發(fā)者必須要注意的一點允坚。我們先看看apk包是由哪些部分組成的魂那。我們可以使用Android Studio自帶的功能Build->Analyze APK,下圖是微信的apk組成:
- lib 存放app所需的native庫文件
- classes.dex 開發(fā)者編寫的java文件最后都會轉化成dex文件運行在Android虛擬機上
- assets 存放需要保持原始文件的資源文件
- res 存放所有的資源文件
- resource.arsc 所有資源文件的id映射
- META-INF 簽名校驗文件
- AndroldManifest.xml Android應用全局配置文件
- 其它一些配置文件和第三方庫生成的文件
刪除無用資源
首先我從資源文件入手稠项。我們可以使用Android Studio中的工具搜索項目中沒有使用的的資源文件:
然后通過unused resources這個功能來查找:
查找完成后會把整個項目中未被使用的資源列出來涯雅,但是這個清理的時候我們需要注意下,有些資源文件可能是在你的項目中有用到的展运,但是資源id未出現(xiàn)在項目中的活逆,比如你在代碼中是通過資源名來獲取這個資源而不是通過資源id,這種情況下的資源就不能刪了拗胜,否則就會出錯划乖。另外一些第三方庫的資源文件也要小心誤刪。
關于保留幾種屏幕分辨率的資源文件
隨著目前手機市場的發(fā)展挤土,屏幕越來越大琴庵,分辨率越來越高,但是基于成本的考慮仰美,仍然還有一些相對較低的分辨率的機型迷殿,而我們的app是適配所有分辨率的機型,還是針對一些主流的機型做適配咖杂,這取決于我們app所適用的人群范圍庆寺。我們UI設計師一般是設計xxhdpi(480dpi)和xhdpi(320dpi)兩套,基本上可以滿足絕大部分的手機屏幕了诉字,如果你的app對apk體積有極高的要求懦尝,那么你也可以只選擇一套分辨率的素材。
資源壓縮
圖片壓縮:一般UI給我們的圖片都可以壓縮壤圃,我一般采用tinypng在線壓縮陵霉,支持png/jpg格式,為了避免失真伍绳,不要對同一張圖片壓縮多次踊挠。如果是Mac系統(tǒng)下,還可以選擇ImageOptim冲杀。
使用SVG圖片:SVG圖片即矢量圖效床,簡單的說睹酌,就是縮放不失真的圖像格式。使用矢量圖的好處有很多剩檀,可被非常多的工具讀取和修改(比如記事本)憋沿。SVG與JPEG和GIF圖像比起來,尺寸更小沪猴,且可壓縮性更強卤妒。SVG是可伸縮的,SVG圖像可在任何的分辨率下被高質(zhì)量地打印字币,SVG可在圖像質(zhì)量不下降的情況下被放大,SVG圖像中的文本是可選的共缕,同時也是可搜索的(很適合制作地圖)洗出,SVG可以與Java技術一起運行,SVG文件是純粹的XML图谷。使用Android Studio也可以將下載的svg格式的矢量圖轉化為xml格式翩活。比如我一般在阿里的Iconfont上下載一些簡單的圖標蔓挖,它可以選擇svg格式下載缓艳,然后在Android Studio的drawable文件夾右鍵NEW->Vector Asset:
然后會進到下面這個界面:
其中Asset Type類型中Material Icon是通過系統(tǒng)自帶的一些符合Material Design設計的icon來制作,Local file就是通過我們自己下載的svg文件來制作禀梳,完成后svg格式的文件就轉化成xml格式保存在我們的目標文件夾里了承璃。當然普通的ImageView是無法使用矢量圖的利耍,必須使用v7包下的AppCompatImageView,而且不能使用src屬性盔粹,必須是app:srcCompat屬性才可以隘梨。
用jpg代替png:因為jpg沒有alpha通道,所以文件更小舷嗡,比較適合于不需要透明度的圖片轴猎。
用shape、color來代替圖片:如果是漸變背景或者純顏色的控件背景进萄,都可以使用shape或者color做背景捻脖,一個xml文件相比一張位圖要小得多。
使用混淆
混淆除了代碼壓縮代碼混淆的功能還有資源壓縮的作用中鼠,在app module的build.gradle文件中配置shrinkResources true和minifyEnabled true這兩個參數(shù)可婶,就可以達到混淆壓縮的目的。關于混淆援雇,參看這篇文章 Android代碼混淆與進階
native庫文件
native庫都是為了支持不同架構的CPU扰肌,雖然native庫文件是占整個apk體積最大的部分,減少對其中一些CPU架構的支持可以很直觀的看到變化熊杨。關于so文件的知識和它的適配曙旭,可以看看下面幾篇文章盗舰,會有很大幫助。
過度繪制
首先我們得知道桂躏,過度繪制這個概念钻趋,到底什么是過渡繪制?UI過度繪制簡單的來說是指在一個界面中有很多元素剂习,但是我們只需要更新某一小塊的元素蛮位,app卻把所有的元素都刷新一遍,這就造成過度繪制鳞绕。過度繪制會造成GPU資源浪費失仁,引起我們的app頁面卡頓,掉幀現(xiàn)象们何。
上圖是UI界面的過度繪制的情況萄焦,不同的顏色代表被過度繪制的次數(shù),開發(fā)者可以在手機設置的開發(fā)者選項中將“調(diào)式過度繪制”選項開啟冤竹,然后就可以看到界面上顯示不同的顏色了拂封。我們優(yōu)化的目的就是盡可能讓界面上只看到藍色或綠色,盡量不要出現(xiàn)大量的紅色鹦蠕。
怎么做才能減少紅色部分冒签?盡量將我們的layout布局層次簡單化,移除Window默認的background钟病,移除layout中非必須的background等萧恕,UI布局層次不應該過多,否則系統(tǒng)繪制UI時會占用較多時間肠阱,引起掉幀廊鸥。
更詳細的優(yōu)化方法請看
內(nèi)存抖動及優(yōu)化
內(nèi)存抖動是因為在短時間內(nèi)大量的對象被創(chuàng)建又馬上被釋放。瞬間產(chǎn)生大量的對象會嚴重占用Young Generation的內(nèi)存區(qū)域辖所,當達到閥值惰说,剩余空間不夠的時候,會觸發(fā)GC從而導致剛產(chǎn)生的對象又很快被回收缘回。即使每次分配的對象占用了很少的內(nèi)存吆视,但是他們疊加在一起會增加Heap的壓力,從而觸發(fā)更多其他類型的GC酥宴。這個操作有可能會影響到幀率啦吧,并使得用戶感知到性能問題。如果你在Memory Monitor里面查看到短時間發(fā)生了多次內(nèi)存的漲跌拙寡,這意味著很有可能發(fā)生了內(nèi)存抖動授滓。同時我們還可以通過Allocation Tracker來查看在短時間內(nèi),同一個棧中不斷進出的相同對象。這是內(nèi)存抖動的典型信號之一般堆。當你大致定位問題之后在孝,接下去的問題修復也就顯得相對直接簡單了。例如淮摔,你需要避免在for循環(huán)里面分配對象占用內(nèi)存私沮,需要嘗試把對象的創(chuàng)建移到循環(huán)體之外,自定義View中的onDraw方法也需要引起注意和橙,每次屏幕發(fā)生繪制以及動畫執(zhí)行過程中仔燕,onDraw方法都會被調(diào)用到,避免在onDraw方法里面執(zhí)行復雜的操作魔招,避免創(chuàng)建對象晰搀。對于那些無法避免需要創(chuàng)建對象的情況,我們可以考慮對象池模型办斑,通過對象池來解決頻繁創(chuàng)建與銷毀的問題外恕,但是這里需要注意結束使用之后,需要手動釋放對象池中的對象俄周。
啟動時間(冷啟動,暖啟動髓迎,熱啟動)
app的啟動分為三種狀態(tài)峦朗,冷啟動即應用從零開始加載運行,而其它狀態(tài)則是應用從后臺運行回到前臺運行排龄。
冷啟動狀態(tài):系統(tǒng)不存在該應用的進程波势,啟動應用才創(chuàng)建出應用的進程。冷啟動一般指的就是應用在開機后或者被系統(tǒng)停止后的第一次啟動過程橄维。因為系統(tǒng)和應用在冷啟動時需要做更多的工作尺铣,所以減少它的啟動時間的難度是最大的。
冷啟動初始時争舞,系統(tǒng)完成三個任務:
- 啟動和加載應用(這里泛指的是應用本身)
- 創(chuàng)建應用的專屬進程
- 啟動后立刻顯示啟動視圖(通常是個空白屏)
一旦系統(tǒng)創(chuàng)建了應用的專屬進程凛忿,該進程開始創(chuàng)建應用:
- 創(chuàng)建應用對象
- 啟動主線程 (MainThread)
- 創(chuàng)建 Launcher Activity
- 加載視圖 (Inflating views)
- 渲染布局 (Laying out)
- 執(zhí)行初始繪制
當應用完成了第一次繪制,系統(tǒng)進程就把當前顯示的啟動視圖切換為應用界面竞川,用戶就可以使用應用了店溢。
應用進程的啟動流程可以分為:Application的創(chuàng)建和Launcher Activity的創(chuàng)建,Application的創(chuàng)建是從Application.onCreate()開始的委乌,重寫onCreate()方法通常我們會在這里完成一些通用組件和第三方SDK的初始化操作床牧。接著應用程序生成主線程,并開始創(chuàng)建Launcher Activity遭贸。創(chuàng)建Activity的過程分為初始化戈咳、調(diào)用構造方法、調(diào)用當前生命周期的回調(diào)方法。通常onCreate()方法對加載時間的影響最大著蛙,因為它要執(zhí)行加載删铃、渲染和初始化Activity所需要的對象等開銷最大的任務,如果Activity的布局過于復雜册踩,那么就很可能會導致啟動性能問題泳姐。
暖啟動狀態(tài):應用程序的暖啟動與冷啟動類似,但比冷啟動開銷低暂吉。在暖啟動中胖秒,系統(tǒng)只需要把 Activity 切換到前臺運行。如果應用的該 Activity 之前駐留在內(nèi)存中慕的,那么應用程序就不用重新初始化對象和渲染布局阎肝。但是,如果由于響應了低內(nèi)存事件肮街,例如在 onTrimMemory() 方法中清除了資源對象风题,那么這些對象就需要在熱啟動時重新創(chuàng)建。
熱啟動狀態(tài):熱啟動為冷啟動的過程操作的子集嫉父,而且開銷比暖啟動稍小沛硅。以下這些情況可以認為是熱啟動:
- 用戶退出應用,但隨后重新啟動它绕辖。應用的進程還在運行摇肌,但應用必須重新從 onCreate() 開始創(chuàng)建 Activity。
- 系統(tǒng)從內(nèi)存中清除了應用(非用戶主動)仪际,然后用戶重新啟動它围小。進程和 Activity 需要重新啟動,但 onCreate() 將接收到保存狀態(tài)的 Bundle树碱。事實上肯适,savedInstanceState 在用戶未主動銷毀 Activity 時系統(tǒng)就會調(diào)用。
啟動時間的優(yōu)化:
一般我們優(yōu)化啟動時間都是基于冷啟動時間的成榜,所以主要是創(chuàng)建應用和創(chuàng)建Activity這兩部分所花時間的優(yōu)化框舔,在Application.onCreate()方法中,要盡量避免創(chuàng)建太多臨時變量赎婚、執(zhí)行IO操作雨饺、以及反序列化操作、多重for循環(huán)等惑淳。而在創(chuàng)建Launcher Activity過程中额港,布局視圖要盡量層次簡單,讓繪制過程盡量短歧焦,加載大量資源時可以選擇懶加載方式移斩。
關于啟動視圖:
之前說過當應用啟動后而且Launcher Activity的布局還沒有被渲染完成時肚医,會出現(xiàn)一個空白屏,如果Launcher Activity的布局非常復雜向瓷,渲染消耗的時間很長肠套,那用戶可能會感知到這個空白屏的存在,體驗上來說猖任,是很不友好的你稚,那么怎么解決這個問題呢?我們可以使用windowBackground這個屬性朱躺,一般我們會設置成一張圖片刁赖,然后在AndroidManifest.xml中的application節(jié)點的Theme中加上這個屬性。這樣做之后长搀,當應用被點擊后立馬就可以看到這張圖片宇弛,而不是一個空白屏了。
主線程IO
如果在主線程中有讀寫文件等這些耗時的操作源请,就會阻塞主線程枪芒,影響app的性能。所以在主線程中谁尸,不要進行IO操作舅踪,也不要進行for循環(huán)創(chuàng)建大量的對象。如果必須要操作IO良蛮,也要放在子線程中或者Service中進行抽碌,以保證主線程不會被阻塞。
64K 引用限制
關于64k引用限制背镇,官方的說明文檔:配置方法數(shù)超過 64K 的應用咬展,規(guī)避64k限制的辦法就是采用Dalvik可執(zhí)行文件分包泽裳。具體的配置方法請看文檔說明瞒斩。