上篇主要讲到如何使用工具去分析查找内存泄漏,那么现在主要讲一下常见的内存泄漏及其解决方法。
常见的泄漏
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());
如果我们在一个外部类中定义一个静态变量,这个静态变量是非静态内部类对象,这就会导致内存泄漏,因为非静态内部类会持有外部类的引用,从而 间接导致静态地引用了外部类 。
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();
如下所示我们在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);
}