android常见的内存泄露及解决方案

时间:2021-09-11 20:56:31

1.什么是内存泄露

Java内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,但是它们却可以直接或间接地引用到gc roots导致无法被GC回收。

2.Handler的内部类造成的内存泄露由什么原因造成?

Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
mImageView.setImageBitmap(mBitmap);
}
}
A.使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用(不然你怎么可能通过Handler来操作Activity中的View?)。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。
B.如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。

简而言之:

  • 在Java中,非静态(匿名)内部类会引用外部类对象。而静态内部类不会引用外部类对象
  • 当Activity Finish后,延时消息会继续存在主线程消息队列中1分钟,然后处理消息。而该消息引用了Activity的Handler对象,然后这个Handler又引用了这个Activity(从以上原因可知道)。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致了上面说的 Activity泄露。

解决方案:

方法一:通过程序逻辑来进行保护。

1.在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。

2.如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。

在activity的onDestroy方法对Handler的取消:

public void onDestroy() {
mHandler.removeMessages(MESSAGE_1);
mHandler.removeMessages(MESSAGE_2);
mHandler.removeMessages(MESSAGE_3);
mHandler.removeMessages(MESSAGE_4);
// ... ...
mHandler.removeCallbacks(mRunnable);
// ... ...
}
或者:
public void onDestroy() {
mHandler.removeCallbacksAndMessages(null);
}


方法二:Handler改为静态类,并使用WeakReference来保持外部的activity对象。

静态类不持有外部类对象的引用,所以你的Activity可以随意被回收。代码如下:

static class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
mImageView.setImageBitmap(mBitmap);
}
}

由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference):

static class MyHandler extends Handler {
WeakReference<Activity > mActivityReference;
MyHandler(Activity activity) {
mActivityReference= new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
final Activity activity = mActivityReference.get();
if (activity != null) {
mImageView.setImageBitmap(mBitmap);
}
}
}

结论:当你在Activity中使用内部类的时候,需要时刻考虑您是否可以控制该内部类的生命周期,如果不可以,则最好定义为静态内部类

3.Thread对象

同上:Thread的生命周期不一定是和Activity生命周期一致。
同上的解决方式为:

  • 改为静态内部类
  • 采用弱引用来保存Context引用

正确的使用为:

public class ThreadAvoidActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MyThread(this).start();
}
private void dosomthing() {//dosomthing是ThreadAvoidActivity的方法
}
private static class MyThread extends Thread {
WeakReference<ThreadAvoidActivity> mThreadActivityRef;//弱引用
public MyThread(ThreadAvoidActivity activity) {
mThreadActivityRef = new WeakReference<ThreadAvoidActivity>(
activity);
}
@Override
public void run() {//先判断是否为null,回收掉
super.run();
if (mThreadActivityRef == null)
return;
if (mThreadActivityRef.get() != null)
mThreadActivityRef.get().dosomthing();//通过弱引用得到外部的activity来调用dosomthing方法
// dosomthing
}
}
}

以上切断两个对象的双向强引用链接

  1. 静态内部类:切断Activity 对于 MyThread的强引用。
  2. 弱引用: 切断MyThread对于Activity 的强引用
4.静态资源问题

静态变量在整个应用的内存里只保存一份,一旦创建就不会释放该变量的内存,直到整个应用都销毁才会释放static静态变量的内存.

例子:

public class MyCustomResource {
//静态变量drawable
private static Drawable drawable;
private View view;

public MyCustomResource(Context context) {
Resources resources = context.getResources();
drawable = resources.getDrawable(R.drawable.ic_launcher);
view = new View(context);
view.setBackgroundDrawable(drawable);
}
}
//查看setBackgroundDrawable的源代码:
public void setBackgroundDrawable(Drawable background) {
..........
/**此处的this就是当前View对象,而View对象又是有Context对象获得 因此,变量background持有View对象的引用,View持有Context的引用, 所有background间接持有Context对象的引用了*/
background.setCallback(this);
.......
}

分析:
此处的background.setCallback(this);this是当前的view的对象。由于background是一个静态变量,会一直持有View对象的引用,而然View对象又是由Context对象创建出来的,因此background会间接持有Context的对象的引用
所以:
该Context对应的Activity退出finish掉的时候其实该Activity是不能完全释放内存的
值得注意的是:
代码是由于静态资源drawable持有View对象的引用导致内存泄漏隐患的,并不是由于context.getResource
导致内存泄漏,因此如果你想通过Context.getApplicaitonContext来获取getResource是解决不了内存泄漏的.
另外提一点:(使用getApplicaitonContext的错误)
android.app.Application cannot be cast to android.app.Activity

因此:在android 3.0 中:
修改了setBackgroundDrawable内部方法中的 background.setCallback(this);方法。里面的实现使用了弱引用来持有View对象的引用,从而避免了内存泄漏隐患

总结:以后代码中避免使用静态资源,或者使用弱引用来解决相应的问题也是可以的。

单例模式导致内存泄漏(单例中的类是静态的,引用context将导致以上的问题)

public class CustomManager {
private static CustomManager sInstance;
public static CustomManager getInstance(Context context) {
if (sInstance == null) {
sInstance = new CustomManager(context);//使用context的引用。
}
return sInstance;
}

private Context mContext;
private CustomManager(Context context) {
mContext = context;
}
}
单例模式使用的是静态类的方式,让该对象在整个应用的内存中保持一份该对象,从而减少对多次创建对象带来的资源浪费

同样的问题:
在创建该单例的时候使用了生命周期端的Context对象的引用,如果你是在Application中创建以上单例的话是木有任何问题的。因为Application的Context生命周期是整个应用,和单例的生命周期一样,因此不会导致内存泄漏。但是,如果你是在Activity中创建以上单例的话,将一样导致跟上一个一样的内存问题。
所以讲以上的代码改成:

if (sInstance == null) { 
sInstance = new CustomManager(context.getApplicationContext()); }//注意这里的不同。
return sInstance;

以上全摘自:Android Context 是什么?

通过上面大家可能对Context比较模糊。请阅读:Android Context 是什么?
记录几点:

  • getApplication和getApplicationContext返回同一个Application对象,只是里面方法调用的成员返回不同。
  • 所有Context都是在应用的主线程ActivityThread中创建的
  • 尽量少用Context对象去获取静态变量,静态方法,以及单例对象。以免导致内存泄漏
  • 在创建与UI相关的地方,比如创建一个Dialog,或者在代码中创建一个TextView,都用Activity的Context去创建。然而在引用静态资源,创建静态方法,单例模式等情况下,使用生命周期更长的Application的Context才不会导致内存泄露AsynTask 内部类线程一样的原理。

5.AsyncTask

内部的实现机制是运用了ThreadPoolExcutor, 该类产生的Thread对象的生命周期是不确定的,是应用程序无法控制的。
你可能会说:AsynTask不是有cancel的方法么?对的,是有这个方法,但还是不一定能取消。通过源码可以知道

public final boolean cancel(boolean mayInterruptIfRunning) {
mCancelled.set(true);
return mFuture.cancel(mayInterruptIfRunning);
}

问题归因于mFuture.cancel(mayInterruptIfRunning);是否能取消正在执行的任务呢?

通过线程执行者(九)执行者取消一个任务,文章不是重点,重点是评论,提出了是否能真正取消任务的质疑
查阅:Android学习系列(37)--App调试内存泄露之Context篇(下),这里引用官方的言语: cancel是不一定成功的。

总结:只要是使用线程正在进行执行任务,都不一定能够取消。大多数是使用:Thread.interrupt()进行中断,但不一定成功的。所以最好的使用静态内存类或者使用弱引用

官方的例子:

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
// Escape early if cancel() is called
// 注意下面这行,如果检测到cancel,则及时退出
if (isCancelled()) break;
}
return totalSize;
}

protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}

protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}

在后台循环中时刻监听cancel状态,防止没有及时退出。

所以:使用cancel并不靠谱,那么如何书写呢?跟Thread的思路是一样,差不多:

/**
*
* 弱引用
* @version 1.0.0
* @author Abay Zhuang <br/>
* Create at 2014-7-17
*/
public abstract class WeakAsyncTask<Params, Progress, Result, WeakTarget>
extends AsyncTask<Params, Progress, Result> {
protected WeakReference<WeakTarget> mTarget;
public WeakAsyncTask(WeakTarget target) {
mTarget = new WeakReference<WeakTarget>(target);
}
@Override
protected final void onPreExecute() {
final WeakTarget target = mTarget.get();
if (target != null) {
this.onPreExecute(target);
}
}
@Override
protected final Result doInBackground(Params... params) {
final WeakTarget target = mTarget.get();
if (target != null) {
return this.doInBackground(target, params);
} else {
return null;
}
}
@Override
protected final void onPostExecute(Result result) {
final WeakTarget target = mTarget.get();
if (target != null) {
this.onPostExecute(target, result);
}
}
protected void onPreExecute(WeakTarget target) {
// Nodefaultaction
}
protected abstract Result doInBackground(WeakTarget target,
Params... params);
protected void onPostExecute(WeakTarget target, Result result) {
// Nodefaultaction
}
}

6.BroadcastReceiver对象

原因:没有取消注册。
直接:getContext().unregisterReceiver(receiver);即可.
注册与反注册:

addCallback             <==>     removeCallback
registerReceiver <==> unregisterReceiver
addObserver <==> deleteObserver
registerContentObserver <==> unregisterContentObserver

TimerTask对象

TimerTask对象在和Timer的schedule()方法配合使用的时候极容易造成内存泄露。
要在合适的时候进行Cancel即可。

private void cancelTimer(){ 
if (mTimer != null) {
mTimer.cancel();
mTimer = null;
}
if (mTimerTask != null) {
mTimerTask.cancel();
mTimerTask = null;
}
}

剩下几个小点:
  • Dialog对象:使用isFinishing()判断Activity是否退出。才可以showDialog
  • Bitmap没调用recycle()
  • 资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们
  • 构造Adapter时,没有使用缓存的 convertView
  • 集合容器对象没清理造成的内存泄露(尤其是static的集合,需要clear清理)
  • WebView对象没有销毁。它的destory()函数来销毁它

总结:

泄漏都是有生命周期和Activity不一定一致,导致无法进行回收。

只要是使用线程正在进行执行任务,都不一定能够取消。大多数是使用:Thread.interrupt()进行中断,但不一定成功的。所以最好的使用静态内存类**或者使用弱引用


参考:http://www.jianshu.com/p/a53197d588bf