Android內存泄漏以及解決辦法

Android程序開發中,若是一個對象已經不須要被使用了,本該被回收時,而這時另外一個對象還在持有對該對象的引用,這樣就會致使沒法被GC回收,就會出現內存泄漏的狀況。java

內存泄漏時Android程序中出現OOM問題的主要緣由之一。設計模式

Android開發中最多見的5個內存泄漏問題:ide

一:單例設計模式形成的內存泄漏:函數

單例設計模式我就很少說了,這個是最基本的設計模式,相信你們都會使用,可是時候咱們在使用單例設計模式時沒有注意到其中的細節,就會形成內存泄漏。oop

單例設計模式的靜態特性會使他的生命週期和應用程序的生命週期同樣長,這就說明了若是一個對象不在使用了,而這時單例對象還在持有該對象的引用,這時GC就會沒法回收該對象,形成了內存泄露的狀況。this

 下面是錯誤的單例設計模式的代碼:線程

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;
    }
}

上面的代碼是一個最普通的單例模式,可是須要注意兩個問題:
一、若是咱們傳入的Context是Application的Context的話,就沒有任何問題,由於Application的Context生命週期和應用程序生命週期同樣長。設計

二、若是咱們傳入的Context是Activity的Context的話,這時若是咱們由於需求銷燬了該Activity的話,Context也會隨着Activity被銷燬,可是單例還在持有對該類對象的引用,這時就會形成內存泄漏。code

因此,正確的單例模式寫法應該是這樣的:
 server

public class AppManager {
    private static AppManager instance;
    private Context context;
    private AppManager(Context context) {
        this.context = context.getApplicationContext();
    }
    public static AppManager getInstance(Context context) {
        if (instance != null) {
            instance = new AppManager(context);
        }
        return instance;
    }
}

這樣的話無論咱們傳入什麼樣的Context,最終使用的都是Application的Context,單例的生命週期和應用同樣長,這樣就不會形成內存泄漏了。

2、非靜態內部類建立的靜態實例形成的內存泄漏

有時候由於需求咱們會去頻繁的啓動一個Activity,這時爲了不頻繁的建立相同的數據源,咱們一般會作以下處理:

public class MainActivity extends AppCompatActivity {
 
    private static TestResource mResource = null;
 
    @Override
 
    protected void onCreate(Bundle savedInstanceState) {
 
        super.onCreate(savedInstanceState);
 
        setContentView(R.layout.activity_main);
 
        if(mManager == null){
 
            mManager = new TestResource();
 
        }
 
        //...
 
    }
 
    class TestResource {
 
        //...
 
    }
 
}

這樣就在Activity中建立了非靜態內部類,非靜態內部類默認持有Activity類的引用,可是他的生命週期仍是和應用程序同樣長,因此當Activity銷燬時,靜態內部類的對象引用不會被GC回收,就會形成了內存溢出,解決辦法:

一、將內部類改成靜態內部類。

二、將這個內部類封裝成一個單例,Context使用Application的Context

3、Handler形成的內存泄漏:

先看一下不規範的Handler寫法:

public class MainActivity extends AppCompatActivity {
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            //...
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        loadData();
    }
    private void loadData(){
        //...request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }
}

這裏的handler也是一個非靜態匿名內部類,他跟上面的同樣,也會持有Activity的引用,咱們知道handler是運行在一個Looper線程中的,而Looper線程是輪詢來處理消息隊列中的消息的,假設咱們處理的消息有十條,而當他執行到第6條的時候,用戶點擊了back返回鍵,銷燬了當前的Activity,這個時候消息尚未處理完,handler還在持有Activity的引用,這個時候就會致使沒法被GC回收,形成了內存泄漏。正確的作法是:
 

public class MainActivity extends AppCompatActivity {
//new一個自定義的Handler
    private MyHandler mHandler = new MyHandler(this);
    private TextView mTextView ;
 
//自定義靜態內部類繼承自Handler
    private static class MyHandler extends Handler {
        private WeakReference<Context> reference;
//在構造函數中使用弱引用來引用context對象
        public MyHandler(Context context) {
            reference = new WeakReference<>(context);
        }
        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = (MainActivity) reference.get();
            if(activity != null){
                activity.mTextView.setText("");
            }
        }
    }
  
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView)findViewById(R.id.textview);
        loadData();
    }
  
    private void loadData() {
        //...request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }
 
@Override
  protected void onDestroy() {
      super.onDestroy();
//移除隊列中全部的Runable和消息
//這裏也可使用mHandler.removeMessage和mHandler.removeCallBacks來移除指定的Message和Runable
      mHandler.removeCallbacksAndMessages(null);
  }
}

建立一個靜態內部類繼承自handler,而後再在構造參數中對handler持有的對象作弱引用,這樣在回收時就會回收了handler持有的對象,這裏還作了一處修改,就是當我
們的回收了handler持有的對向,即銷燬了該Activity時,這時若是handler中的還有未處理的消息,咱們就須要在OnDestry方法中移除消息隊列中的消息。

4、線程形成的內存泄漏

線程使用不恰當形成的內存泄漏也是很常見的,下面舉兩個例子:

//——————test1
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... params) {
                SystemClock.sleep(10000);
                return null;
            }
        }.execute();
//——————test2
        new Thread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(10000);
            }
        }).start();

上面是兩個內部類,當咱們的Activity銷燬時,這兩個任務沒有執行完畢,就會使Activity的內存資源沒法被回收,形成了內存泄漏。

正確的作法是使用靜態內部類:以下

static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
        private WeakReference<Context> weakReference;
  
        public MyAsyncTask(Context context) {
            weakReference = new WeakReference<>(context);
        }
  
        @Override
        protected Void doInBackground(Void... params) {
            SystemClock.sleep(10000);
            return null;
        }
  
        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
            MainActivity activity = (MainActivity) weakReference.get();
            if (activity != null) {
                //...
            }
        }
    }
    static class MyRunnable implements Runnable{
        @Override
        public void run() {
            SystemClock.sleep(10000);
        }
    }
//——————
    new Thread(new MyRunnable()).start();
    new MyAsyncTask(this).execute();

這樣就避免了內存泄漏,固然在Activity銷燬時也要記得在OnDestry中調用AsyncTask.cancal()方法來取消相應的任務。避免在後臺運行浪費資源。

5、資源未關閉形成的內存泄漏 在使用完BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等資源時,必定要在Activity中的OnDestry中及時的關閉、註銷或者釋放內存, 不然這些資源不會被GC回收,就會形成內存泄漏。  

相關文章
相關標籤/搜索