android 内存泄漏(多篇文章摘录)

什么叫内存泄漏呢?

gc没有办法回收activity的内存

那什么情况下会GC呢?

  • 屏幕旋转的时候
  • 内存不够用的时候。

什么情况会发生内存泄漏呢?

静态资源问题

静态变量在整个应用的内存里只保存一份,一旦创建就不会释放该变量的内存,直到整个应用都销毁才会释放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才不会导致内存泄漏

内部类Handler类引起内存泄漏

在Activity中定义了一个内部Handler类:

public class MainActivity extends Activity {
    private  Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            //TODO handle message...
        }
    };
    @TargetApi(11)
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler.sendMessageDelayed(Message.obtain(), 60000);//延迟一分钟执行
        //just finish this activity
        finish();//关闭activity
    }
}

以上代码将会导致内存泄漏的问题(原因):

  • 在Java中,非静态(匿名)内部类会引用外部类对象。而静态内部类不会引用外部类对象

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

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

private Handler mHandler = new MyHandler(this);
private static class MyHandler extends Handler{
    private final WeakReference<Activity> mActivity;
    public MyHandler(Activity activity) {
        mActivity = new WeakReference<Activity>(activity);
    }
    @Override
    public void handleMessage(Message msg) {
        System.out.println(msg);
        if(mActivity.get() == null) {
            return;
        }
    }
}

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

以上摘自:内部Handler类引起内存泄露

另一种解决的方式:
activtyonDestroy方法对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);
}

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 的强引用

AsynTask 内部类

跟线程一样的原理。
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
}
}

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()进行中断,但不一定成功的。所以最好的使用静态内存类**或者使用弱引用

实战检测定位内存泄露

1. 使用MAT工具定位Android应用内存泄漏
# 使用Android studio分析内存泄露

资料:

Android Context 是什么?
# Android内存泄露之Thread
# Android内存泄露之Handler
Android App 内存泄露之资源
Android内存泄露之DDMS –> Heap工具,这个系列的文章。
Android学习系列(37)--App调试内存泄露之Context篇(下)
Android内存泄露的整理

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,681评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,710评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,623评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,202评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,232评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,368评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,795评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,461评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,647评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,476评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,525评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,226评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,785评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,857评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,090评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,647评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,215评论 2 341

推荐阅读更多精彩内容