您好,登錄后才能下訂單哦!
這篇文章主要介紹了Android性能優(yōu)化之內(nèi)存優(yōu)化的方法的相關(guān)知識,內(nèi)容詳細(xì)易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇Android性能優(yōu)化之內(nèi)存優(yōu)化的方法文章都會有所收獲,下面我們一起來看看吧。
先上一張JVM將內(nèi)存劃分區(qū)域的圖
程序計數(shù)器:存儲當(dāng)前線程執(zhí)行目標(biāo)方法執(zhí)行到第幾行。
棧內(nèi)存:Java棧中存放的是一個個棧幀,每個棧幀對應(yīng)一個被調(diào)用的方法。棧幀包括局部標(biāo)量表,
操作數(shù)棧。
本地方法棧:本地方法棧主要是為執(zhí)行本地方法服務(wù)的。而Java棧是為執(zhí)行Java方法服務(wù)的。
方法區(qū):該區(qū)域被線程共享。主要存儲每個類的信息(類名,方法信息,字段信息等)、靜態(tài)變量,常量,以及編譯器編譯后的代碼等。
堆:Java中的堆是被線程共享的,且JVM中只有一個堆內(nèi)存,主要存儲對象本身及數(shù)組
Dalvik:Dalvik是Google公司自己設(shè)計用于Android平臺的Java虛擬機。它可以支持已轉(zhuǎn)換為.dex格式的Java應(yīng)用程序的運行,.dex格式是專門為Dalvik應(yīng)用設(shè)計的一種壓縮格式,適合內(nèi)存和處理器速度有限的系統(tǒng),Dalvik經(jīng)過優(yōu)化,允許在有限的內(nèi)存中同時運行多個虛擬機實例,并且每一個Dalvik應(yīng)用做為獨立的Linux進程執(zhí)行,獨立的進程可以防止在虛擬機崩潰的時候所有程序都被關(guān)閉。
ART:ART表示Android Runtime,Dalvik是依靠一個just-In -Time編譯器去解釋字節(jié)碼,運行時編譯后的應(yīng)用都需要通過一個解釋器在用戶的設(shè)備上運行,這一機制并不是特別高效,但是能讓應(yīng)用更容易在不同的硬件和架構(gòu)上運行。ART則是完全改變了這種做法,在安裝應(yīng)用的時候就預(yù)編譯字節(jié)碼到機器語言,這一機制叫預(yù)編譯。在移除解釋代碼這一過程,應(yīng)用程序執(zhí)行將更有效率,啟動速度更快。
ART優(yōu)點:
1.系統(tǒng)性能更高
2.應(yīng)用啟動速度,運行更快,體驗更好,觸感反饋更加及時。
3.更長的電池續(xù)航能力
4.支持更低的硬件
ART缺點:
1.儲存空間占用更大。
2.應(yīng)用安裝時間更長。
Dalvik與ART區(qū)別
1.Dalvik每次都要編譯在運行,art只會安裝時啟動編譯
2.art占用的空間比Dalvik要大,就是用空間換時間
3.art減少編譯,減少CPU使用頻率,使用明顯改善電池續(xù)航
4.art啟動,運行更快,體驗更好,觸感反饋更及時。
1.減少oom,提高應(yīng)用的穩(wěn)定性
2.減少卡頓,體驗更好
3.減少內(nèi)存占用,應(yīng)用存活率更高
4.提前處理掉一些異常的隱患
2.1.1 引用計數(shù)算法
堆內(nèi)存的每個對象都有一個引用計數(shù)器,當(dāng)對象被引用的時候,計數(shù)器+1,當(dāng)引用失效時計數(shù)器-1,當(dāng)計數(shù)器的值為0時,說明該對象沒有被引用,就會被認(rèn)為是垃圾對象,系統(tǒng)將會將其回收內(nèi)存重新分配。
優(yōu)點:引用計數(shù)器執(zhí)行簡單,判定效率高。
缺點:對于循環(huán)引用的對象難以判斷出來,同時引用計數(shù)器增加了程序執(zhí)行的開銷,在jdk1.1后,就不在使用了。
2.1.1 根搜索法
GC Roots的對象做為起點,然后向下搜索,搜索所走過的路徑稱為引用鏈,當(dāng)一個對象到GC Roots沒有任何引用鏈相連時,則該對象不可達,也就是說該對象為為垃圾對象,可以被回收。
在Java中,可以做為GC Roots的對象包括一下四種:
1.虛擬機棧中引用的對象
2.方法區(qū)中的類靜態(tài)屬性引用的對象
3.方法區(qū)中常量引用的對象
4.本地方法棧中JNI的引用對象
2.2.1 標(biāo)記清除法
最基礎(chǔ)的垃圾收集算法,算法分為標(biāo)記和清除兩個階段:首先標(biāo)記出所有需要回收的對象,在標(biāo)記完成之后統(tǒng)一回收掉所有被標(biāo)記的對象。
缺點:效率低,其次會產(chǎn)生大量的不連續(xù)的內(nèi)存碎片,導(dǎo)致提前觸發(fā)另一次垃圾收集動作。
2.2.2 復(fù)制回收算法
復(fù)制回收算法是將可用內(nèi)存按容量分成大小相等的兩塊,每次只使用其中的一塊,當(dāng)這塊內(nèi)存使用完了,就將存活的對象復(fù)制到另一塊內(nèi)存上去,然后把使用過的內(nèi)存空間一次清理掉,這樣使得每都次都是對其中一塊內(nèi)存進行回收,內(nèi)存分配時不用考慮內(nèi)存碎片等復(fù)雜情況。
缺點:可使用內(nèi)存降為原來的一半。
2.2.3 標(biāo)記整理法
標(biāo)記-整理算法在標(biāo)記-清除算法的基礎(chǔ)上做了改進,標(biāo)記階段將可回收的對象標(biāo)記出來,標(biāo)記完成后不是直接對可回收的對象進行清理,而是讓所有存活的對象都向一端移動,在移動的過程中清理掉可回收的對象。
優(yōu)點:相比于標(biāo)記清除法來說,標(biāo)記整理法不會大量產(chǎn)生不連續(xù)內(nèi)存碎片問題。
缺點:如果是在對象存活率較高的情況下會執(zhí)行較多的復(fù)制操作,效率將會降低很多,而在存活率較低的情況下,效率會大大提高。
2.2.4 分代收集回收算法
當(dāng)前商業(yè)虛擬機都是采用的是分代收集算法,根據(jù)對象存活的周期不同將內(nèi)存劃分為幾塊,一般是將java堆分為年輕代,老年代和永久代。然后根據(jù)各個年代的特點來采取不同收集算法,年輕代存活率較低,采用復(fù)制回收算法,老年代對象存活率較高,采用標(biāo)記清除法或者是標(biāo)記整理法來進行回收。
內(nèi)存波動圖呈鋸齒狀,gc頻繁導(dǎo)致卡頓。
內(nèi)存泄露簡單來說就是系統(tǒng)分配出去的內(nèi)存由于某種原因?qū)е聸]法釋放,內(nèi)存會越來越小,最終導(dǎo)致oom。
即OOM,OOM時會導(dǎo)致程序異常。Android設(shè)備出廠以后,java虛擬機對單個應(yīng)用的最大內(nèi)存分配就確定下來了,超出這個值就會OOM。
Memory Profiler是Android studio自帶的工具,實時圖表形式展示應(yīng)用內(nèi)存使用的情況,可以用來識別內(nèi)存泄露,抖動等
注意:如果在控制臺中沒有找到Profiler,可View -----> Tool Windows ---> Profiler 進行打開
優(yōu)點:方便直觀,便于線下使用
1、強大的java heap分析工具,查找內(nèi)存泄露及內(nèi)存占用
2、生成整體報告,便于分析問題
3、可以在線下深入使用
MAT使用:
獲取hprof文件
導(dǎo)出來的Dump是沒法直接使用mat打開的,Android SDK自帶了一個轉(zhuǎn)換工具在SDK的platform-tools下,其中轉(zhuǎn)換語句為:
cd D:\aa\sdk\platform-tools
hprof-conv aaa.hprof bbb.hprof
注:aaa.hprof表示從profiler中導(dǎo)出來的dump文件,bbb.hprof 表示轉(zhuǎn)化出來的dump文件
使用mat打開轉(zhuǎn)化出來的dump
MAT視圖
在MAT窗口上,OverView是一個總體概覽,顯示總體的內(nèi)存消耗情況和疑似問題。
1、Histogram:列出內(nèi)存中的所有實例對象和個數(shù)以及大小,在頂部regex區(qū)域支撐正則表達式查找
2、Dominator Tree:列出最大的對象及其依賴存活的Object,相比于Histogram,能更方便的看出引用關(guān)系。
3、Top Consumers:通過圖像列出最大的Object
4、Leak Suspects:通過MAT自動分析內(nèi)存泄露的原因和泄露的一份總體報告
其中分析內(nèi)存情況,我們基本用到的就是Histogram和Dominator Tree
Class Name:類名。
Objects:對象實例個數(shù)。
Shallow Heap:對象自身占用內(nèi)存大小,不包括它引用的對象
Retained Heap:是當(dāng)前對象大小和直接或者間接引用到的對象大小總和,包括遞歸釋放的。
查找內(nèi)存泄露方式
步驟一:在Regex通過包名進行匹配,當(dāng)然也可以通過其他方式進行匹配
步驟二:右鍵選中懷疑對象,List objects --> with incoming references
注 with outgoing references 他引用了那些對象
with incoming references 那些對象引用了他
步驟三:選擇當(dāng)前的一個 Path to GC Roots/Merge to GC Roots 的 exclude All 弱軟虛引用。
圖標(biāo)的左下角出現(xiàn)這個,則表示出現(xiàn)了內(nèi)存泄露。然后回調(diào)代碼中分析即可。
使用
implementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
application中
public class App extends Application { private RefWatcher mRefWatcher; @Override public void onCreate() { super.onCreate(); mRefWatcher = LeakCanary.install(this); } public static RefWatcher getRefWatcher(Context context) { App application = (App) context.getApplicationContext(); return application.mRefWatcher; } }
在activity或者fragment中的onDestory()方法調(diào)用
RefWatcher refWatcher = App.getRefWatcher(getActivity()); refWatcher.watch(this);
原理
主要是通過WeakReference + ReferenceQueue來判斷對象是否被系統(tǒng)GC回收,WeakReference創(chuàng)建時,傳入一個ReferenceQueue對象,當(dāng)WeakReference引用的對象生命周期結(jié)束后,會被添加到ReferenceQueue中,當(dāng)GC過后,對象一直沒有被添加進入到ReferenceQueue,可能就會存在內(nèi)存泄露,再次觸發(fā)GC,二次確認(rèn)。
對于資源性對象不再使用時,應(yīng)該立即調(diào)用它的close()函數(shù),將其關(guān)閉,然后再置為null。例如Bitmap等資源未關(guān)閉會造成內(nèi)存泄漏,此時我們應(yīng)該在Activity銷毀時及時關(guān)閉。
例如BraodcastReceiver、EventBus未注銷造成的內(nèi)存泄漏,我們應(yīng)該在Activity銷毀時及時注銷。
盡量避免使用靜態(tài)變量存儲數(shù)據(jù),特別是大數(shù)據(jù)對象,建議使用數(shù)據(jù)庫存儲。
優(yōu)先使用Application的Context,如需使用Activity的Context,可以在傳入Context時使用弱引用進行封裝,然后,在使用到的地方從弱引用中獲取Context,如果獲取不到,則直接return即可。
該實例的生命周期和應(yīng)用一樣長,這就導(dǎo)致該靜態(tài)實例一直持有該Activity的引用,Activity的內(nèi)存資源不能正?;厥?。此時,我們可以將該內(nèi)部類設(shè)為靜態(tài)內(nèi)部類或?qū)⒃搩?nèi)部類抽取出來封裝成一個單例,如果需要使用Context,盡量使用Application Context,如果需要使用Activity Context,就記得用完后置空讓GC可以回收,否則還是會內(nèi)存泄漏。
Message發(fā)出之后存儲在MessageQueue中,在Message中存在一個target,它是Handler的一個引用,Message在Queue中存在的時間過長,就會導(dǎo)致Handler無法被回收。如果Handler是非靜態(tài)的,則會導(dǎo)致Activity或者Service不會被回收。并且消息隊列是在一個Looper線程中不斷地輪詢處理消息,當(dāng)這個Activity退出時,消息隊列中還有未處理的消息或者正在處理的消息,并且消息隊列中的Message持有Handler實例的引用,Handler又持有Activity的引用,所以導(dǎo)致該Activity的內(nèi)存資源無法及時回收,引發(fā)內(nèi)存泄漏。解決方案如下所示:
1、使用一個靜態(tài)Handler內(nèi)部類,然后對Handler持有的對象(一般是Activity)使用弱引用,這樣在回收時,也可以回收Handler持有的對象。
2、在Activity的Destroy或者Stop時,應(yīng)該移除消息隊列中的消息,避免Looper線程的消息隊列中有待處理的消息需要處理。需要注意的是,AsyncTask內(nèi)部也是Handler機制,同樣存在內(nèi)存泄漏風(fēng)險,但其一般是臨時性的。對于類似AsyncTask或是線程造成的內(nèi)存泄漏,我們也可以將AsyncTask和Runnable類獨立出來或者使用靜態(tài)內(nèi)部類。
在退出程序之前,將集合里的東西clear,然后置為null,再退出程序
WebView都存在內(nèi)存泄漏的問題,在應(yīng)用中只要使用一次WebView,內(nèi)存就不會被釋放掉。我們可以為WebView開啟一個獨立的進程,使用AIDL與應(yīng)用的主進程進行通信,WebView所在的進程可以根據(jù)業(yè)務(wù)的需要選擇合適的時機進行銷毀,達到正常釋放內(nèi)存的目的。
在構(gòu)造Adapter時,使用緩存的convertView。
強引用:我們平時開發(fā)寫的代碼,基本百分之九十九的都是強引用。
軟引用:如果一個對象具有軟引用,那么當(dāng)內(nèi)存不足時,就會回收它。
弱引用:GC時,只要發(fā)現(xiàn)有弱引用,那么就會回收它,當(dāng)然,有可能存在GC多次才發(fā)現(xiàn)
虛引用:虛引用必須要和引用隊列關(guān)聯(lián)起來使用。任何時候都有可能被垃圾回收器回收。一般可以用來判斷GC的頻率,GC頻率過高,那么說明內(nèi)存出了問題。同時也可以監(jiān)聽某個重要的對象是否被回收。
所以,在平時我們編寫代碼的時候,適當(dāng)?shù)氖褂密浺?,弱引用,對我們的?nèi)存優(yōu)化也能起到重要的作用。
1、AutoBoxing
自動裝箱的核心是吧基礎(chǔ)數(shù)據(jù)類型轉(zhuǎn)換成對應(yīng)的包裝類,比如int 類型只是占用4字節(jié),但是Integer對象占用16字節(jié)。
2、內(nèi)存復(fù)用
資源復(fù)用:通用的字符串,顏色定義,簡單頁面布局的復(fù)用
視圖復(fù)用:
3、使用優(yōu)化過的數(shù)據(jù)類型
如 SparseArray、SparseBooleanArray、LongSparseArray,使用這些API可以讓我們的程序更加高效。HashMap 工具類會相對比較 低效,因為它 需要為每一個鍵值對都提供一個對象入口,而 SparseArray 就 避免 掉了 基本數(shù)據(jù)類型轉(zhuǎn)換成對象數(shù)據(jù)類型的時間。
4、項目中少用枚舉
枚舉占用內(nèi)存是常量三倍。
5、在應(yīng)用可以內(nèi)存過低時主動釋放內(nèi)存
在application中的 onTrimMemory/onLowMemory,內(nèi)存緊張時會回調(diào)該方法,我們可以在這個方法中釋放掉圖片緩存,靜態(tài)緩存來避免被kill。
6、避免創(chuàng)建一些不必要的對象
如在字符串拼接時不要用“+”來進行拼接,而是使用StringBuffer,StringBuilder來替代。因為String 內(nèi)部是被final修飾的,不可繼承,使用+進行拼接是會產(chǎn)生一個新的對象,而占用內(nèi)存。
7、盡量不要在一些循環(huán)的地方創(chuàng)建對象。
如自定義的時候在onDraw()方法。
項目中會經(jīng)常遇到這樣的情況,我們的布局中,控件的寬高可能只是50 * 50 但是從服務(wù)器給過來的圖片或者是UI給過來的圖片往往會大很多,而如果圖片在資源文件下還好,可以直接查看寬高,但是如果從服務(wù)器上獲取到的呢,這是我們經(jīng)常會忽略的。而圖片過大,占用的內(nèi)存就更多,這是沒有必要的。那么我們怎么檢測出服務(wù)器給過來的圖片過大的呢?
這種方法我們可以重新測量圖片的寬高,超過一定的范圍,我們就可以輸出警告。但是這種方法對代碼侵入性很強。如果是有新同學(xué)加入,容易造成代碼混亂。
Hook的意思是鉤子,也就是在消息過去之前可以把消息勾住,不讓其傳遞,能夠針對不同的消息或者api在執(zhí)行之前,先執(zhí)行我們自己的操作。
這里推薦使用Epic 框架:https://github.com/tiann/epic
添加依賴
implementation 'me.weishu:epic:0.3.6'
創(chuàng)建一個ImageHook類
package com.optimize.performance.memory; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.util.Log; import android.view.View; import android.view.ViewTreeObserver; import android.widget.ImageView; import com.optimize.performance.utils.LogUtils; import com.taobao.android.dexposed.XC_MethodHook; public class ImageHook extends XC_MethodHook { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { super.afterHookedMethod(param); // 實現(xiàn)我們的邏輯 ImageView imageView = (ImageView) param.thisObject; checkBitmap(imageView,((ImageView) param.thisObject).getDrawable()); } private static void checkBitmap(Object thiz, Drawable drawable) { if (drawable instanceof BitmapDrawable && thiz instanceof View) { final Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap(); if (bitmap != null) { final View view = (View) thiz; int width = view.getWidth(); int height = view.getHeight(); if (width > 0 && height > 0) { // 圖標(biāo)寬高都大于view帶下的2倍以上,則警告 if (bitmap.getWidth() >= (width << 1) && bitmap.getHeight() >= (height << 1)) { warn(bitmap.getWidth(), bitmap.getHeight(), width, height, new RuntimeException("Bitmap size too large")); } } else { final Throwable stackTrace = new RuntimeException(); view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { int w = view.getWidth(); int h = view.getHeight(); if (w > 0 && h > 0) { if (bitmap.getWidth() >= (w << 1) && bitmap.getHeight() >= (h << 1)) { warn(bitmap.getWidth(), bitmap.getHeight(), w, h, stackTrace); } view.getViewTreeObserver().removeOnPreDrawListener(this); } return true; } }); } } } } private static void warn(int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight, Throwable t) { String warnInfo = new StringBuilder("Bitmap size too large: ") .append("\n real size: (").append(bitmapWidth).append(',').append(bitmapHeight).append(')') .append("\n desired size: (").append(viewWidth).append(',').append(viewHeight).append(')') .append("\n call stack trace: \n").append(Log.getStackTraceString(t)).append('\n') .toString(); LogUtils.i(warnInfo); } }
在application中
DexposedBridge.hookAllConstructors(ImageView.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { super.afterHookedMethod(param); DexposedBridge.findAndHookMethod(ImageView.class, "setImageBitmap", Bitmap.class, new ImageHook()); } });
這樣在開發(fā)者調(diào)用setImageBitmap 來設(shè)置圖片的時候,都會進行對圖片的寬高進行比如,如果超出一定的范圍則進行提示。
關(guān)于“Android性能優(yōu)化之內(nèi)存優(yōu)化的方法”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對“Android性能優(yōu)化之內(nèi)存優(yōu)化的方法”知識都有一定的了解,大家如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。