導(dǎo)讀
讀完本篇能學(xué)到以下知識
- 解決Activity的內(nèi)存泄漏
- Bitmap加載優(yōu)化
前言
內(nèi)存優(yōu)化是Android中優(yōu)化的一個重點(diǎn),內(nèi)存優(yōu)化不到位會引起頻繁的GC,導(dǎo)致耗電嚴(yán)重.
要做內(nèi)存優(yōu)化首先要找到優(yōu)化的對象.在Android開發(fā)中有兩個內(nèi)存大戶,Activity和Bitmap.Activity主要是防止內(nèi)存泄漏,Bitmap需要防止oom
Activity的內(nèi)存泄漏
所謂內(nèi)存泄漏就是這個對象至少有一條到達(dá)根節(jié)點(diǎn)的路徑.
那有哪些是根節(jié)點(diǎn)呢?
Java虛擬機(jī)中的根節(jié)點(diǎn)如下:
- 方法區(qū)中類靜態(tài)屬性引用的對象
- 方法區(qū)中常量引用的對象
- 本地方法棧中JNI(即一般說的Native方法)引用的對象
- Thread
還有比較隱蔽的內(nèi)部類,內(nèi)部類會默認(rèn)持有外部類的引用要注意下.
內(nèi)存泄漏情景
- 靜態(tài)引用持有
public class TestActivity extends Activity{
public static List list = new ArrayList();
@Override
protected void onCreate(Bundle savedInstanceState) {
...
list.add(this);
}
}
解決方法:及時remove會避免靜態(tài)引用持有.
- Handler發(fā)送消息
public class TestActivity extends Activity{
Handler handler = new Handler(){
@Override
public void handleMessage(){
...
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
...
handler.sendMessageDelay(msg, 100000);
}
}
因?yàn)閔andler現(xiàn)在是個匿名內(nèi)部類,所以持有Activity的引用,一旦有未發(fā)送的Message,Activity就會內(nèi)存泄漏.
解決方法
1. 在onDestroy()里讓handler移除所有消息
Activity:
...
@Override
protected void onCreate(Bundle savedInstanceState) {
handler.removeMessage(...);
}
...
2. 定義一個繼承Handler的static類,用WeakReference持有Activity(如果需要調(diào)用Activity的話)
class Activity{
MyHandler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
handler = new MyHandler(this);
}
private static class MyHandler extends Handler{
private WeakReference<Context> weakRef;
public Handler(Context context){
weakRef = new WeakReference<>(context);
}
@Override
public void handleMessage(){
...
}
}
}
- 執(zhí)行Thread
class Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
...
new Thread(new Runnable(){
@Override
public void run(){
//耗時操作
}
}).start();
}
}
解決線程導(dǎo)致的內(nèi)存泄漏跟Handler類似
1. 在onDestroy()里調(diào)用Thread.interrupt()結(jié)束線程
2. 把線程定義成靜態(tài)內(nèi)部類用WeakReference持有Activity
檢測內(nèi)存泄漏方法
- adb命令(最簡單,我最喜歡用)
adb shell dumpsys meminfo [包名]
用這個命令開始的時候打印一次,然后打開關(guān)閉泄漏Activity一定次數(shù),接著觸發(fā)GC(Profile里Memory的垃圾桶圖標(biāo)),最后再打印一次這個命令,看看兩次Activities的數(shù)量差是否和打開泄漏Activity次數(shù)一樣.如果不一樣說明部分(GC并不一定回收所有對象)Activity已經(jīng)被回收了,沒有內(nèi)存泄露.反之則內(nèi)存泄漏.
Views: 189 ViewRootImpl: 1
AppContexts: 11 Activities: 1
Assets: 2 AssetManagers: 2
Local Binders: 15 Proxy Binders: 25
Parcel memory: 4 Parcel count: 16
Death Recipients: 0 OpenSSL Sockets: 0
第一種方法前提是有懷疑泄漏的對象.如果沒有懷疑對象,那可以用LeakCanary.LeakCanary接入到應(yīng)用中會定期觸發(fā)GC來判斷是否內(nèi)存泄露,一泄露就會給你提示.
MAT
用MAT檢測內(nèi)存泄露步驟跟1類似,就是去對比兩次dump出來的內(nèi)存信息,但MAT功能更強(qiáng)大.他可以找出泄露對象被誰持有.
泄漏代碼:
Activity:
public static List list = new ArrayList();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
list.add(this);
}
具體操作如下:
- dump出當(dāng)前heap快照
-
用命令 hprof-conv 1.hprof 2.hprof轉(zhuǎn)換hprof文件(AS生成的hprof需要轉(zhuǎn)換才能被MAT識別)
用Leak Suspects Report打開2.hprof文件,點(diǎn)擊Histogram,
Show objects by class
1.png
Merge Shortest Paths to GC Roots-->exclude all phantom/weak/soft etc.reference
2.png
現(xiàn)在就可以看到持有該Activity的是一個list
3.png
Bitmap優(yōu)化
- 防止OOM
有時我們加載Bitmap,需要的只是一個很小的圖,但從本地或網(wǎng)絡(luò)加載的圖片非常的大,稍不注意就會報oom.
解決方法如下:
Bitmap bm;
BitmapFactory.Options opt = new BitmapFactory.Options();
//設(shè)置只加載大小不加載像素數(shù)據(jù)
opt.inJustDecodeBounds = true;
bm = BitmapFactory.decodeFile(absolutePath, opt);
//計算出你需要的比例
opt.inSampleSize = bm.getHeight()/neededHeight;//偽代碼
//inJustDecodeBounds設(shè)為true去加載像素數(shù)據(jù)
opt.inJustDecodeBounds = false;
bm = BitmapFactory.decodeFile(absolutePath, opt);
就是先加載原始圖的大小,接著計算出需要的比例傳給inSampleSize,再加載一遍圖片.
- 設(shè)備分級
對于一些低端機(jī)我們可以選擇降低圖片質(zhì)量(比如RGB_565) - 緩存
圖片緩存一般采取Lru的策略,其核心思想是“如果數(shù)據(jù)最近被訪問過原杂,那么將來被訪問的幾率也更高”.實(shí)現(xiàn)Lru策略可以用LinkedHashMap這個類構(gòu)造的時候傳入一個accessOrder為true
public LinkedHashMap(int initialCapacity,
float loadFactor,
boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
這時LinkedHashMap就會生成一個雙端隊(duì)列,get的時候把值插到最前面實(shí)現(xiàn)Lru的策略效果,實(shí)現(xiàn)原理可以參考這篇文章.