Android内存泄露之Handler

对于一个初级程序员来说,内存泄露这种东西压根想都没想过,以前也总听一些大牛再说,可能是现在手机的内存都比较大,所以还没什么深刻的体会,但是作为一个负责的程序员,现在还是有必要了解和预防一下了。

本节说的是Handler引发的内存泄漏,这种场景也是很多的,比如需要去网上获取一张图片,然后显示在ImageView上面,然后还没拿到的时候就退出了Activity了,这个时候系统就不会去回收Activity了,直接导致内存泄漏。

public class TestActivity extends Activity{
    ImageView mImageView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        mImageView=(ImageView) findViewById(R.id.end);
        //模拟网络请求60s
        handler.sendEmptyMessageDelayed(0, 60000);
        //立即关闭Activity(模拟退出,此刻网络请求还在跑)
        finish();
    }
    Handler handler=new Handler(){
        public void handleMessage(android.os.Message msg) {
            mImageView.setImageResource(R.drawable.ic_launcher);
            Toast.makeText(TestActivity.this, "dddd", 0).show();
        };
    };
}

IDE会报一个黄色的错误。

image.png

This Handler class should be static or leaks might occur

因为:
在Java中,非静态(匿名)内部类会默认隐性引用外部类对象。而静态内部类不会引用外部类对象。
如果外部类是Activity,则会引起Activity泄露 。

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

Handler 的生命周期与Activity 不一致
当Android应用启动的时候,会先创建一个UI主线程的Looper对象,Looper实现了一个简单的消息队列,一个一个的处理里面的Message对象。主线程Looper对象在整个应用生命周期中存在。
当在主线程中初始化Handler时,该Handler和Looper的消息队列关联(没有关联会报错的)。发送到消息队列的Message会引用发送该消息的Handler对象,这样系统可以调用 Handler#handleMessage(Message) 来分发处理该消息。

上面代码可以发现,ImageView持有一个Activity的引用,Toast持有一个,而Handler跟Activity关联又持有一个引用,因此这样很容易就内存泄漏了,

内存泄露的危害

只有一个,那就是虚拟机占用内存过高,导致OOM(内存溢出),程序出错。对于Android应用来说,就是你的用户打开一个Activity,使用完之后关闭它,内存泄露;又打开,又关闭,又泄露;几次之后,程序占用内存超过系统限制,如果这个Activity干的活比较多的话(也就是操作的东西比较多),那么不经意间就jj了。

既然让我们声明为静态的,那我们就改改代码:

static Handler handler=new Handler(){
        public void handleMessage(android.os.Message msg) {
            mImageView.setImageResource(R.drawable.ic_launcher);
            Toast.makeText(TestActivity.this, "dddd", 0).show();
        };
    };

顿时有尴尬了:

在静态类中不能引用非静态的变量,

但其实没这么简单。使用了以上代码之后,你会发现,由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference):

什么是WeakReference?
WeakReference弱引用,与强引用(即我们常说的引用)相对,它的特点是,GC在回收时会忽略掉弱引用,即就算有弱引用指向某对象,但只要该对象没有被强引用指向(实际上多数时候还要求没有软引用,但此处软引用的概念可以忽略),该对象就会在被GC检查到时回收掉。对于上面的代码,用户在关闭Activity之后,就算后台线程还没结束,但由于仅有一条来自Handler的弱引用指向Activity,所以GC仍然会在检查的时候把Activity回收掉。这样,内存泄露的问题就不会出现了。

好了,我们再改改代码:

 public class TestActivity extends Activity{
    ImageView mImageView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        mImageView=(ImageView) findViewById(R.id.end);
        handler.sendEmptyMessageDelayed(0, 60000);
        finish();
    }
    final MyHandler handler=new MyHandler(this);
    static class MyHandler extends Handler{
        private final WeakReference<TestActivity> mActivity;  
        public MyHandler(TestActivity activity) {
            mActivity=new WeakReference<TestActivity>(activity);
        }
        public void handleMessage(android.os.Message msg) {
            TestActivity activity=mActivity.get();
            if(activity!=null){
                activity.mImageView.setImageResource(R.drawable.ic_launcher);
                Toast.makeText(activity, "dddd", 0).show();
            }
        };
    };
}

这样总算完美了?????

当Activity finish后 handler对象还是在Message中排队。 还是会处理消息,还是会触发回调。

那到底怎样才算完美呢?
看看Handler的api中我们发现有这么几个方法:


image.png

所以我们在onDestory方法的时候,移除所有的Handler回调跟Message。

@Override
    protected void onDestroy() {
        if(handler!=null){
            handler.removeCallbacksAndMessages(null);
        }
        super.onDestroy();
    }

到此才勉强算是完美了,所以如果想要当一个好的程序员要考虑的东西还不是那么简单的,就一个看似简单的Handler

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

推荐阅读更多精彩内容