Handler的使用、内存泄漏和解决

Handler的使用:

 Handler是Android线程间通讯的一种方式,它常被我们用来更新UI,是的,我是这么用,还有延时,只有拿出来总结的时候,才会发现有时候使用的时候是有缺漏的。所以总结很重要啊!
 目前为止总结的一些使用情况如下:
 1.子线程发送消息到主线程
 2.在子线程中更新UI
 3.在子线程中使用Handler
 4.使用HandlerThread
 5.Handler的callback回调

  1. 子线程发送消息到主线程
Handler mainHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            Toast.makeText(HandlerActivity.this, "接收到啦", Toast.LENGTH_SHORT).show();

        }
    };
new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                /*子线程传给主线程*/
               mainHandler.sendEmptyMessage(0);
             }
        }).start();

 这里是在子线程中Handler对象发送一个空消息,然后在handleMessage方法中进行操作,此时Toast执行已经是在UI线程了。
 然后刚刚测试了一下,不仅仅是子线程往主线程发消息,主线程也可以向子线程发消息,子线程也可以向子线程发消息,自己手动去试一下才会理解Handler这个线程间通信是怎么回事。


  1. 在子线程中更新UI
 Handler updateHandler = new Handler();
    new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                /*在子线程中更新UI*/
                updateHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        /*
                         *更新UI的操作
                         * */
                    }
                });
            }
        }).start();

 这里的代码都是一些局部的代码块,这里的updateHandler是在主线程声明的,子线程是开在主线程下的, 然后updateHandler对象在子线程使用post方法,new了一个Runnable去切换线程到主线程执行更新UI的代码。当然,也可以像上面那样发送一个消息在Handler的handleMessage里更新UI喔~!


  1. 在子线程中使用Handler

匿名内部类实现

new Thread(new Runnable() {//创建一个子线程
            @Override
            public void run() {

                Looper.prepare();//创建与当前线程相关的Looper
                myThreadTwoHandler = new Handler() {    //创建一个子线程里的Handler
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        //log日志测试结果,是在子线程的Handler
                        Log.e(TAG, "当前存在线程为" + Thread.currentThread().getName());
                    }
                };
                Looper.loop();//调用此方法,消息才会循环处理
            }
        }).start();

        myThreadTwoHandler.sendEmptyMessageDelayed(0, 5000);//主线程调用子线程的Handler对象发送消息

子类继承Thread实现

//MyThread 子类继承 Thread
 public class MyThread extends Thread {
        public Looper childLooper;

        @Override
        public void run() {
            Looper.prepare();//创建与当前线程相关的Looper
            childLooper = Looper.myLooper();//获取当前线程的Looper对象
            Looper.loop();//调用此方法,消息才会循环处理
        }
    }
  /*在子线程使用Handler*/
        MyThread myThread = new MyThread();
        myThread.start();
        myThreadHandler = new Handler(myThread.childLooper) {//与MyThread线程绑定
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Log.e(TAG, "当前存在线程为" + Thread.currentThread().getName());
            }
        };
        //主线程调用子线程的Handler对象发送消息
        myThreadHandler.sendEmptyMessageDelayed(0, 5000);

HandlerThread实现

        HandlerThread handlerThread = new HandlerThread("ceshi");
        handlerThread.start();
        //通过HandlerThread的getLooper方法可以获取Looper
        Looper looper = handlerThread.getLooper();
        //通过Looper我们就可以创建子线程的handler了
        Handler handler = new Handler(looper) {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                //测试结果是在ceshi这个线程内
                Log.e(TAG, "这个是HandlerThread线程哦 : " + Thread.currentThread().getName());
            }
        };
        handler.sendEmptyMessageDelayed(0, 1000);
  1. 使用HandlerThread

 HandlerThread上面已经展示过代码了,为了和其他两种对比,我就把HandlerThread实现Handler的使用写在上面的部分了。HandlerThread其实就是系统封装好的Thread子类,和自己封装的子类不同的是,HandlerThread里面做了更好的判断来避免一些问题。
 例如:Only one Looper may be created per thread。这个问题我在自己写MyThread这个子类并使用的时候,子类对象调用run就报了这个错误,调用start可以运行了。目前为止,我觉得HandlerThread这个子类相对自己写来说,会比较好用,在实际的项目操作中,如何需要,应该还是看具体需求了。目前是这样,以后有了新的理解进行更新。

  1. Handler的Callback回调

 其实这个Handler还有一个Callback的回调这个东西,我一开始并不知道的,当时应该是从大佬同事那里了解到这个东西的时候,感觉很神奇,又感觉很懵逼,为什么会有布尔类型的返回值?然后在我的实验中发现,咦?true和false的结果是一样的?脑子里更懵逼了,这是什么???黑人问号脸...

 Handler callBackHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            Toast.makeText(HandlerActivity.this, "这里是Callback", Toast.LENGTH_SHORT).show();
            /*
             * callback的返回值
             * */
            return true;
        }
    });

 对,就是这样,我的Toast不论true还是false,完全没有影响,照弹不误!为了内心世界的和平,肯定不能放弃治疗!源码什么的,对不起,一开始我肯定选择了百度。百度果然是没有辜负我的期望,什么有用的都没有找到,找到的都是一模一样的无数个copy的没有用的文字~~~
 然后,在我努力追求真理的情况下,大佬告诉我其实是这样的。。。

 Handler callBackHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            Toast.makeText(HandlerActivity.this, "这里是Callback", Toast.LENGTH_SHORT).show();
            /*
             * callback的返回值
             * */
            return true;
        }
    }) {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Log.e(TAG, "handleMessage: 返回true不处理");
        }
    };

 对,没有看错!handleMessage有两个,一个属于Callback,一个属于Handler。
 然后,让我们和源码结合一下~~~

//这里是Handler的源码
 public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

 然后其实一看就恍然大悟了,返回值为true的时候直接return掉了,不会去执行Handler的handleMessage这个方法了,如果为false的话就会执行啦,当然,好像默认情况都是执行false。
 这里也是暂时理解是这样的,对于它的应用场景,具体需求,以后遇到了再更新!
 哦对了,还有AndroidStudio中,我们平常使用的Handler其实都会可能导致内存泄露的,AS会标黄色,表示警告。然后在使用这个Handler.Callback的时候,我一开始发现警告消除掉了。其实并不是,在只使用Callback的handleMessage方法时,是没有警告的,但是加上Handler的handleMessage后,警告就有了,所以,网上有的人说的Handler.Callback会解决内存泄露,是错误的,没有警告了只是写法有问题罢了,哈哈哈哈哈!


Handler的内存泄露

 Handler的内存泄漏,一开始让我注意到这个问题的时候,是我的AS报警告。更新以后的AS,对于Handler的这个警告,不要太凶喔,一报就是一大片屎黄色,咦~~~受不了
 比如说这样:


image.png

 然后就开始探究了啊,为什么会导致内存泄露呢?哈哈哈,上面图片里其实有说啦,这种Handler的使用其实就是将Handler声明为Activity的内部。而在Java语言中,非静态内部类会持有外部类的一个隐式引用,所以,Handler会持有Activity的引用啦,然后就会有可能造成外部类,也就是Activity无法被回收,导致内存泄露~~~
 然后,经过询问等等等方法探究了他人的经验之后,发现其实很多开发一直使用的是可能会导致内存泄露的版本的Handler,因为正面看上去并不会影响App的运行,不像图像过大会导致OOM这种很致命的问题就会让很多忽视,或者说没有注意到这个问题,比如了解这个问题之前的那个我~
 那么,如何避免内存泄漏,使用正确的Handler呢?先看一下AS给我们的建议

This Handler class should be static or leaks might occur (anonymous android.os.Handler) less (Ctrl+F1) 
Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. 
If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. 
If the Handler is using the Looper or MessageQueue of the main thread, 
you need to fix your Handler declaration, as follows: Declare the Handler as a static class; 
In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler;
 Make all references to members of the outer class using the WeakReference object.

 哇,全是英文呐,扔谷歌翻译进化一下~

这个处理程序类应该是静态的或可能发生泄漏(匿名android.os.Handler)少...(按Ctrl+ F1)
由于此Handler被声明为内部类,因此可能会阻止外部类被垃圾收集。
如果处理程序对主线程以外的线程使用Looper或MessageQueue,则没有问题。 
如果Handler使用主线程的Looper或MessageQueue,则需要修复Handler声明,
如下所示:将Handler声明为静态类; 在外部类中,实例化WeakReference到外部类并在实例化Handler时将此对象传递给Handler; 
使用WeakReference对象创建对外部类成员的所有引用。

 然后我们就开始搞我们的正确姿势啦~


正确使用Handler,避免内存泄露
  • 使用静态的匿名内部类,并持有外部类的弱引用

 声明静态的Handler内部类,持有外部类的弱引用,通过外部类实例去引用外部类的各种控件实例,参数实例等等。然后当GC回收时,因为外部类是弱引用,所以会被回收。

/**
     * 声明一个静态的Handler内部类,并持有外部类的弱引用
     */
    private static class MyHandler extends Handler {

        private final WeakReference<HandlerActivity> mActivty;

        private MyHandler(HandlerActivity mActivty) {
            this.mActivty = new WeakReference<>(mActivty);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            HandlerActivity activity = mActivty.get();
            if (activity != null) {
                Log.e("eee", "handleMessage: " + Thread.currentThread().getName());
            }
        }
    }

 在外部类中声明MyHandler对象

 private final MyHandler mHandler = new MyHandler(this);

 然后调用发送消息,post的方式和sendMessage的方式

mHandler.post(sRunnable);
mHandler.sendMessage(message);

 如果使用sendMessage方法的话,会被MyHandler的 handleMessage方法接收。那么,若使用post方法的话,我们还需要声明一个静态的Runable来完成我们的post

 /**
     * 静态的匿名内部类不会持有外部类的引用
     */
    private static final Runnable sRunnable = new Runnable() {
        @Override
        public void run() {
            // ...你的操作
            Log.e(TAG, "这里是run");
        }
    };

结束语

 还有很多没有讲到的,讲到的也都感觉没有说的很彻底,先写到这里,然后多钻研再修改更新~~~

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

推荐阅读更多精彩内容