Android——內存泄漏及OOM整理

內存泄漏

內存泄漏是指對象已經沒有被應用程序使用,但是垃圾回收器沒辦法移除它們,因爲還在被引用着。

下面是一些常見的Activity內存泄漏

靜態變量引用Activity

靜態變量引用Activty對象時,會導致Activty對象所佔內存內漏。

原因:靜態變量是駐紮在JVM的方法區,因此,靜態變量引用的對象是不會被GC回收的,因爲它們所引用的對象本身就是GC ROOT,即最終導致Activity對象不被回收,從而也就造成內存泄漏。

常見的錯誤用法:

將Context或Activity賦值給某個靜態變量

解決:

  • 去掉 static 關鍵字,使用別的方法來實現想要的功能。
  • 在 onDestroy 方法中置空 Activity 靜態引用
  • 也可以使用到軟引用解決,確保在 Activity 銷燬時,垃圾回收機制可以將其回收。

static間接修飾Activity

有時,當一個Activity經常啓動,但是對應的View讀取非常耗時,我們可以通過靜態View變量來保持對該Activity的rootView引用。這樣就可以不用每次啓動Activity都去讀取並渲染View了。這確實是一個提高Activity啓動速度的好方法!但是要注意,一旦View attach到我們的Window上,就會持有一個Context(即Activity)的引用。而我們的View有事一個靜態變量,所以導致Activity不被回收。當然了,也不是說不能使用靜態View,但是在使用靜態View時,需要確保在資源回收時,將靜態View detach掉。

常見問題

public class LoadingDialog extends Dialog {
    private static LoadingDialog mDialog;
    private TextView mText;
}

這裏 static 雖然沒有直接修飾 TextView(擁有 Context 引用),但是修飾了 mDialog 成員變量,mDialog 是 一個 LoadingDialog 對象, LoadingDialog 對象 包含一個 TextView 類型的成員變量,所以 mText 變量的生命週期也是全局的,和應用一樣。這樣,mText 持有的 Context 對象銷燬時,沒有 GC 回收,導致內存泄露。

解決:

  • 不使用static修飾
  • 在適當的地方置空 mDialog

單例引用Context

public class AppManager {
    private static AppManager instance;
    private Context context;

    private AppManager(Context context) {
        this.context = context;
    }

    public static AppManager getInstance(Context context) {
        if (instance == null) {
            instance = new AppManager(context);
        }
        return instance;
    }
} 

AppManager是一個單例類,他需要Context作爲成員變量,Context 如果是 Activity Context 的話,必然會引起內存泄漏。

解決

  • 使用 Applicaion Context 代替 Activity Context
  • 在調用的地方使用弱引用

匿名內部類執行耗時任務

public class MainActivity extends Activity {  
    @Override
    protected void onCreate(Bundle savedInstanceState) {    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        test();
    }  
    public void test() {    
        new Thread(new Runnable() {     
            @Override
            public void run() {        
                while (true) {          
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
} 

原因

  • 非靜態內部類或匿名類會擁有所在外部類的引用,new Thread是匿名內部類,它一直在執行當Activity消耗後,該匿名內部類還在執行任務,導致外部的Activity不能回收。

解決

  • 靜態匿名內部類,使用static修飾
    public static void test() {
        new Thread(new Runnable() {     
                @Override
                public void run() {        
                    while (true) {          
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
    } 
    

非靜態內部類

如果一個變量,既是靜態變量,而且是非靜態的內部類對象,那麼也會造成內存泄漏。

public class LeakActivity extends AppCompatActivity {
    
    private static Hello sHello;
    
    @Override    
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_leak);
        
        sHello = new Hello();
    }
    
    public class Hello {}
}

這裏我們定義的 Hello 雖然是空的,但它是一個非靜態的內部類,所以它必然會持有外部類即 LeakActivity.this 引用,導致 sHello 這個靜態變量一直持有這個 Activity,Activity 無法被回收。

Handler引起的內存泄漏

這與非靜態匿名內部類執行耗時任務一樣,錯誤代碼如下。

public class MainActivity extends Activity {
    private final Handler mLeakyHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            // ...
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mLeakyHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                // ...
            }
        }, 1000 * 60 * 10);
        finish();
    }
} 

在活動中發送了一個延時十分鐘消息的message,handler會將消息放入MessageQueue裏面。當活動被finish()時,Message還會繼續存在於主線程,Handler是非靜態內部類,會持有該活動的引用,所以此時finish()掉的活動就不會回收。

解決方法:

自定義靜態Handler

public class MainActivity extends Activity {

    private final MyHandler mHandler = new MyHandler(this);

    private static final Runnable mRunnable = new Runnable() {
        @Override
        public void run() { /* ... */ }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mHandler.postDelayed(mRunnable, 1000 * 60 * 10);
        finish();
    }

    private static class MyHandler extends Handler {
        private final WeakReference<MainActivity> mActivityReference;

        public MyHandler(MainActivity activity) {
            mActivityReference = new WeakReference<MainActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = mActivityReference.get();
            if (activity != null) {
                // ...
            }
        }
    }
} 

解釋:

  • 聲明一個靜態的MyHandler類,他不會隱式的持有MainActivity的引用
  • 內部利用弱引用獲取外部類的引用,若Activity回收,弱引用不會影響Activity的回收

資源對象沒有關閉

資源性對象比如(Cursor,File文件等)往往都用了一些緩衝,我們在不使用的時候,應該及時關閉它們,以便它們的緩衝及時回收內存。它們的緩衝不僅存在於 java虛擬機內,還存在於java虛擬機外。如果我們僅僅是把它的引用設置爲null,而不關閉它們,往往會造成內存泄漏。

OOM

OOM,全稱Out Of Memory,當JVM因爲沒有足夠的內存來爲對象分配空間並且垃圾回收器也已經沒有空間可回收時,就會拋出這個Error。

原因

  • 內存泄漏導致,頻繁的內存泄漏將會引發內存溢出。
  • 佔用內存較多的對象,保存了多個耗用內存較多的對象(如Bitmap),加載超大的圖片。

加載圖片OOM處理

  • 等比例縮小圖片
    使用setImageBitmap或setImageResource或BitmapFactory.decodeResource設置大圖時,這些函數在完成decode後,最終都是通過java層的createBitmap來完成的,需要消耗更多內存。
    public static Bitmap scaleImage(Bitmap bitmap, int newWidth, int newHeight) {
            if (bitmap == null) {
                return null;
            }
            float scaleWidth = (float) newWidth / bitmap.getWidth();
            float scaleHeight = (float) newHeight / bitmap.getHeight();
            Matrix matrix = new Matrix();
            matrix.postScale(scaleWidth, scaleHeight);
            return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
        }
    
  • 對圖片採用軟引用,及時地進行recycle()操作。
    雖然系統能夠確認Bitmap分配的內存最終會被銷燬,但是由於它佔用的內存過多,所以很可能會超過java堆的限制。因此,在用完Bitmap時要及時的recycle掉。recycle並不能確定立即就會將Bitmap釋放掉,但是會給虛擬機一個暗示:“該圖片可以釋放了”。
    SoftReference<Bitmap> bitmap;
        bitmap = new SoftReference<>(pBitmap);
        if(bitmap != null){
            if(bitmap.get() != null && !bitmap.get().isRecycled()){
                bitmap.get().recycle();
                bitmap = null;
            }
        }
    

其他OOM可使用LeakCanary檢測是否發生了內存泄漏。


對於圖片加載還有種情況,就是單個圖片非常巨大,並且還不允許壓縮。

那麼對於這種需求,該如何做呢?

首先不壓縮,按照原圖尺寸加載,那麼屏幕肯定是不夠大的,並且考慮到內存的情況,不可能一次性整圖加載到內存中,所以肯定是局部加載,那麼就需要用到一個類:BitmapRegionDecoder ,這個類提供了decodeRegion()方法來顯示指定的區域。

發佈了91 篇原創文章 · 獲贊 63 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章