android性能評測與優(yōu)化-內(nèi)存

書籍推薦

市面上android性能優(yōu)化的書籍不多
因為性能優(yōu)化這塊稍微深入一點,涉及知識的深度和廣度就比較大了,而且性能優(yōu)化依賴很多的平臺相關(guān)的工具和分析技巧坦弟,導(dǎo)致通用性和實效性又不太高蹋辅,所以以下書籍的內(nèi)容也比較淺嘗輒止

移動App性能評測與優(yōu)化

  • 總體不錯照筑,但是給出的實例場景對于日常的開發(fā)工作來說比較不常見到
  • 網(wǎng)絡(luò)優(yōu)化這章寫的感覺最好炸卑,初看一遍感覺有不少啟示既鞠,正在繼續(xù)啃

Android移動性能實戰(zhàn)

  • 工具介紹的比較多
  • 給出的例子比較常見,但是介紹的比較潦草
  • 夾帶私貨

Android應(yīng)用性能優(yōu)化

  • jni層的知識和優(yōu)化講的比較多
  • 除去jni的部分之后的內(nèi)容比較像一個個tips盖文,內(nèi)容比較散

內(nèi)存性能分析及優(yōu)化的意義

Overview of memory management
內(nèi)存管理介紹

OOM

  • 系統(tǒng)分配給app的堆內(nèi)存是有上限的嘱蛋,不是系統(tǒng)空閑多少內(nèi)存app就可以用多少,getMemoryClass()可以獲取到這個值
  • 可以在manifest文件中設(shè)置largeHeap為true,這樣會增大堆內(nèi)存上限五续,getLargeMemoryClass()可以獲取到這個值
  • 超出虛擬機(jī)堆內(nèi)存上限會造成OOM

Low Memory Killer

  • android內(nèi)存管理使用了分頁(paging)和內(nèi)存映射(memory-mapping)技術(shù)洒敏,但是沒有使用swap,而是使用Low Memory Killer策略來提升物理內(nèi)存的利用率 疙驾,導(dǎo)致除了gc和殺死進(jìn)程回收物理內(nèi)存之外沒有其他方式來利用已經(jīng)被占用的內(nèi)存
  • 當(dāng)前臺應(yīng)用切換到后臺后凶伙,系統(tǒng)并不結(jié)束它的進(jìn)程,而是把它緩存起來它碎,供下次啟動函荣。當(dāng)系統(tǒng)內(nèi)存不足時,按最近最少使用+優(yōu)先釋放內(nèi)存使用密集的策略釋放緩存進(jìn)程扳肛。

GC

  • 內(nèi)存使用的多也會造成GC速度變慢傻挂,造成卡頓
  • 內(nèi)存占用過高,在創(chuàng)建對象時內(nèi)存不足,很容易造成觸發(fā)GC影響APP性能

綜上

關(guān)注并減少應(yīng)用的內(nèi)存消耗可以減少oom的概率挖息,在內(nèi)存緊張的場景下獲得更好的用戶體驗金拒,也可以增加應(yīng)用的后臺存活時間

工具介紹

調(diào)查 RAM 使用情況

  • GC-LOG
  • dumpsys meminfo
  • Profiler
  • jhat

dumpsys procstats

用來衡量一段時間內(nèi)應(yīng)用消耗內(nèi)存的情況

  • PSS:Proportional Set Size按比例分配共享內(nèi)存的實際內(nèi)存
  • USS:Unique Set Size進(jìn)程私有內(nèi)存
    PSS=USS+共享內(nèi)存/共享內(nèi)存的進(jìn)程數(shù)

(最小PSS-平均PSS-最大PSS/最小USS-平均USS-最大USS)


procstats.png

LeakCanary

檢測內(nèi)存泄漏的工具

MAT

比較常用的內(nèi)存dump文件分析工具

使用方法

  • 使用Memory Profiler Dump內(nèi)存數(shù)據(jù)
  • 導(dǎo)出的hprof文件不是MAT的標(biāo)準(zhǔn)文件,需要使用sdk帶的hprof-conv轉(zhuǎn)換
hprof-conv -z src dst //-z可以排除android框架創(chuàng)建的對象

使用場景

  • 總體性找出內(nèi)存優(yōu)化的瓶頸
  • 只有dump文件的現(xiàn)實場景套腹,或者無法定位具體問題等只有現(xiàn)場而沒有線索的情況下庖丁解牛的工具
  • 對于專項功能的內(nèi)存優(yōu)化感覺不如代碼調(diào)試+profiler

分析場景構(gòu)建

性能測試的一些注意點

  • 需要考慮盡量真實的場景
  • 需要關(guān)閉log等調(diào)試組件避免干擾

常見的性能測試方式

  • 切換到后臺
  • 反復(fù)執(zhí)行功能
  • 長時間執(zhí)行功能
  • 多個場景來回切換

容易出現(xiàn)內(nèi)存問題的場景

  • 包含了圖片顯示的界面
  • 網(wǎng)絡(luò)傳輸大量數(shù)據(jù)的場景
  • 需要緩存數(shù)據(jù)的場景

常見的內(nèi)存問題

內(nèi)存泄漏

內(nèi)存泄漏產(chǎn)生的原因

一個對象的生命周期已經(jīng)結(jié)束了绪抛,但是有其他對象持有了它的實例導(dǎo)致無法在GC時被回收,在Android中通常是Activity在finish之后依然有對象引用它導(dǎo)致內(nèi)存泄漏

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

  • 異步操作中異步邏輯未結(jié)束电禀,而Activity結(jié)束或者重建了
    • Thread/Handler/AsyncTask/Rxjava/Timer等
  • 使用靜態(tài)變量或者單例直接或者間接的保存Activty實例但是未及時釋放
  • 注冊廣播未注銷
  • ObjectAnimator未調(diào)用cancel
  • I/O操作等完成后未及時關(guān)閉或者釋放
  • WebView造成的內(nèi)存泄漏 Android 5.1 WebView內(nèi)存泄漏分析

內(nèi)存泄漏在分析工具上的表現(xiàn)

內(nèi)存泄漏.png

每次activity的重建都會造成內(nèi)存上升且gc不會使內(nèi)存使用降低

內(nèi)存泄漏的避免

  • LeakCanary
  • StrictMode
  • 沒有必要使用Activity作為Context的地方全部使用ApplicationContext
  • 使用WeakReference
  • 使用ViewModel+LiveData/RxJava+Rxlifecycle等工具實現(xiàn)異步邏輯避免內(nèi)存泄漏
  • 對需要銷毀時進(jìn)行處理的操作進(jìn)行檢查幢码,如xxx.cancel()/xxx.close()/xxx.unregister()/xxx.remove()等操作

內(nèi)存抖動

內(nèi)存抖動的原因

內(nèi)存抖動一般是瞬間創(chuàng)建了大量對象,會在短時間內(nèi)觸發(fā)多次GC尖飞,產(chǎn)生卡頓

內(nèi)存抖動的場景

  • IM通知需要轉(zhuǎn)發(fā)到所有WebView界面症副,當(dāng)剛打開APP時多個通知同時到達(dá),或者在群聊中消息很多的場景下葫松,會造成短時間內(nèi)頻繁GC瓦糕,同時伴隨界面卡頓

內(nèi)存抖動的在分析工具上的表現(xiàn)

內(nèi)存抖動.jpg

制造了一個內(nèi)存抖動的場景

 public void testThrashing(boolean needLog) {
        int dimension = 300;
        int[][] lotsOfInts = new int[dimension][dimension];
        Random randomGenerator = new Random();
        for (int i = 0; i < lotsOfInts.length; i++) {
            for (int j = 0; j < lotsOfInts[i].length; j++) {
                lotsOfInts[i][j] = randomGenerator.nextInt();
            }
        }
        //優(yōu)化以前
        for (int i = 0; i < lotsOfInts.length; i++) {
            String rowAsStr = "";
            int[] sorted = getSorted(lotsOfInts[i]);
            for (int j = 0; j < lotsOfInts[i].length; j++) {
                rowAsStr += sorted[j];
                if (j < (lotsOfInts[i].length - 1)) {
                    rowAsStr += ", ";
                }
            }
            if (needLog) {
                Log.i(TAG, "Row " + i + ": " + rowAsStr);
            }
        }
    }

    public void optimizeThrashing() {
        int dimension = 300;
        int[][] lotsOfInts = new int[dimension][dimension];
        Random randomGenerator = new Random();
        for (int i = 0; i < lotsOfInts.length; i++) {
            for (int j = 0; j < lotsOfInts[i].length; j++) {
                lotsOfInts[i][j] = randomGenerator.nextInt();
            }
        }
        //優(yōu)化以后
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < lotsOfInts.length; i++) {
            sb.delete(0, sb.length());
            int[] sorted = getSorted(lotsOfInts[i]);
            for (int j = 0; j < lotsOfInts[i].length; j++) {
                sb.append(sorted[j]);
                if (j < (lotsOfInts[i].length - 1)) {
                    sb.append(", ");
                }
            }
            Log.e(TAG, "Row " + i + ": " + sb);
        }

    }
  • 自己試驗的感受底洗,gc帶來的卡頓其實并不明顯(也可能是demo不太復(fù)雜腋么,GC耗時不長)
  • 個人感覺卡頓主要是因為內(nèi)存抖動大多出現(xiàn)在一些復(fù)雜場景,通常伴隨著主線程的大量操作已經(jīng)出現(xiàn)了卡頓亥揖,而內(nèi)存抖動引起的頻繁GC會加劇卡頓的程度

解決方案

  • 最簡單的做法就是把之前的主線程操作放到子線程去珊擂,雖然內(nèi)存抖動依然存在圣勒,但是卡頓問題可以大大緩解
  • 對于內(nèi)存抖動本身
    • 盡量避免在循環(huán)體內(nèi)創(chuàng)建對象,應(yīng)該把對象創(chuàng)建移到循環(huán)體外
    • 需要大量使用Bitmap和其他大型對象時摧扇,盡量嘗試復(fù)用之前創(chuàng)建的對象
  • 對于黑盒子(例如之前例子中im通知造成的webview的內(nèi)存抖動和主線程耗時操作)
    • 控制觸發(fā)頻率圣贸,減輕卡頓程度
    • 添加注冊機(jī)制,需要接收通知的頁面才發(fā)送通知

圖片加載的內(nèi)存占用

不同dpi文件夾對圖片內(nèi)存的影響

  • 不同dpi限定符對應(yīng)的dpi
    xxxhdpi-640
    xxhdpi-480
    xhdpi-320
    mdpi-160

  • 通過resId加載的Bitmap的寬高計算
    bitmap寬高=圖片實際寬高*屏幕dpi/文件夾對應(yīng)的dpi

  • nodpi
    從這個文件夾中加載的圖片資源生成的Bitmap會保持圖片本身的尺寸

  • 1920*1080圖片資源放在不同的文件夾下加載的Bitmap大小計算
    使用設(shè)備小米note扛稽,設(shè)備dpi為440

文件夾 對應(yīng)dpi bitmap width height size 倍數(shù)
nodpi 1920 1080 8294400 1
xxxhdpi 640 1320 743 3923040 0.47
xxhdpi 480 1760 990 6969600 0.84
xhdpi 320 2640 1485 15681600 1.89
mdpi 160 5280 2970 62726400 7.56
圖片加載.png
  • 使用圖片的建議
    • 盡量使用1080p的尺寸下的切圖
    • 圖片盡量放xxhdpi以上的文件夾下
    • 大圖如Splash頁和引導(dǎo)頁的圖片放在nodpi文件夾下,通過控制ImageView大小來限制圖片大小
    • 按照上面操作會導(dǎo)致apk大小增加吁峻,可以將圖片轉(zhuǎn)成webp并進(jìn)行壓縮

RGB565

除了圖片資源的文件夾,加載圖片時使用的色彩模式也影響了Bitmap大小在张。ARGB8888使用了32bit用含,所以一個像素需要4byte;RGB565使用了16bit帮匾,一個像素只需要2byte
但是因為RGB565少了alpha通道啄骇,對有透明度的圖片顯示有問題,而且顯示效果上還是有些區(qū)別瘟斜,所以并不建議修改這個屬性缸夹,只是在對內(nèi)存有嚴(yán)格要求的場景下可以作為特殊手段進(jìn)行優(yōu)化

ProGuard對內(nèi)存的影響

壓縮代碼和資源

  • ProGuard可以對類、方法和變量重命名螺句,剔除無用代碼和資源虽惭,減小dex大小,除了減小了apk的大小壹蔓,同時也減小了加載dex所需的內(nèi)存
  • 因為虛擬機(jī)加載dex文件是按需加載的趟妥,而內(nèi)存分配的最小單位是頁,所以加載一個功能的代碼時同一個內(nèi)存頁中也會加載dex文件中該功能前后不相關(guān)的代碼佣蓉,ProGuard可以重新排序類的字節(jié)碼在dex文件中位置披摄,使得有相互調(diào)用關(guān)系的類在dex中更加緊湊,加載相同功能所需的內(nèi)存更小

內(nèi)存碎片

Overview of memory management
內(nèi)存碎片

Davik的內(nèi)存回收算法不能移動對象勇凭,所以會造成一個小對象占據(jù)整個內(nèi)存頁疚膊,產(chǎn)生內(nèi)存碎片
而ART虛擬機(jī)的可以在GC時對內(nèi)存空間進(jìn)行整理,隨著5.0以上系統(tǒng)的占有率逐漸提升虾标,內(nèi)存碎片造成的內(nèi)存消耗可以不必過于關(guān)心

其他內(nèi)存問題

Manage your app's memory

  • 頁面不可見收到onTrimMemory(TRIM_MEMORY_UI_HIDDEN)時釋放UI資源
  • 通過getMemoryInfo()獲取內(nèi)存信息寓盗,保證自己不開辟大內(nèi)存導(dǎo)致oom
  • 謹(jǐn)慎的使用Service
    • 使用IntentService
    • 使用JobScheduler進(jìn)行后臺調(diào)度
  • 使用優(yōu)化的容器如SparseArray
  • 代碼抽象會帶來額外的內(nèi)存消耗
  • 使用@IntDef、@StringDef代替枚舉
    ...

to be continue...

  • 流暢度
  • 耗電
  • 網(wǎng)絡(luò)
  • apk瘦身
  • Matrix
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末璧函,一起剝皮案震驚了整個濱河市傀蚌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蘸吓,老刑警劉巖善炫,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異库继,居然都是意外死亡箩艺,警方通過查閱死者的電腦和手機(jī)窜醉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來艺谆,“玉大人榨惰,你說我怎么就攤上這事【蔡溃” “怎么了琅催?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長虫给。 經(jīng)常有香客問我恢暖,道長,這世上最難降的妖魔是什么狰右? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任杰捂,我火速辦了婚禮,結(jié)果婚禮上棋蚌,老公的妹妹穿的比我還像新娘嫁佳。我一直安慰自己,他們只是感情好谷暮,可當(dāng)我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布蒿往。 她就那樣靜靜地躺著,像睡著了一般湿弦。 火紅的嫁衣襯著肌膚如雪瓤漏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天颊埃,我揣著相機(jī)與錄音蔬充,去河邊找鬼。 笑死班利,一個胖子當(dāng)著我的面吹牛饥漫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播罗标,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼庸队,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了闯割?” 一聲冷哼從身側(cè)響起彻消,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宙拉,沒想到半個月后宾尚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡鼓黔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年央勒,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片澳化。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡崔步,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出缎谷,到底是詐尸還是另有隱情井濒,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布列林,位于F島的核電站瑞你,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏希痴。R本人自食惡果不足惜者甲,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望砌创。 院中可真熱鬧虏缸,春花似錦、人聲如沸嫩实。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽甲献。三九已至宰缤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間晃洒,已是汗流浹背慨灭。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留球及,地道東北人缘挑。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像桶略,于是被迫代替她去往敵國和親语淘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,055評論 2 355

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