上篇主要讲到如何使用工具去分析查找内存泄漏,那么现在主要讲一下常见的内存泄漏及其解决方法。

常见的泄漏

1. 单例模式导致内存泄漏(实质是静态变量引用Activity

public class SingleUtils {  
    private static SingleUtils mInstance = null;  
    private Context context;  
    private SingleUtils (Context context){  
        this.context = context;  
    }  
  
    public static SingleUtils getInstance(Context context){  
        if(mInstance == null){  
            mInstance = new SingleUtils (context);  
        }  
        return mInstance;  
    }  
  
    public Object getObject(){//根据业务逻辑传入参数  
        //返回业务逻辑结果,这里需要用到context  
    }  
}



单例由于它的静态特性使得其生命周期跟应用一样长,如果我们把上下文context(比如说一个Activity)传入到了单例类中的执行业务逻辑,这时候静态变量就引用了我们的Activity,如果没有及时置空,就会在这个Activity finish的时候,导致该Activty一直驻留在内存中,并发生内存泄漏。


解决方案:

在单例中我们尽可能的引用生命周期较长的对象,将第10行代码修改如下即可。

mInstance = new SingleUtils (context.getApplicationContext());



2. 内部类导致内存泄漏


如果我们在一个外部类中定义一个静态变量,这个静态变量是非静态内部类对象,这就会导致内存泄漏,因为非静态内部类会持有外部类的引用,从而

间接导致静态地引用了外部类 。


public class MyActivity extends Activity {  
    //非静态内部类InnerClass创建的静态实例mInnerClass  
    private static InnerClass mInnerClass = null;  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);  
        mInnerClass = new InnerClass();  
    }  
     class InnerClass{  
    }  
}

解决方案:

(1)在onDestroy方法中手动将mInnerClass置为null。

(2)将内部类定义为静态内部类,使其不能与外部类建立关系。 

3.线程造成的内存泄漏

异步任务和Runnable都是一个匿名内部类,因此它们对当前Activity都有一个隐式引用。如果Activity在销毁之前,任务还未完成, 那么将导致Activity的内存资源无法回收,造成内存泄漏

new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... params) {
                SystemClock.sleep(10000);
                return null;
            }
        }.execute();


        new Thread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(10000);
            }
        }).start();


解决方案:

使用 静态内部类,避免了Activity的内存资源泄漏,当然在Activity销毁时候也应该取消相应的任务AsyncTask::cancel(),避免任务在后台执行浪费资源

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();



4.Handler导致内存泄漏

如下所示我们在Activity中定义了一个handler,然后在Activity的onCreate()方法中,发送了一条延迟消息。

当Activity finish的时候,延时消息还保存在主线程的消息队列里。而且,这条消息持有对handler的引用,而handler又持有对Activity引用。这条引用关系会保持到消息被处理,从而,这就阻止了Activity被垃圾回收器回收,造成内存泄漏。

private final Handler handler = new Handler() {  
    @Override  
    public void handleMessage(Message msg) {  
  
    }  
};  
  
@Override  
protected void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
    setContentView(R.layout.activity_main);  
  
    handler.postDelayed(new Runnable() {  
        @Override  
        public void run() { /* ... */ }  
    }, Integer.MAX_VALUE);   
}


解决方案:

如果一个内部类实例的生命周期比Activity更长,那么我们就不要使用非静态的内部类。最好的做法是使用静态内部类,然后在该类里使用弱引用来指向所在的Activity。

public class SampleActivity extends Activity {  
  
  private static class MyHandler extends Handler {  
    private final WeakReference<SampleActivity> mActivity;  
  
    public MyHandler(SampleActivity activity) {  
      mActivity = new WeakReference<SampleActivity>(activity);  
    }  
  
    @Override  
    public void handleMessage(Message msg) {  
      SampleActivity activity = mActivity.get();  
      if (activity != null) {  
        // ...  
      }  
    }  
  }  
  
  private final MyHandler mHandler = new MyHandler(this);  
  
  private static final Runnable sRunnable = new Runnable() {  
      @Override  
      public void run() { /* ... */ }  
  };  
  
  @Override  
  protected void onCreate(Bundle savedInstanceState) {  
    super.onCreate(savedInstanceState);  
  
    mHandler.postDelayed(sRunnable, 1000 * 60 * 10);  
    finish();  
  }  
}



5.资源未关闭造成的内存泄漏


对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏


解决方案:

在Activity销毁时及时关闭或者注销



6.webView的内存泄漏

webView的内存泄漏是一直以来的问题,目前主要分为2中方式进行解决。

方法一:

不要在布局文件layout.xml上直接声明,而是在代码上new 一个webView出来,因为webView本身有Bug,在布局文件声明还是会持有Activity的引用的。

webView = new WebView(getApplicationContext());
		ll_webview_parent = ((LinearLayout) findViewById(R.id.ll_webview_parent));
		ll_webview_parent.addView(webView);


@Override
	protected void onDestroy() {
		super.onDestroy();
		if (webView != null) {
			// 如果先调用destroy()方法,则会命中if (isDestroyed()) return;这一行代码,需要先onDetachedFromWindow(),再
			// destory()
			ViewParent parent = webView.getParent();
			if (parent != null) {
				((ViewGroup) parent).removeView(webView);
			}

			webView.stopLoading();
			// 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错
			webView.getSettings().setJavaScriptEnabled(false);
			webView.clearHistory();
			webView.clearView();
			webView.removeAllViews();

			try {
				webView.destroy();
			} catch (Throwable ex) {

			}
		}
	}

不过这种方法只适合webView的简单呈现,如果里面你需要弹窗、flash、播放视频等会导致上下文对象强转(cast)错误,因为这些操作都需要在父容器webView上进行的,它需要的是Activity的Context来绘制,而你给的是Application的Context,最终会导致程序崩溃。

方法二:

开启一个新的进程,与主进程分开。在AndroidMainfest.xml中设置android:process 名字自定义



<activity
            android:name=".service.MainActivity"
            android:process=":NewProcess.Main">
        </activity>

然后在关闭Activity的时候,关闭进程


@Override
	protected void onDestroy() {
		super.onDestroy();
		System.exit(0);
	}