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è)備都不會(huì)輸于 IOS 設(shè)備弄抬,甚至都強(qiáng)于它茎辐,關(guān)鍵是在于軟件上。造成這種現(xiàn)象的原因是多方面的掂恕,簡單羅列幾點(diǎn)如下:

  • 其實(shí)近年來拖陆,隨著 Android 版本不斷迭代,Google 提供的Android 系統(tǒng)已經(jīng)越來越流暢懊亡,目前最新發(fā)布的版本是 Android 8.0 Oreo 依啰。但是在國內(nèi)大部分用戶用的 Android 手機(jī)系是各大廠商定制過的版本,往往不是最新的原生系統(tǒng)內(nèi)核店枣,可能絕大多數(shù)還停留在 Android 5.0 系統(tǒng)上速警,甚至 Android 6.0 以上所占比例還偏小叹誉,更新存在延遲性。
  • 由于 Android 系統(tǒng)源碼是開放的闷旧,每個(gè)人只要遵從相應(yīng)的協(xié)議长豁,就可以對(duì)源碼進(jìn)行修改,那么國內(nèi)各個(gè)廠商就把基于 Android 源碼改造成自己對(duì)外發(fā)布的系統(tǒng)忙灼,比如我們熟悉的小米手機(jī) Miui 系統(tǒng)匠襟、華為手機(jī) EMUI 系統(tǒng)、Oppo 手機(jī) ColorOS 系統(tǒng)等该园。由于每個(gè)廠商都修改過 Android 原生系統(tǒng)源碼宅此,這里面就會(huì)引發(fā)一個(gè)問題,那就是著名的Android 碎片化問題爬范,本質(zhì)就是不同 Android 系統(tǒng)的應(yīng)用兼容性不同父腕,達(dá)不到一致性。
  • 由于存在著各種 Android 碎片化和兼容性問題青瀑,導(dǎo)致 Android 開發(fā)者在開發(fā)應(yīng)用時(shí)需要對(duì)不同系統(tǒng)進(jìn)行適配璧亮,同時(shí)每個(gè) Android 開發(fā)者的開發(fā)水平參差不齊,寫出來的應(yīng)用性能也都存在不同類型的問題斥难,導(dǎo)致用戶在使用過程中用戶體驗(yàn)感受不同枝嘶,那么有些問題用戶就會(huì)轉(zhuǎn)化為 Android 系統(tǒng)問題,進(jìn)而影響對(duì)Android 手機(jī)的評(píng)價(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)不好的話担猛,也會(huì)給產(chǎn)品帶來不好的口碑幕垦。那么一個(gè)好的應(yīng)用應(yīng)該如何定義呢?主要有以下三方面:

  • 業(yè)務(wù)/功能
  • 符合邏輯的交互
  • 優(yōu)秀的性能

眾所周知傅联,Android 系統(tǒng)作為以移動(dòng)設(shè)備為主的操作系統(tǒng)先改,硬件配置是有一定的限制的,雖然配置現(xiàn)在越來越高級(jí)蒸走,但仍然無法與 PC 相比仇奶,在 CPU 和內(nèi)存上使用不合理或者耗費(fèi)資源多時(shí),就會(huì)碰到內(nèi)存不足導(dǎo)致的穩(wěn)定性問題载碌、CPU 消耗太多導(dǎo)致的卡頓問題等猜嘱。

面對(duì)問題時(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ì)量之拨、安裝包體積這一類問題茉继,整理歸類如下:

性能優(yōu)化圖

從圖中可以看到,打造一個(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ù)成本會(huì)增加叮雳。

卡頓優(yōu)化

Android 應(yīng)用啟動(dòng)慢想暗,使用時(shí)經(jīng)常卡頓债鸡,是非常影響用戶體驗(yàn)的江滨,應(yīng)該盡量避免出現(xiàn)⊙峋卡頓的場景有很多唬滑,按場景可以分為4類:UI 繪制、應(yīng)用啟動(dòng)棺弊、頁面跳轉(zhuǎn)晶密、事件響應(yīng),如圖:

卡頓場景

這4種卡頓場景的根本原因可以分為兩大類:

  • 界面繪制模她。主要原因是繪制的層級(jí)深稻艰、頁面復(fù)雜、刷新不合理侈净,由于這些原因?qū)е驴D的場景更多出現(xiàn)在 UI 和啟動(dòng)后的初始界面以及跳轉(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)先級(jí),可以看出層級(jí)越深俺祠、元素越多公给、耗時(shí)也就越長。

真正把需要顯示的數(shù)據(jù)渲染到屏幕上蜘渣,是通過系統(tǒng)級(jí)進(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 的序列,并對(duì) 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 之間都會(huì)創(chuàng)建一個(gè)SharedClient ,然后在每個(gè) SharedClient 中燕偶,最多可以創(chuàng)建 31 個(gè) SharedBufferStack喝噪,每個(gè) Surface 都對(duì)應(yīng)一個(gè) SharedBufferStack,也就是一個(gè) Window指么。

一個(gè) SharedClient 對(duì)應(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 信號(hào),觸發(fā)對(duì) UI 進(jìn)行渲染枯夜,如果每次渲染都成功弯汰,這樣就能夠達(dá)到流暢的畫面所需的 60FPS。如果某個(gè)操作花費(fèi)的時(shí)間是 24ms 湖雹,系統(tǒng)在得到 VSYNC 信號(hào)時(shí)就無法正常進(jìn)行正常渲染咏闪,這樣就發(fā)生了丟幀現(xiàn)象。那么用戶在 32ms 內(nèi)看到的會(huì)是同一幀畫面摔吏,這種現(xiàn)象在執(zhí)行動(dòng)畫或滑動(dòng)列表比較常見鸽嫂,還有可能是你的 Layout 太過復(fù)雜,層疊太多的繪制單元征讲,無法在 16ms 完成渲染据某,最終引起刷新不及時(shí)。

卡頓根本原因

根據(jù)Android 系統(tǒng)顯示原理可以看到诗箍,影響繪制的根本原因有以下兩個(gè)方面:

  • 繪制任務(wù)太重癣籽,繪制一幀內(nèi)容耗時(shí)太長。
  • 主線程太忙,根據(jù)系統(tǒng)傳遞過來的 VSYNC 信號(hào)來時(shí)還沒準(zhǔn)備好數(shù)據(jù)導(dǎo)致丟幀筷狼。

繪制耗時(shí)太長瓶籽,有一些工具可以幫助我們定位問題。主線程太忙則需要注意了埂材,主線程關(guān)鍵職責(zé)是處理用戶交互塑顺,在屏幕上繪制像素,并進(jìn)行加載顯示相關(guān)的數(shù)據(jù)俏险,所以特別需要避免任何主線程的事情严拒,這樣應(yīng)用程序才能保持對(duì)用戶操作的即時(shí)響應(yīng)∈溃總結(jié)起來裤唠,主線程主要做以下幾個(gè)方面工作:

  • UI 生命周期控制
  • 系統(tǒng)事件處理
  • 消息處理
  • 界面布局
  • 界面繪制
  • 界面刷新

除此之外,應(yīng)該盡量避免將其他處理放在主線程中预鬓,特別復(fù)雜的數(shù)據(jù)計(jì)算和網(wǎng)絡(luò)請(qǐng)求等巧骚。

性能分析工具

性能問題并不容易復(fù)現(xiàn),也不好定位格二,但是真的碰到問題還是需要去解決的劈彪,那么分析問題和確認(rèn)問題是否解決,就需要借助相應(yīng)的的調(diào)試工具顶猜,比如查看 Layout 層次的 Hierarchy View沧奴、Android 系統(tǒng)上帶的 GPU Profile 工具和靜態(tài)代碼檢查工具 Lint 等,這些工具對(duì)性能優(yōu)化起到非常重要的作用长窄,所以要熟悉滔吠,知道在什么場景用什么工具來分析。

1挠日,Profile GPU Rendering

在手機(jī)開發(fā)者模式下疮绷,有一個(gè)卡頓檢測工具叫做:Profile GPU Rendering,如圖:

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)用過程喜德,可以對(duì) Android 的應(yīng)用程序以及 Framework 層的代碼進(jìn)行性能分析。它是一個(gè)圖形化的工具垮媒,最終會(huì)產(chǎn)生一個(gè)圖表舍悯,用于對(duì)性能分析進(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ù)娶耍,特別是在動(dòng)畫播放不流暢免姿、渲染卡等問題上。

優(yōu)化建議

1榕酒,布局優(yōu)化

布局是否合理主要影響的是頁面測量時(shí)間的多少胚膊,我們知道一個(gè)頁面的顯示測量和繪制過程都是通過遞歸來完成的,多叉樹遍歷的時(shí)間與樹的高度h有關(guān)想鹰,其時(shí)間復(fù)雜度 O(h)紊婉,如果層級(jí)太深,每增加一層則會(huì)增加更多的頁面顯示時(shí)間辑舷,所以布局的合理性就顯得很重要喻犁。

那布局優(yōu)化有哪些方法呢,主要通過減少層級(jí)惩妇、減少測量和繪制時(shí)間株汉、提高復(fù)用性三個(gè)方面入手「柩辏總結(jié)如下:

  • 減少層級(jí)乔妈。合理使用 RelativeLayout 和 LinerLayout,合理使用Merge氓皱。
  • 提高顯示速度路召。使用 ViewStub勃刨,它是一個(gè)看不見的、不占布局位置股淡、占用資源非常小的視圖對(duì)象身隐。
  • 布局復(fù)用∥椋可以通過 標(biāo)簽來提高復(fù)用贾铝。
  • 盡可能少用wrap_content。wrap_content 會(huì)增加布局 measure 時(shí)計(jì)算成本埠帕,在已知寬高為固定值時(shí)垢揩,不用wrap_content 。
  • 刪除控件中無用的屬性敛瓷。

2叁巨,避免過度繪制

過度繪制是指在屏幕上的某個(gè)像素在同一幀的時(shí)間內(nèi)被繪制了多次。在多層次重疊的 UI 結(jié)構(gòu)中呐籽,如果不可見的 UI 也在做繪制的操作锋勺,就會(huì)導(dǎo)致某些像素區(qū)域被繪制了多次,從而浪費(fèi)了多余的 CPU 以及 GPU 資源狡蝶。

如何避免過度繪制呢庶橱,如下:

  • 布局上的優(yōu)化。移除 XML 中非必須的背景牢酵,移除 Window 默認(rèn)的背景悬包、按需顯示占位背景圖片
  • 自定義View優(yōu)化。使用 canvas.clipRect()來幫助系統(tǒng)識(shí)別那些可見的區(qū)域馍乙,只有在這個(gè)區(qū)域內(nèi)才會(huì)被繪制布近。

3,啟動(dòng)優(yōu)化

通過對(duì)啟動(dòng)速度的監(jiān)控丝格,發(fā)現(xiàn)影響啟動(dòng)速度的問題所在撑瞧,優(yōu)化啟動(dòng)邏輯,提高應(yīng)用的啟動(dòng)速度显蝌。啟動(dòng)主要完成三件事:UI 布局预伺、繪制和數(shù)據(jù)準(zhǔn)備。因此啟動(dòng)速度優(yōu)化就是需要優(yōu)化這三個(gè)過程:

  • UI 布局曼尊。應(yīng)用一般都有閃屏頁酬诀,優(yōu)化閃屏頁的 UI 布局,可以通過 Profile GPU Rendering 檢測丟幀情況骆撇。
  • 啟動(dòng)加載邏輯優(yōu)化瞒御。可以采用分布加載神郊、異步加載肴裙、延期加載策略來提高應(yīng)用啟動(dòng)速度趾唱。
  • 數(shù)據(jù)準(zhǔn)備。數(shù)據(jù)初始化分析蜻懦,加載數(shù)據(jù)可以考慮用線程初始化等策略甜癞。

4,合理的刷新機(jī)制

在應(yīng)用開發(fā)過程中宛乃,因?yàn)閿?shù)據(jù)的變化悠咱,需要刷新頁面來展示新的數(shù)據(jù),但頻繁刷新會(huì)增加資源開銷烤惊,并且可能導(dǎo)致卡頓發(fā)生乔煞,因此,需要一個(gè)合理的刷新機(jī)制來提高整體的 UI 流暢度柒室。合理的刷新需要注意以下幾點(diǎn):

  • 盡量減少刷新次數(shù)。
  • 盡量避免后臺(tái)有高的 CPU 線程運(yùn)行逗宜。
  • 縮小刷新區(qū)域雄右。

5,其他

在實(shí)現(xiàn)動(dòng)畫效果時(shí)纺讲,需要根據(jù)不同場景選擇合適的動(dòng)畫框架來實(shí)現(xiàn)擂仍。有些情況下,可以用硬件加速方式來提供流暢度熬甚。

內(nèi)存優(yōu)化

在 Android 系統(tǒng)中有個(gè)垃圾內(nèi)存回收機(jī)制逢渔,在虛擬機(jī)層自動(dòng)分配和釋放內(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)會(huì)根據(jù)不同的規(guī)則自動(dòng)釋放系統(tǒng)認(rèn)為可以釋放的內(nèi)存哀蘑,也正是因?yàn)?Android 程序把內(nèi)存控制的權(quán)力交給了 Generational Heap Memory,一旦出現(xiàn)內(nèi)存泄漏和溢出方面的問題葵第,排查錯(cuò)誤將會(huì)成為一項(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ī)制就會(huì)導(dǎo)致一系列性能問題逐漸呈現(xiàn)将硝,對(duì)應(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對(duì)象生命周期

Java對(duì)象生命周期

一般Java對(duì)象在虛擬機(jī)上有7個(gè)運(yùn)行階段:

創(chuàng)建階段->應(yīng)用階段->不可見階段->不可達(dá)階段->收集階段->終結(jié)階段->對(duì)象空間重新分配階段

2误辑,內(nèi)存分配

在 Android 系統(tǒng)中沧踏,內(nèi)存分配實(shí)際上是對(duì)堆的分配和釋放。當(dāng)一個(gè) Android 程序啟動(dòng)巾钉,應(yīng)用進(jìn)程都是從一個(gè)叫做 Zygote 的進(jìn)程衍生出來翘狱,系統(tǒng)啟動(dòng) Zygote 進(jìn)程后,為了啟動(dòng)一個(gè)新的應(yīng)用程序進(jìn)程砰苍,系統(tǒng)會(huì)衍生 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)會(huì)為每一個(gè)應(yīng)用程序都設(shè)置一個(gè)硬性的 Dalvik Heap Size 最大限制閾值凰锡,整個(gè)閾值在不同設(shè)備上會(huì)因?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(持久代)智绸。最近分配的對(duì)象會(huì)存放在 Young Generation 區(qū)域。對(duì)象在某個(gè)時(shí)機(jī)觸發(fā) GC 回收垃圾访忿,而沒有回收的就根據(jù)不同規(guī)則瞧栗,有可能被移動(dòng)到 Old Generation,最后累積一定時(shí)間在移動(dòng)到 Permanent Generation 區(qū)域海铆。系統(tǒng)會(huì)根據(jù)內(nèi)存中不同的內(nèi)存數(shù)據(jù)類型分別執(zhí)行不同的 GC 操作迹恐。GC 通過確定對(duì)象是否被活動(dòng)對(duì)象引用來確定是否收集對(duì)象,進(jìn)而動(dòng)態(tài)回收無任何引用的對(duì)象占據(jù)的內(nèi)存空間卧斟。但需要注意的是頻繁的 GC 會(huì)增加應(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)是哪些具體對(duì)象導(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)存對(duì)象磷瘤,再進(jìn)一步分析這些大對(duì)象芒篷,進(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)用堆棧固蛾,可以查看所有對(duì)象內(nèi)存分配的周期结执。

4度陆,Memory Analyzer Tool(MAT)

MAT 是一個(gè)快速,功能豐富的 Java Heap 分析工具献幔,通過分析 Java 進(jìn)程的內(nèi)存快照 HPROF 分析懂傀,從眾多的對(duì)象中分析,快速計(jì)算出在內(nèi)存中對(duì)象占用的大小斜姥,查看哪些對(duì)象不能被垃圾收集器回收鸿竖,并可以通過視圖直觀地查看可能造成這種結(jié)果的對(duì)象。

常見內(nèi)存泄漏場景

如果在內(nèi)存泄漏發(fā)生后再去找原因并修復(fù)會(huì)增加開發(fā)的成本铸敏,最好在編寫代碼時(shí)就能夠很好地考慮內(nèi)存問題缚忧,寫出更高質(zhì)量的代碼,這里列出一些常見的內(nèi)存泄漏場景杈笔,在以后的開發(fā)過程中需要避免這類問題闪水。

  • 資源性對(duì)象未關(guān)閉。比如Cursor蒙具、File文件等球榆,往往都用了一些緩沖,在不使用時(shí)禁筏,應(yīng)該及時(shí)關(guān)閉它們持钉。
  • 注冊(cè)對(duì)象未注銷。比如事件注冊(cè)后未注銷篱昔,會(huì)導(dǎo)致觀察者列表中維持著對(duì)象的引用每强。
  • 類的靜態(tài)變量持有大數(shù)據(jù)對(duì)象。
  • 非靜態(tài)內(nèi)部類的靜態(tài)實(shí)例州刽。
  • Handler臨時(shí)性內(nèi)存泄漏空执。如果Handler是非靜態(tài)的,容易導(dǎo)致 Activity 或 Service 不會(huì)被回收穗椅。
  • 容器中的對(duì)象沒清理造成的內(nèi)存泄漏辨绊。
  • WebView。WebView 存在著內(nèi)存泄漏的問題匹表,在應(yīng)用中只要使用一次 WebView门坷,內(nèi)存就不會(huì)被釋放掉。

除此之外桑孩,內(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)化,在移動(dòng)設(shè)備上蜻势,由于物理設(shè)備的存儲(chǔ)空間有限撑刺,Android 系統(tǒng)對(duì)每個(gè)應(yīng)用進(jìn)程也都分配了有限的堆內(nèi)存,因此使用最小內(nèi)存對(duì)象或者資源可以減小內(nèi)存開銷握玛,同時(shí)讓GC 能更高效地回收不再需要使用的對(duì)象够傍,讓應(yīng)用堆內(nèi)存保持充足的可用內(nèi)存,使應(yīng)用更穩(wěn)定高效地運(yùn)行挠铲。常見做法如下:

  • 對(duì)象引用冕屯。強(qiáng)引用、軟引用拂苹、弱引用安聘、虛引用四種引用類型,根據(jù)業(yè)務(wù)需求合理使用不同瓢棒,選擇不同的引用類型浴韭。
  • 減少不必要的內(nèi)存開銷。注意自動(dòng)裝箱脯宿,增加內(nèi)存復(fù)用念颈,比如有效利用系統(tǒng)自帶的資源、視圖復(fù)用连霉、對(duì)象池舍肠、Bitmap對(duì)象的復(fù)用。
  • 使用最優(yōu)的數(shù)據(jù)類型窘面。比如針對(duì)數(shù)據(jù)類容器結(jié)構(gòu),可以使用ArrayMap數(shù)據(jù)結(jié)構(gòu)叽躯,避免使用枚舉類型财边,使用緩存Lrucache等等。
  • 圖片內(nèi)存優(yōu)化点骑『眩可以設(shè)置位圖規(guī)格,根據(jù)采樣因子做壓縮黑滴,用一些圖片緩存方式對(duì)圖片進(jìn)行管理等等憨募。

穩(wěn)定性優(yōu)化

Android 應(yīng)用的穩(wěn)定性定義很寬泛,影響穩(wěn)定性的原因很多袁辈,比如內(nèi)存使用不合理菜谣、代碼異常場景考慮不周全、代碼邏輯不合理等,都會(huì)對(duì)應(yīng)用的穩(wěn)定性造成影響尾膊。其中最常見的兩個(gè)場景是:Crash 和 ANR媳危,這兩個(gè)錯(cuò)誤將會(huì)使得程序無法使用,比較常用的解決方式如下:

  • 提高代碼質(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)化

在移動(dòng)設(shè)備中,電池的重要性不言而喻粘衬,沒有電什么都干不成荞估。對(duì)于操作系統(tǒng)和設(shè)備開發(fā)商來說,耗電優(yōu)化一致沒有停止稚新,去追求更長的待機(jī)時(shí)間勘伺,而對(duì)于一款應(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)用安裝包大小對(duì)應(yīng)用使用沒有影響笙瑟,但應(yīng)用的安裝包越大楼镐,用戶下載的門檻越高,特別是在移動(dòng)網(wǎng)絡(luò)情況下往枷,用戶在下載應(yīng)用時(shí)框产,對(duì)安裝包大小的要求更高,因此错洁,減小安裝包大小可以讓更多用戶愿意下載和體驗(yàn)產(chǎn)品秉宿。

常用應(yīng)用安裝包的構(gòu)成,如圖所示:

應(yīng)用安裝包構(gòu)成

從圖中我們可以看到:

  • assets文件夾屯碴。存放一些配置文件描睦、資源文件,assets不會(huì)自動(dòng)生成對(duì)應(yīng)的 ID导而,而是通過 AssetManager 類的接口獲取忱叭。

  • res。res 是 resource 的縮寫今艺,這個(gè)目錄存放資源文件韵丑,會(huì)自動(dòng)生成對(duì)應(yīng)的 ID 并映射到 .R 文件中,訪問直接使用資源 ID虚缎。

  • META-INF埂息。保存應(yīng)用的簽名信息,簽名信息可以驗(yàn)證 APK 文件的完整性遥巴。

  • AndroidManifest.xml。這個(gè)文件用來描述 Android 應(yīng)用的配置信息享幽,一些組件的注冊(cè)信息铲掐、可使用權(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 工具對(duì) 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í)比較低贝次,等進(jìn)入項(xiàng)目投入使用階段崔兴,就需要把優(yōu)先級(jí)提高,但在項(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)解決方案秆剪,最終解決問題赊淑,這也是日常工作中常會(huì)用到的處理方式。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末仅讽,一起剝皮案震驚了整個(gè)濱河市陶缺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌洁灵,老刑警劉巖饱岸,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異处渣,居然都是意外死亡伶贰,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門罐栈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來黍衙,“玉大人,你說我怎么就攤上這事荠诬±欧” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵柑贞,是天一觀的道長方椎。 經(jīng)常有香客問我,道長钧嘶,這世上最難降的妖魔是什么棠众? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮有决,結(jié)果婚禮上闸拿,老公的妹妹穿的比我還像新娘。我一直安慰自己书幕,他們只是感情好新荤,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著台汇,像睡著了一般苛骨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上苟呐,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天痒芝,我揣著相機(jī)與錄音,去河邊找鬼牵素。 笑死严衬,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的两波。 我是一名探鬼主播瞳步,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼腰奋!你這毒婦竟也來了单起?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤劣坊,失蹤者是張志新(化名)和其女友劉穎嘀倒,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體局冰,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡测蘑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了康二。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碳胳。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖沫勿,靈堂內(nèi)的尸體忽然破棺而出挨约,到底是詐尸還是另有隱情,我是刑警寧澤产雹,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布诫惭,位于F島的核電站,受9級(jí)特大地震影響蔓挖,放射性物質(zhì)發(fā)生泄漏夕土。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一瘟判、第九天 我趴在偏房一處隱蔽的房頂上張望怨绣。 院中可真熱鬧,春花似錦荒适、人聲如沸梨熙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽咽扇。三九已至,卻和暖如春陕壹,著一層夾襖步出監(jiān)牢的瞬間质欲,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國打工糠馆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嘶伟,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓又碌,卻偏偏與公主長得像九昧,于是被迫代替她去往敵國和親绊袋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容