Android 常见内存泄漏及解决方法
Android中的內存泄漏:
先說一下為什么會出現內存泄漏:
Android程序開發中,如果一個對象已經不需要被使用了,本該被回收時,而這時另一個對象還在持有對該對象的引用,這樣就會導致無法被GC回收,就會出現內存泄漏的情況。
內存泄漏時Android程序中出現OOM問題的主要原因之一。所以我們在編寫代碼時,一定要細心處理好這一類的問題。
下面說一下Android開發中最常見的5個內存泄漏問題:
一:單例設計模式造成的內存泄漏:
單例設計模式我就不多說了,這個是最基本的設計模式,相信大家都會使用,但是時候我們在使用單例設計模式時沒有注意到其中的細節,就會造成內存泄漏。
單例設計模式的靜態特性會使他的生命周期和應用程序的生命周期一樣長,這就說明了如果一個對象不在使用了,而這時單例對象還在持有該對象的引用,這時GC就會無法回收該對象,造成了內存泄露的情況。
?下面是錯誤的單例設計模式的代碼:
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;
? ? }
}
上面的代碼是一個最普通的單例模式,但是需要注意兩個問題:
1、如果我們傳入的Context是Application的Context的話,就沒有任何問題,因為Application的Context生命周期和應用程序生命周期一樣長。
2、如果我們傳入的Context是Activity的Context的話,這時如果我們因為需求銷毀了該Activity的話,Context也會隨著Activity被銷毀,但是單例還在持有對該類對象的引用,這時就會造成內存泄漏。
所以,正確的單例模式寫法應該是這樣的:
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,單例的生命周期和應用一樣長,這樣就不會造成內存泄漏了。
二、非靜態內部類創建的靜態實例造成的內存泄漏
有時候因為需求我們會去頻繁的啟動一個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回收,就會造成了內存溢出,解決辦法:
1、將內部類改為靜態內部類。
2、將這個內部類封裝成一個單例,Context使用Application的Context
三、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方法中移除消息隊列中的消息。
四、線程造成的內存泄漏
線程使用不恰當造成的內存泄漏也是很常見的,下面舉兩個例子:
//——————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()方法來取消相應的任務。避免在后臺運行浪費資源。
五、資源未關閉造成的內存泄漏
在使用完BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等資源時,一定要在Activity中的OnDestry中及時的關閉、注銷或者釋放內存,
否則這些資源不會被GC回收,就會造成內存泄漏。
---------------------?
原文:https://blog.csdn.net/qq_35373333/article/details/74909811?
?
總結
以上是生活随笔為你收集整理的Android 常见内存泄漏及解决方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: eclipse xml 注释快捷键
- 下一篇: Android 固定式底部上滑抽屉vie