纠正编码错误篇 (2)Handler的Warning你解决了吗

前言#

首先必须要声明中文中间插入英文不是我的爱好,这一点别吐槽我,因为有些单词我想不到特别准备的翻译,就跟人家说读英文书和读翻译书完全是两个感觉,重点是意会,你懂得。

上次聊了聊inflater,这次来聊聊Handler,他俩绝对是我们最常用的左青龙,右白虎,屌爆了。

正文#

<h2>Handler 的作用</h2>

再熟悉的东西都要简单介绍一下,都是一种礼貌和尊敬,Handler主要是为我们提供子线程和UI线程之间进行操作上的切换,还提供了延时任务、定时功能等非常强大的功能,他还有两个好基友Looper 和MessageQueue,帮助他去实现上面的功能,这些知识大家都懂,面试必考内容就不多说了,感兴趣的朋友可以自己去查看一下相关资料。

<h2>This Handler class should be static or leaks might occur (anonymous android.os.Handler) less... </h2>

这个熟悉的警告你还记得吗,说不得的人肯定是没走心,尤其是写过下面这段代码的人:

public class MainActivity extends AppCompatActivity {

    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            Log.e("lzp", "我是一个屌爆了的handler" );
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

相信很多人还保持着这种写法,对于右侧提出的warning视而不见,觉得无伤大雅,反正是警告,编译运行都很完美,例如我之前就是这样的,那么我希望你能够自我批评一下。

这段警告大概就是这个意思:

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.

由于Handlder被声明为内部类,他可能阻止外部类被垃圾回收。如果Handler是采用新的Looper 或 MessageQueue,而不是主线程,那么就没有问题。如果Handlder是采用主线程的Looper 或 MessageQueue,你需要修正这个Handler的声明如下:声明Handler作为一个静态类;在外部类中,实例化Hander时,在他的内部也实例化一个WeakReference(弱引用),并传入他自己;确保这个外部类中的所有成员都使用WeakReference(弱引用)中的这个对象。

这段翻译是我自己翻译的,感慨我的英文水平屌爆了的同时,也直接明白了这段警告的意思:

要么给Handler创建一个新的Handler,要不就使用静态类,并且弱引用,防止对垃圾回收进行影响。

那直接创建一个新的Looper 不就完事了吗?很遗憾,Looper的构造方法是私有的(),我们无法去手动创建一个新的Looper:

这里写图片描述

看的出来Looper创建的同时,也同时创建了MessageQueue,而且绑定了相应的线程。

为什么不把构造方法公开呢?我个人觉得可能是设计者不希望开发者滥用Looper。因为Looper涉及到跨线程的问题,大家都懂的跨线程需要考虑很到安全操作,就显得比较棘手,所以开放了一个静态方法:Looper.prepare() 和 Looper.loop()配合使用,并且Handler已经满足了大部分应用场景,估计是想让开发者尽量用Handler吧,我就是小小的意淫一下设计者的想法。

在伤心和痛苦中突然发现Looper.myLooper()方法,好像抓住了一个救命稻草,于是我充满期待的做了下面的测试:

public class MainActivity extends AppCompatActivity {

    private Handler handler = new Handler(Looper.myLooper()){
        @Override
        public void handleMessage(Message msg) {
            Log.e("lzp", "我是一个屌爆了的handler" + getLooper().hashCode() );
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        handler.sendEmptyMessage(0);
        Log.e("lzp", "UI handler:" + Looper.myLooper().hashCode() );
    }
}
这里写图片描述

好尴尬,和UI线程是一样的,仔细一想很容易理解,本身我传进去的就是UI线程的Looper,得到肯定还是UI线程的Looper。

Looper.myLooper()返回的是当前线程的Looper,如果你是在线程中创建了looper,返回的就是这个线程的Looper,例如我运行了下面的代码:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.e("lzp", "Main Looper:" + Looper.myLooper().hashCode());
        new Thread(){
            @Override
            public void run() {
                Looper.prepare();
                Toast.makeText(MainActivity.this, "屌爆了哦~", Toast.LENGTH_SHORT).show();
                Log.e("lzp", "Thread Looper:" + Looper.myLooper().hashCode());
                Looper.loop();

            }
        }.start();
    }

打印的Log:

这里写图片描述

果然hashcode不一样,说明已经是不同的Looper了。

顺便贴一下Looper.prepare()的源码:

/** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
public static void prepare() {
    prepare(true);
}
    
...
    
private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

Looper.prepare()先从ThreadLocal(有点类似于线程池,里面的线程可以复用)中找到对应的Looper,没有就创建一个新的Looper并放入ThreadLocal,对ThreadLocal不了解的朋友可以自己去百度。

那就没办法了,只能去研究第二种方法了:静态类,弱引用。

废话没有,直接看代码:

public class MainActivity extends AppCompatActivity {

    private TestHandler handler = new TestHandler(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {

            }
        }, 2000);
    }

    private void handleMessage(Message msg){
        Toast.makeText(this, "hahaha", Toast.LENGTH_LONG).show();
    }

    static class TestHandler extends Handler{

        private WeakReference<Activity> mActivity;

        TestHandler(Activity activity){
            mActivity = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = (MainActivity) mActivity.get();
            if (activity != null) {
                activity.handleMessage(msg);
            }
        }
    }
}

刚才扯了那么多高大上的蛋,一看代码大概就明白差不多的意思了:

静态类不会和外部类直接引用(就是强引用),并且TestHandler和activity 的关系是弱引用,所以就不会对外部类造成垃圾回收上的影响。

上面这种写法是比较流行的写法,因为静态类无法直接声明,所以只能用静态内部类。我是把TestHandler写到BaseActivity中,并且在BaseActivity中设置一个方法handlerMessage(),给TestHandler调用,我会把自己的用法放在最后供大家下载参考。

<h2>Handler使用不当,会出现什么问题?</h2>

说了这么多,写了这么多,到底Handler使用不当会出现啥问题啊?

其实主要是内存上的问题,如果仅仅是更新了UI界面,那其实是无所谓的,但是谁也不会为了更新UI还特地去使用Handler,直接UI线程就好了。

问题就是出线程上,大部分都是配合Thread使用,下面举几个例子:

1、网络请求或者是耗时任务,成功或者失败更新UI,但是还没完成,Activity被销毁了;

2、有一个延时/定时任务,还没有触发,Activity销毁了,并且没有removeCallback。

这两个情况非常常见,Handler保持着对Activity的强引用,在任务完成之前,是无法被回收的,这就可能出现内存溢出等情况,并且Activity被回收再去更新UI很明显是没有意义的。

总结#

又到了总结的时间了,Handler最推荐的使用方法就结束了,可能过多的新技术新花样吸引了我们太多的目光,而忽略了不断的扎实自己的基础,所以在不停学习新招式的时候,别忘了练习和加强我们的基本功。

到此结束,有问题或者有讲解有误的地方欢迎批评指正,拜拜~

Demo点击下载

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

推荐阅读更多精彩内容