原文地址:http://www.reibang.com/p/9755da0f4e8f
Android APP 性能優(yōu)化
說到 Android 系統(tǒng)手機(jī)肥橙,大部分人的印象是用了一段時(shí)間就變得有點(diǎn)卡頓续挟,有些程序在運(yùn)行期間莫名其妙的出現(xiàn)崩潰夫壁,打開系統(tǒng)文件夾一看,發(fā)現(xiàn)多了很多文件趋艘,然后用手機(jī)管家 APP 不斷地進(jìn)行清理優(yōu)化 ,才感覺運(yùn)行速度稍微提高了點(diǎn),就算手機(jī)在各種性能跑分軟件面前分?jǐn)?shù)遙遙領(lǐng)先么抗,還是感覺無論有多大的內(nèi)存空間都遠(yuǎn)遠(yuǎn)不夠用。相信每個(gè)使用 Android 系統(tǒng)的用戶都有過以上類似經(jīng)歷亚铁,確實(shí)蝇刀,Android 系統(tǒng)在流暢性方面不如 IOS 系統(tǒng),為何呢徘溢,明明在看手機(jī)硬件配置上時(shí)吞琐,Android 設(shè)備都不會輸于 IOS 設(shè)備,甚至都強(qiáng)于它然爆,關(guān)鍵是在于軟件上站粟。造成這種現(xiàn)象的原因是多方面的,簡單羅列幾點(diǎn)如下:
由于 Android 系統(tǒng)源碼是開放的曾雕,每個(gè)人只要遵從相應(yīng)的協(xié)議奴烙,就可以對源碼進(jìn)行修改,那么國內(nèi)各個(gè)廠商就把基于 Android 源碼改造成自己對外發(fā)布的系統(tǒng)剖张,比如我們熟悉的小米手機(jī) Miui 系統(tǒng)切诀、華為手機(jī) EMUI 系統(tǒng)、Oppo 手機(jī) ColorOS 系統(tǒng)等搔弄。由于每個(gè)廠商都修改過 Android 原生系統(tǒng)源碼趾牧,這里面就會引發(fā)一個(gè)問題,那就是著名的Android 碎片化問題肯污,本質(zhì)就是不同 Android 系統(tǒng)的應(yīng)用兼容性不同翘单,達(dá)不到一致性吨枉。
由于存在著各種 Android 碎片化和兼容性問題,導(dǎo)致 Android 開發(fā)者在開發(fā)應(yīng)用時(shí)需要對不同系統(tǒng)進(jìn)行適配哄芜,同時(shí)每個(gè) Android 開發(fā)者的開發(fā)水平參差不齊貌亭,寫出來的應(yīng)用性能也都存在不同類型的問題,導(dǎo)致用戶在使用過程中用戶體驗(yàn)感受不同认臊,那么有些問題用戶就會轉(zhuǎn)化為 Android 系統(tǒng)問題圃庭,進(jìn)而影響對Android 手機(jī)的評價(jià)。
性能優(yōu)化
今天想說的重點(diǎn)是Android APP 性能優(yōu)化失晴,也就是在開發(fā)應(yīng)用程序時(shí)應(yīng)該注意的點(diǎn)有哪些剧腻,如何更好地提高用戶體驗(yàn)。一個(gè)好的應(yīng)用涂屁,除了要有吸引人的功能和交互之外书在,在性能上也應(yīng)該有高的要求,即時(shí)應(yīng)用非常具有特色拆又,在產(chǎn)品前期可能吸引了部分用戶儒旬,但是用戶體驗(yàn)不好的話,也會給產(chǎn)品帶來不好的口碑帖族。那么一個(gè)好的應(yīng)用應(yīng)該如何定義呢栈源?主要有以下三方面:
業(yè)務(wù)/功能
符合邏輯的交互
優(yōu)秀的性能
眾所周知,Android 系統(tǒng)作為以移動設(shè)備為主的操作系統(tǒng)竖般,硬件配置是有一定的限制的甚垦,雖然配置現(xiàn)在越來越高級,但仍然無法與 PC 相比涣雕,在 CPU 和內(nèi)存上使用不合理或者耗費(fèi)資源多時(shí)艰亮,就會碰到內(nèi)存不足導(dǎo)致的穩(wěn)定性問題、CPU 消耗太多導(dǎo)致的卡頓問題等胞谭。
面對問題時(shí)垃杖,大家想到的都是聯(lián)系用戶男杈,然后查看日志丈屹,但殊不知有關(guān)性能類問題的反饋,原因也非常難找伶棒,日志大多用處不大旺垒,為何呢?因?yàn)樾阅軉栴}大部分是非必現(xiàn)的問題肤无,問題定位很難復(fù)現(xiàn)先蒋,而又沒有關(guān)鍵的日志,當(dāng)然就無法找到原因了宛渐。這些問題非常影響用戶體驗(yàn)和功能使用竞漾,所以了解一些性能優(yōu)化的一些解決方案就顯得很重要了眯搭,并在實(shí)際的項(xiàng)目中優(yōu)化我們的應(yīng)用,進(jìn)而提高用戶體驗(yàn)业岁。
四個(gè)方面
可以把用戶體驗(yàn)的性能問題主要總結(jié)為4個(gè)類別:
流暢
穩(wěn)定
省電鳞仙、省流量
安裝包小
性能問題的主要原因是什么,原因有相同的笔时,也有不同的棍好,但歸根到底,不外乎內(nèi)存使用允耿、代碼效率借笙、合適的策略邏輯、代碼質(zhì)量较锡、安裝包體積這一類問題业稼,整理歸類如下:
從圖中可以看到,打造一個(gè)高質(zhì)量的應(yīng)用應(yīng)該以4個(gè)方向?yàn)槟繕?biāo):快念链、穩(wěn)盼忌、省、小掂墓。
快:使用時(shí)避免出現(xiàn)卡頓谦纱,響應(yīng)速度快,減少用戶等待的時(shí)間君编,滿足用戶期望跨嘉。
穩(wěn):減低 crash 率和 ANR 率,不要在用戶使用過程中崩潰和無響應(yīng)吃嘿。
熟裟恕:節(jié)省流量和耗電,減少用戶使用成本兑燥,避免使用時(shí)導(dǎo)致手機(jī)發(fā)燙亮瓷。
小:安裝包小可以降低用戶的安裝成本降瞳。
要想達(dá)到這4個(gè)目標(biāo)嘱支,具體實(shí)現(xiàn)是在右邊框里的問題:卡頓、內(nèi)存使用不合理挣饥、代碼質(zhì)量差除师、代碼邏輯亂、安裝包過大扔枫,這些問題也是在開發(fā)過程中碰到最多的問題汛聚,在實(shí)現(xiàn)業(yè)務(wù)需求同時(shí),也需要考慮到這點(diǎn)短荐,多花時(shí)間去思考倚舀,如何避免功能完成后再來做優(yōu)化叹哭,不然的話等功能實(shí)現(xiàn)后帶來的維護(hù)成本會增加。
卡頓優(yōu)化
Android 應(yīng)用啟動慢痕貌,使用時(shí)經(jīng)郴八伲卡頓,是非常影響用戶體驗(yàn)的芯侥,應(yīng)該盡量避免出現(xiàn)泊交。卡頓的場景有很多柱查,按場景可以分為4類:UI 繪制廓俭、應(yīng)用啟動、頁面跳轉(zhuǎn)唉工、事件響應(yīng)研乒,如圖:
這4種卡頓場景的根本原因可以分為兩大類:
界面繪制。主要原因是繪制的層級深淋硝、頁面復(fù)雜雹熬、刷新不合理,由于這些原因?qū)е驴D的場景更多出現(xiàn)在 UI 和啟動后的初始界面以及跳轉(zhuǎn)到頁面的繪制上谣膳。
數(shù)據(jù)處理竿报。導(dǎo)致這種卡頓場景的原因是數(shù)據(jù)處理量太大,一般分為三種情況继谚,一是數(shù)據(jù)在處理 UI 線程烈菌,二是數(shù)據(jù)處理占用 CPU 高,導(dǎo)致主線程拿不到時(shí)間片花履,三是內(nèi)存增加導(dǎo)致 GC 頻繁芽世,從而引起卡頓。
引起卡頓的原因很多诡壁,但不管怎么樣的原因和場景济瓢,最終都是通過設(shè)備屏幕上顯示來達(dá)到用戶,歸根到底就是顯示有問題妹卿,所以旺矾,要解決卡頓,就要先了解 Android 系統(tǒng)的顯示原理纽帖。
Android系統(tǒng)顯示原理
Android 顯示過程可以簡單概括為:Android 應(yīng)用程序把經(jīng)過測量宠漩、布局举反、繪制后的 surface 緩存數(shù)據(jù)懊直,通過 SurfaceFlinger 把數(shù)據(jù)渲染到顯示屏幕上, 通過 Android 的刷新機(jī)制來刷新數(shù)據(jù)火鼻。也就是說應(yīng)用層負(fù)責(zé)繪制室囊,系統(tǒng)層負(fù)責(zé)渲染雕崩,通過進(jìn)程間通信把應(yīng)用層需要繪制的數(shù)據(jù)傳遞到系統(tǒng)層服務(wù),系統(tǒng)層服務(wù)通過刷新機(jī)制把數(shù)據(jù)更新到屏幕上融撞。
我們都知道在 Android 的每個(gè) View 繪制中有三個(gè)核心步驟:Measure盼铁、Layout、Draw尝偎。具體實(shí)現(xiàn)是從 ViewRootImp 類的performTraversals() 方法開始執(zhí)行饶火,Measure 和 Layout都是通過遞歸來獲取 View 的大小和位置,并且以深度作為優(yōu)先級致扯,可以看出層級越深肤寝、元素越多、耗時(shí)也就越長抖僵。
真正把需要顯示的數(shù)據(jù)渲染到屏幕上鲤看,是通過系統(tǒng)級進(jìn)程中的 SurfaceFlinger 服務(wù)來實(shí)現(xiàn)的,那么這個(gè)SurfaceFlinger 服務(wù)主要做了哪些工作呢耍群?如下:
響應(yīng)客戶端事件义桂,創(chuàng)建 Layer 與客戶端的 Surface 建立連接。
接收客戶端數(shù)據(jù)及屬性蹈垢,修改 Layer 屬性慷吊,如尺寸、顏色曹抬、透明度等罢浇。
將創(chuàng)建的 Layer 內(nèi)容刷新到屏幕上。
維持 Layer 的序列沐祷,并對 Layer 最終輸出做出裁剪計(jì)算嚷闭。
既然是兩個(gè)不同的進(jìn)程,那么肯定是需要一個(gè)跨進(jìn)程的通信機(jī)制來實(shí)現(xiàn)數(shù)據(jù)傳遞赖临,在 Android 顯示系統(tǒng)中胞锰,使用了 Android 的匿名共享內(nèi)存:SharedClient,每一個(gè)應(yīng)用和 SurfaceFlinger 之間都會創(chuàng)建一個(gè)SharedClient 兢榨,然后在每個(gè) SharedClient 中嗅榕,最多可以創(chuàng)建 31 個(gè) SharedBufferStack,每個(gè) Surface 都對應(yīng)一個(gè) SharedBufferStack吵聪,也就是一個(gè) Window凌那。
一個(gè) SharedClient 對應(yīng)一個(gè)Android 應(yīng)用程序,而一個(gè) Android 應(yīng)用程序可能包含多個(gè)窗口吟逝,即 Surface 帽蝶。也就是說 SharedClient 包含的是 SharedBufferStack的集合,其中在顯示刷新機(jī)制中用到了雙緩沖和三重緩沖技術(shù)块攒。最后總結(jié)起來顯示整體流程分為三個(gè)模塊:應(yīng)用層繪制到緩存區(qū)励稳,SurfaceFlinger 把緩存區(qū)數(shù)據(jù)渲染到屏幕佃乘,由于是不同的進(jìn)程,所以使用 Android 的匿名共享內(nèi)存 SharedClient 緩存需要顯示的數(shù)據(jù)來達(dá)到目的驹尼。
除此之外趣避,我們還需要一個(gè)名詞:FPS。FPS 表示每秒傳遞的幀數(shù)新翎。在理想情況下程帕,60 FPS 就感覺不到卡,這意味著每個(gè)繪制時(shí)長應(yīng)該在16 ms 以內(nèi)地啰。但是 Android 系統(tǒng)很有可能無法及時(shí)完成那些復(fù)雜的頁面渲染操作骆捧。Android 系統(tǒng)每隔 16ms 發(fā)出 VSYNC 信號,觸發(fā)對 UI 進(jìn)行渲染髓绽,如果每次渲染都成功敛苇,這樣就能夠達(dá)到流暢的畫面所需的 60FPS。如果某個(gè)操作花費(fèi)的時(shí)間是 24ms 顺呕,系統(tǒng)在得到 VSYNC 信號時(shí)就無法正常進(jìn)行正常渲染枫攀,這樣就發(fā)生了丟幀現(xiàn)象。那么用戶在 32ms 內(nèi)看到的會是同一幀畫面株茶,這種現(xiàn)象在執(zhí)行動畫或滑動列表比較常見来涨,還有可能是你的 Layout 太過復(fù)雜,層疊太多的繪制單元启盛,無法在 16ms 完成渲染蹦掐,最終引起刷新不及時(shí)。
卡頓根本原因
根據(jù)Android 系統(tǒng)顯示原理可以看到僵闯,影響繪制的根本原因有以下兩個(gè)方面:
繪制任務(wù)太重卧抗,繪制一幀內(nèi)容耗時(shí)太長。
主線程太忙鳖粟,根據(jù)系統(tǒng)傳遞過來的 VSYNC 信號來時(shí)還沒準(zhǔn)備好數(shù)據(jù)導(dǎo)致丟幀社裆。
繪制耗時(shí)太長,有一些工具可以幫助我們定位問題向图。主線程太忙則需要注意了泳秀,主線程關(guān)鍵職責(zé)是處理用戶交互,在屏幕上繪制像素榄攀,并進(jìn)行加載顯示相關(guān)的數(shù)據(jù)嗜傅,所以特別需要避免任何主線程的事情,這樣應(yīng)用程序才能保持對用戶操作的即時(shí)響應(yīng)檩赢÷类郑總結(jié)起來,主線程主要做以下幾個(gè)方面工作:
UI 生命周期控制
系統(tǒng)事件處理
消息處理
界面布局
界面繪制
界面刷新
除此之外,應(yīng)該盡量避免將其他處理放在主線程中币他,特別復(fù)雜的數(shù)據(jù)計(jì)算和網(wǎng)絡(luò)請求等。
性能分析工具
性能問題并不容易復(fù)現(xiàn)憔狞,也不好定位蝴悉,但是真的碰到問題還是需要去解決的,那么分析問題和確認(rèn)問題是否解決瘾敢,就需要借助相應(yīng)的的調(diào)試工具拍冠,比如查看 Layout 層次的 Hierarchy View、Android 系統(tǒng)上帶的 GPU Profile 工具和靜態(tài)代碼檢查工具 Lint 等簇抵,這些工具對性能優(yōu)化起到非常重要的作用庆杜,所以要熟悉,知道在什么場景用什么工具來分析碟摆。
1晃财,Profile GPU Rendering
在手機(jī)開發(fā)者模式下,有一個(gè)卡頓檢測工具叫做:Profile GPU Rendering典蜕,如圖:
它的功能特點(diǎn)如下:
一個(gè)圖形監(jiān)測工具断盛,能實(shí)時(shí)反應(yīng)當(dāng)前繪制的耗時(shí)
橫軸表示時(shí)間,縱軸表示每一幀的耗時(shí)
隨著時(shí)間推移愉舔,從左到右的刷新呈現(xiàn)
提供一個(gè)標(biāo)準(zhǔn)的耗時(shí)钢猛,如果高于標(biāo)準(zhǔn)耗時(shí),就表示當(dāng)前這一幀丟失
2轩缤,TraceView
TraceView 是 Android SDK 自帶的工具命迈,用來分析函數(shù)調(diào)用過程,可以對 Android 的應(yīng)用程序以及 Framework 層的代碼進(jìn)行性能分析火的。它是一個(gè)圖形化的工具壶愤,最終會產(chǎn)生一個(gè)圖表,用于對性能分析進(jìn)行說明馏鹤,可以分析到每一個(gè)方法的執(zhí)行時(shí)間公你,其中可以統(tǒng)計(jì)出該方法調(diào)用次數(shù)和遞歸次數(shù),實(shí)際時(shí)長等參數(shù)維度假瞬,使用非常直觀陕靠,分析性能非常方便。
3脱茉,Systrace UI 性能分析
Systrace 是 Android 4.1及以上版本提供的性能數(shù)據(jù)采樣和分析工具剪芥,它是通過系統(tǒng)的角度來返回一些信息。它可以幫助開發(fā)者收集 Android 關(guān)鍵子系統(tǒng)琴许,如 surfaceflinger税肪、WindowManagerService 等 Framework 部分關(guān)鍵模塊、服務(wù)、View系統(tǒng)等運(yùn)行信息益兄,從而幫助開發(fā)者更直觀地分析系統(tǒng)瓶頸锻梳,改進(jìn)性能。Systrace 的功能包括跟蹤系統(tǒng)的 I/O 操作净捅、內(nèi)核工作隊(duì)列疑枯、CPU 負(fù)載等,在 UI 顯示性能分析上提供很好的數(shù)據(jù)蛔六,特別是在動畫播放不流暢荆永、渲染卡等問題上。
優(yōu)化建議
1国章,布局優(yōu)化
布局是否合理主要影響的是頁面測量時(shí)間的多少具钥,我們知道一個(gè)頁面的顯示測量和繪制過程都是通過遞歸來完成的,多叉樹遍歷的時(shí)間與樹的高度h有關(guān)液兽,其時(shí)間復(fù)雜度 O(h)骂删,如果層級太深,每增加一層則會增加更多的頁面顯示時(shí)間四啰,所以布局的合理性就顯得很重要桃漾。
那布局優(yōu)化有哪些方法呢,主要通過減少層級拟逮、減少測量和繪制時(shí)間撬统、提高復(fù)用性三個(gè)方面入手《仄總結(jié)如下:
減少層級恋追。合理使用 Constraintlayout、RelativeLayout 和 LinerLayout罚屋,合理使用Merge苦囱。
提高顯示速度。使用 ViewStub脾猛,它是一個(gè)看不見的撕彤、不占布局位置、占用資源非常小的視圖對象猛拴。
布局復(fù)用羹铅。可以通過<include> 標(biāo)簽來提高復(fù)用愉昆。
盡可能少用wrap_content职员。wrap_content 會增加布局 measure 時(shí)計(jì)算成本,在已知寬高為固定值時(shí)跛溉,不用wrap_content 缓呛。
刪除控件中無用的屬性。
2哀峻,避免過度繪制
過度繪制是指在屏幕上的某個(gè)像素在同一幀的時(shí)間內(nèi)被繪制了多次。在多層次重疊的 UI 結(jié)構(gòu)中刹勃,如果不可見的 UI 也在做繪制的操作,就會導(dǎo)致某些像素區(qū)域被繪制了多次嚎尤,從而浪費(fèi)了多余的 CPU 以及 GPU 資源荔仁。
如何避免過度繪制呢,如下:
布局上的優(yōu)化诺苹。移除 XML 中非必須的背景咕晋,移除 Window 默認(rèn)的背景雹拄、按需顯示占位背景圖片
自定義View優(yōu)化收奔。使用 canvas.clipRect()來幫助系統(tǒng)識別那些可見的區(qū)域,只有在這個(gè)區(qū)域內(nèi)才會被繪制滓玖。
3坪哄,啟動優(yōu)化
通過對啟動速度的監(jiān)控,發(fā)現(xiàn)影響啟動速度的問題所在势篡,優(yōu)化啟動邏輯翩肌,提高應(yīng)用的啟動速度。啟動主要完成三件事:UI 布局禁悠、繪制和數(shù)據(jù)準(zhǔn)備念祭。因此啟動速度優(yōu)化就是需要優(yōu)化這三個(gè)過程:
UI 布局。應(yīng)用一般都有閃屏頁碍侦,優(yōu)化閃屏頁的 UI 布局粱坤,可以通過 Profile GPU Rendering 檢測丟幀情況。
啟動加載邏輯優(yōu)化瓷产≌拘可以采用分布加載、異步加載濒旦、延期加載策略來提高應(yīng)用啟動速度株旷。
數(shù)據(jù)準(zhǔn)備。數(shù)據(jù)初始化分析尔邓,加載數(shù)據(jù)可以考慮用線程初始化等策略晾剖。
4,合理的刷新機(jī)制
在應(yīng)用開發(fā)過程中梯嗽,因?yàn)閿?shù)據(jù)的變化钞瀑,需要刷新頁面來展示新的數(shù)據(jù),但頻繁刷新會增加資源開銷慷荔,并且可能導(dǎo)致卡頓發(fā)生雕什,因此缠俺,需要一個(gè)合理的刷新機(jī)制來提高整體的 UI 流暢度。合理的刷新需要注意以下幾點(diǎn):
盡量減少刷新次數(shù)贷岸。
盡量避免后臺有高的 CPU 線程運(yùn)行壹士。
縮小刷新區(qū)域。
5偿警,其他
在實(shí)現(xiàn)動畫效果時(shí)躏救,需要根據(jù)不同場景選擇合適的動畫框架來實(shí)現(xiàn)。有些情況下螟蒸,可以用硬件加速方式來提供流暢度盒使。
內(nèi)存優(yōu)化
在 Android 系統(tǒng)中有個(gè)垃圾內(nèi)存回收機(jī)制,在虛擬機(jī)層自動分配和釋放內(nèi)存七嫌,因此不需要在代碼中分配和釋放某一塊內(nèi)存少办,從應(yīng)用層面上不容易出現(xiàn)內(nèi)存泄漏和內(nèi)存溢出等問題,但是需要內(nèi)存管理诵原。Android 系統(tǒng)在內(nèi)存管理上有一個(gè) Generational Heap Memory 模型英妓,內(nèi)存回收的大部分壓力不需要應(yīng)用層關(guān)心, Generational Heap Memory 有自己一套管理機(jī)制绍赛,當(dāng)內(nèi)存達(dá)到一個(gè)閾值時(shí)蔓纠,系統(tǒng)會根據(jù)不同的規(guī)則自動釋放系統(tǒng)認(rèn)為可以釋放的內(nèi)存,也正是因?yàn)?Android 程序把內(nèi)存控制的權(quán)力交給了 Generational Heap Memory吗蚌,一旦出現(xiàn)內(nèi)存泄漏和溢出方面的問題腿倚,排查錯(cuò)誤將會成為一項(xiàng)異常艱難的工作。除此之外蚯妇,部分 Android 應(yīng)用開發(fā)人員在開發(fā)過程中并沒有特別關(guān)注內(nèi)存的合理使用敷燎,也沒有在內(nèi)存方面做太多的優(yōu)化,當(dāng)應(yīng)用程序同時(shí)運(yùn)行越來越多的任務(wù)侮措,加上越來越復(fù)雜的業(yè)務(wù)需求時(shí)懈叹,完全依賴 Android 的內(nèi)存管理機(jī)制就會導(dǎo)致一系列性能問題逐漸呈現(xiàn),對應(yīng)用的穩(wěn)定性和性能帶來不可忽視的影響分扎,因此澄成,解決內(nèi)存問題和合理優(yōu)化內(nèi)存是非常有必要的。
Android內(nèi)存管理機(jī)制
Android 應(yīng)用都是在 Android 的虛擬機(jī)上運(yùn)行畏吓,應(yīng)用 程序的內(nèi)存分配與垃圾回收都是由虛擬機(jī)完成的墨状。在 Android 系統(tǒng),虛擬機(jī)有兩種運(yùn)行模式:Dalvik 和 ART菲饼。
1肾砂,Java對象生命周期
一般Java對象在虛擬機(jī)上有7個(gè)運(yùn)行階段:
創(chuàng)建階段->應(yīng)用階段->不可見階段->不可達(dá)階段->收集階段->終結(jié)階段->對象空間重新分配階段
2,內(nèi)存分配
在 Android 系統(tǒng)中宏悦,內(nèi)存分配實(shí)際上是對堆的分配和釋放镐确。當(dāng)一個(gè) Android 程序啟動包吝,應(yīng)用進(jìn)程都是從一個(gè)叫做 Zygote 的進(jìn)程衍生出來,系統(tǒng)啟動 Zygote 進(jìn)程后源葫,為了啟動一個(gè)新的應(yīng)用程序進(jìn)程诗越,系統(tǒng)會衍生 Zygote 進(jìn)程生成一個(gè)新的進(jìn)程,然后在新的進(jìn)程中加載并運(yùn)行應(yīng)用程序的代碼息堂。其中嚷狞,大多數(shù)的 RAM pages 被用來分配給Framework 代碼,同時(shí)促使 RAM 資源能夠在應(yīng)用所有進(jìn)程之間共享荣堰。
但是為了整個(gè)系統(tǒng)的內(nèi)存控制需要床未,Android 系統(tǒng)會為每一個(gè)應(yīng)用程序都設(shè)置一個(gè)硬性的 Dalvik Heap Size 最大限制閾值,整個(gè)閾值在不同設(shè)備上會因?yàn)?RAM 大小不同而有所差異振坚。如果應(yīng)用占用內(nèi)存空間已經(jīng)接近整個(gè)閾值時(shí)薇搁,再嘗試分配內(nèi)存的話,就很容易引起內(nèi)存溢出的錯(cuò)誤屡拨。
3只酥,內(nèi)存回收機(jī)制
我們需要知道的是褥实,在 Java 中內(nèi)存被分為三個(gè)區(qū)域:Young Generation(年輕代)呀狼、Old Generation(年老代)、Permanent Generation(持久代)损离。最近分配的對象會存放在 Young Generation 區(qū)域哥艇。對象在某個(gè)時(shí)機(jī)觸發(fā) GC 回收垃圾,而沒有回收的就根據(jù)不同規(guī)則僻澎,有可能被移動到 Old Generation貌踏,最后累積一定時(shí)間在移動到 Permanent Generation 區(qū)域。系統(tǒng)會根據(jù)內(nèi)存中不同的內(nèi)存數(shù)據(jù)類型分別執(zhí)行不同的 GC 操作窟勃。GC 通過確定對象是否被活動對象引用來確定是否收集對象祖乳,進(jìn)而動態(tài)回收無任何引用的對象占據(jù)的內(nèi)存空間。但需要注意的是頻繁的 GC 會增加應(yīng)用的卡頓情況秉氧,影響應(yīng)用的流暢性眷昆,因此需要盡量減少系統(tǒng) GC 行為,以便提高應(yīng)用的流暢度汁咏,減小卡頓發(fā)生的概率亚斋。
內(nèi)存分析工具
做內(nèi)存優(yōu)化前,需要了解當(dāng)前應(yīng)用的內(nèi)存使用現(xiàn)狀攘滩,通過現(xiàn)狀去分析哪些數(shù)據(jù)類型有問題帅刊,各種類型的分布情況如何,以及在發(fā)現(xiàn)問題后如何發(fā)現(xiàn)是哪些具體對象導(dǎo)致的漂问,這就需要相關(guān)工具來幫助我們赖瞒。
1女揭,Memory Monitor
Memory Monitor 是一款使用非常簡單的圖形化工具,可以很好地監(jiān)控系統(tǒng)或應(yīng)用的內(nèi)存使用情況栏饮,主要有以下功能:
顯示可用和已用內(nèi)存田绑,并且以時(shí)間為維度實(shí)時(shí)反應(yīng)內(nèi)存分配和回收情況。
快速判斷應(yīng)用程序的運(yùn)行緩慢是否由于過度的內(nèi)存回收導(dǎo)致抡爹。
快速判斷應(yīng)用是否由于內(nèi)存不足導(dǎo)致程序崩潰掩驱。
2,Heap Viewer
Heap Viewer 的主要功能是查看不同數(shù)據(jù)類型在內(nèi)存中的使用情況冬竟,可以看到當(dāng)前進(jìn)程中的 Heap Size 的情況欧穴,分別有哪些類型的數(shù)據(jù),以及各種類型數(shù)據(jù)占比情況泵殴。通過分析這些數(shù)據(jù)來找到大的內(nèi)存對象涮帘,再進(jìn)一步分析這些大對象,進(jìn)而通過優(yōu)化減少內(nèi)存開銷笑诅,也可以通過數(shù)據(jù)的變化發(fā)現(xiàn)內(nèi)存泄漏调缨。
3,Allocation Tracker
Memory Monitor 和 Heap Viewer 都可以很直觀且實(shí)時(shí)地監(jiān)控內(nèi)存使用情況吆你,還能發(fā)現(xiàn)內(nèi)存問題弦叶,但發(fā)現(xiàn)內(nèi)存問題后不能再進(jìn)一步找到原因,或者發(fā)現(xiàn)一塊異常內(nèi)存妇多,但不能區(qū)別是否正常伤哺,同時(shí)在發(fā)現(xiàn)問題后,也不能定位到具體的類和方法者祖。這時(shí)就需要使用另一個(gè)內(nèi)存分析工具 Allocation Tracker立莉,進(jìn)行更詳細(xì)的分析, Allocation Tracker 可以分配跟蹤記錄應(yīng)用程序的內(nèi)存分配七问,并列出了它們的調(diào)用堆棧蜓耻,可以查看所有對象內(nèi)存分配的周期。
4械巡,Memory Analyzer Tool(MAT)
MAT 是一個(gè)快速刹淌,功能豐富的 Java Heap 分析工具,通過分析 Java 進(jìn)程的內(nèi)存快照 HPROF 分析坟比,從眾多的對象中分析芦鳍,快速計(jì)算出在內(nèi)存中對象占用的大小,查看哪些對象不能被垃圾收集器回收葛账,并可以通過視圖直觀地查看可能造成這種結(jié)果的對象柠衅。
常見內(nèi)存泄漏場景
如果在內(nèi)存泄漏發(fā)生后再去找原因并修復(fù)會增加開發(fā)的成本,最好在編寫代碼時(shí)就能夠很好地考慮內(nèi)存問題籍琳,寫出更高質(zhì)量的代碼菲宴,這里列出一些常見的內(nèi)存泄漏場景贷祈,在以后的開發(fā)過程中需要避免這類問題。
資源性對象未關(guān)閉喝峦。比如Cursor势誊、File文件等,往往都用了一些緩沖谣蠢,在不使用時(shí)粟耻,應(yīng)該及時(shí)關(guān)閉它們。
注冊對象未注銷眉踱。比如事件注冊后未注銷挤忙,會導(dǎo)致觀察者列表中維持著對象的引用。
類的靜態(tài)變量持有大數(shù)據(jù)對象谈喳。
非靜態(tài)內(nèi)部類的靜態(tài)實(shí)例册烈。
Handler臨時(shí)性內(nèi)存泄漏。如果Handler是非靜態(tài)的婿禽,容易導(dǎo)致 Activity 或 Service 不會被回收赏僧。
容器中的對象沒清理造成的內(nèi)存泄漏。
WebView扭倾。WebView 存在著內(nèi)存泄漏的問題淀零,在應(yīng)用中只要使用一次 WebView,內(nèi)存就不會被釋放掉吆录。
除此之外窑滞,內(nèi)存泄漏可監(jiān)控琼牧,常見的就是用LeakCanary 第三方庫恢筝,這是一個(gè)檢測內(nèi)存泄漏的開源庫,使用非常簡單巨坊,可以在發(fā)生內(nèi)存泄漏時(shí)告警撬槽,并且生成 leak tarce 分析泄漏位置,同時(shí)可以提供 Dump 文件進(jìn)行分析趾撵。
優(yōu)化內(nèi)存空間
沒有內(nèi)存泄漏侄柔,并不意味著內(nèi)存就不需要優(yōu)化,在移動設(shè)備上占调,由于物理設(shè)備的存儲空間有限暂题,Android 系統(tǒng)對每個(gè)應(yīng)用進(jìn)程也都分配了有限的堆內(nèi)存,因此使用最小內(nèi)存對象或者資源可以減小內(nèi)存開銷究珊,同時(shí)讓GC 能更高效地回收不再需要使用的對象薪者,讓應(yīng)用堆內(nèi)存保持充足的可用內(nèi)存,使應(yīng)用更穩(wěn)定高效地運(yùn)行剿涮。常見做法如下:
對象引用言津。強(qiáng)引用攻人、軟引用、弱引用悬槽、虛引用四種引用類型怀吻,根據(jù)業(yè)務(wù)需求合理使用不同,選擇不同的引用類型初婆。
減少不必要的內(nèi)存開銷蓬坡。注意自動裝箱,增加內(nèi)存復(fù)用磅叛,比如有效利用系統(tǒng)自帶的資源渣窜、視圖復(fù)用、對象池宪躯、Bitmap對象的復(fù)用乔宿。
使用最優(yōu)的數(shù)據(jù)類型。比如針對數(shù)據(jù)類容器結(jié)構(gòu)访雪,可以使用ArrayMap數(shù)據(jù)結(jié)構(gòu)详瑞,避免使用枚舉類型,使用緩存Lrucache等等臣缀。
圖片內(nèi)存優(yōu)化坝橡。可以設(shè)置位圖規(guī)格精置,根據(jù)采樣因子做壓縮计寇,用一些圖片緩存方式對圖片進(jìn)行管理等等。
穩(wěn)定性優(yōu)化
Android 應(yīng)用的穩(wěn)定性定義很寬泛脂倦,影響穩(wěn)定性的原因很多,比如內(nèi)存使用不合理蝶押、代碼異常場景考慮不周全、代碼邏輯不合理等赶盔,都會對應(yīng)用的穩(wěn)定性造成影響。其中最常見的兩個(gè)場景是:Crash 和 ANR沉眶,這兩個(gè)錯(cuò)誤將會使得程序無法使用柳击,比較常用的解決方式如下:
提高代碼質(zhì)量捌肴。比如開發(fā)期間的代碼審核状知,看些代碼設(shè)計(jì)邏輯,業(yè)務(wù)合理性等。
代碼靜態(tài)掃描工具贷揽。常見工具有Android Lint、Findbugs印屁、Checkstyle樱拴、PMD等等。
Crash監(jiān)控正罢。把一些崩潰的信息履怯,異常信息及時(shí)地記錄下來,以便后續(xù)分析解決裆泳。
Crash上傳機(jī)制叹洲。在Crash后,盡量先保存日志到本地工禾,然后等下一次網(wǎng)絡(luò)正常時(shí)再上傳日志信息运提。
耗電優(yōu)化
在移動設(shè)備中,電池的重要性不言而喻闻葵,沒有電什么都干不成民泵。對于操作系統(tǒng)和設(shè)備開發(fā)商來說,耗電優(yōu)化一致沒有停止槽畔,去追求更長的待機(jī)時(shí)間栈妆,而對于一款應(yīng)用來說,并不是可以忽略電量使用問題厢钧,特別是那些被歸為“電池殺手”的應(yīng)用签钩,最終的結(jié)果是被卸載。因此坏快,應(yīng)用開發(fā)者在實(shí)現(xiàn)需求的同時(shí)铅檩,需要盡量減少電量的消耗。
在 Android5.0 以前莽鸿,在應(yīng)用中測試電量消耗比較麻煩昧旨,也不準(zhǔn)確,5.0 之后專門引入了一個(gè)獲取設(shè)備上電量消耗信息的 API:Battery Historian祥得。Battery Historian 是一款由 Google 提供的 Android 系統(tǒng)電量分析工具兔沃,和Systrace 一樣,是一款圖形化數(shù)據(jù)分析工具级及,直觀地展示出手機(jī)的電量消耗過程乒疏,通過輸入電量分析文件,顯示消耗情況饮焦,最后提供一些可供參考電量優(yōu)化的方法怕吴。
除此之外,還有一些常用方案可提供:
計(jì)算優(yōu)化县踢,避開浮點(diǎn)運(yùn)算等转绷。
避免 WaleLock 使用不當(dāng)。
使用 Job Scheduler硼啤。
安裝包大小優(yōu)化
應(yīng)用安裝包大小對應(yīng)用使用沒有影響议经,但應(yīng)用的安裝包越大,用戶下載的門檻越高,特別是在移動網(wǎng)絡(luò)情況下煞肾,用戶在下載應(yīng)用時(shí)咧织,對安裝包大小的要求更高,因此籍救,減小安裝包大小可以讓更多用戶愿意下載和體驗(yàn)產(chǎn)品习绢。
常用應(yīng)用安裝包的構(gòu)成,如圖所示:
從圖中我們可以看到:
assets文件夾钧忽。存放一些配置文件毯炮、資源文件,assets不會自動生成對應(yīng)的 ID耸黑,而是通過 AssetManager 類的接口獲取桃煎。
res。res 是 resource 的縮寫大刊,這個(gè)目錄存放資源文件为迈,會自動生成對應(yīng)的 ID 并映射到 .R 文件中,訪問直接使用資源 ID缺菌。
META-INF葫辐。保存應(yīng)用的簽名信息,簽名信息可以驗(yàn)證 APK 文件的完整性伴郁。
AndroidManifest.xml耿战。這個(gè)文件用來描述 Android 應(yīng)用的配置信息,一些組件的注冊信息焊傅、可使用權(quán)限等剂陡。
classes.dex。Dalvik 字節(jié)碼程序狐胎,讓 Dalvik 虛擬機(jī)可執(zhí)行鸭栖,一般情況下,Android 應(yīng)用在打包時(shí)通過 Android SDK 中的 dx 工具將 Java 字節(jié)碼轉(zhuǎn)換為 Dalvik 字節(jié)碼握巢。
resources.arsc晕鹊。記錄著資源文件和資源 ID 之間的映射關(guān)系,用來根據(jù)資源 ID 尋找資源暴浦。
減少安裝包大小的常用方案
代碼混淆溅话。使用proGuard 代碼混淆器工具,它包括壓縮肉渴、優(yōu)化公荧、混淆等功能。
資源優(yōu)化同规。比如使用 Android Lint 刪除冗余資源,資源文件最少化等。
圖片優(yōu)化券勺。比如利用 AAPT 工具對 PNG 格式的圖片做壓縮處理绪钥,降低圖片色彩位數(shù)等。
避免重復(fù)功能的庫关炼,使用 WebP圖片格式等程腹。
插件化。比如功能模塊放在服務(wù)器上儒拂,按需下載寸潦,可以減少安裝包大小。
小結(jié)
性能優(yōu)化不是更新一兩個(gè)版本就可以解決的社痛,是持續(xù)性的需求见转,持續(xù)集成迭代反饋。在實(shí)際的項(xiàng)目中蒜哀,在項(xiàng)目剛開始的時(shí)候斩箫,由于人力和項(xiàng)目完成時(shí)間限制,性能優(yōu)化的優(yōu)先級比較低撵儿,等進(jìn)入項(xiàng)目投入使用階段乘客,就需要把優(yōu)先級提高,但在項(xiàng)目初期淀歇,在設(shè)計(jì)架構(gòu)方案時(shí)易核,性能優(yōu)化的點(diǎn)也需要提早考慮進(jìn)去,這就體現(xiàn)出一個(gè)程序員的技術(shù)功底了浪默。
什么時(shí)候開始有性能優(yōu)化的需求牡直,往往都是從發(fā)現(xiàn)問題開始,然后分析問題原因及背景浴鸿,進(jìn)而尋找最優(yōu)解決方案井氢,最終解決問題,這也是日常工作中常會用到的處理方式岳链。