Android 面试(五):探索 Android 的 Handler

这是 面试系列 的第五期。本期我们将来探讨一下 Android 异步消息处理线程 —— Handler。

往期内容传递:
Android 面试(一):说说 Android 的四种启动模式
Android 面试(二):如何理解 Activity 的生命周期
Android 面试(三):用广播 BroadcastReceiver 更新 UI 界面真的好吗?
Android 面试(四):Android Service 你真的能应答自如了吗?

开始

Android 的消息机制,也就是 Handler 机制,相信各位都已经是烂熟于心了吧。即创建一个 Message 对象,然后借助 Handler 发送出去,之后在 HandlerhandleMessage() 方法中获取刚才发送的 Message 对象,然后在这里进行 UI 操作就不会出现崩溃了。

既然 Handler 操作都烂熟于心,还讲这个干什么?

嗯,对,在 Android 开发中,我们确实经常用到它,对于基本代码流程自然也是倒背如流,但了解它的原理的人却不是很多,所以面试官通常会考验你对 Handler 源码机制的理解,毕竟只有知己知彼,才能百战不殆嘛。

我们都知道 UI 操作只能在主线程进行,通常是怎么在子线程更新 UI 的?

  • Handler
  • Activity.runOnUiThread()
  • View.post(Runnable r)

讲讲 Handler 机制吧

Handler 主要由以下部分组成。

  • Handler
    Handler 是一个消息辅助类,主要负责向消息池发送各种消息事件Handler.sendMessage() 和处理相应的消息事件Handler.handleMessage()

  • Message
    Message 即消息,它能容纳任意数据,相当于一个信息载体。

  • MessageQueue
    MessageQueue 如其名,消息队列。它按时序将消息插入队列,最小的时间戳将被优先处理。

  • Looper
    Looper 负责从消息队列读取消息,然后分发给对应的 Handler 进行处理。它是一个死循环,不断地调用 MessageQueue.next() 去读取消息,在没有消息分发的时候会变成阻塞状态,在有消息可用时继续轮询。

在 Android 开发中使用 Handler 有什么需要注意的

首先自然是在工作线程中创建自己的消息队列必须要调用 Looper.prepare(),并且在一个线程中只能调用一次。当然,仅仅创建了 Looper 还不行,还必须使用 Looper.loop() 开启消息循环,要不然要 Looper 也没用。

我们平时在开发中不用调用是因为默认会调用主线程的 Looper。
此外,一个线程中只能有一个 Looper 对象和一个 MessageQueue 对象。

大概的标准写法是这样。

Looper.prepare();
Handler mHandler = new Handler() {
   @Override
   public void handleMessage(Message msg) {
          Log.i(TAG, "在子线程中定义Handler,并接收到消息。。。");
   }
};
Looper.loop();

另外一个比较常考察的就是 Handler 可能引起的内存泄漏了。

Handler 可能引起的内存泄漏

我们经常会写这样的代码。

 private final Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

当你这样写的时候,你一定会收到编译器的黄色警告。

In Android, Handler classes should be static or leaks might occur, Messages enqueued on the application thread’s MessageQueue also retain their target Handler. If the Handler is an inner class, its outer class will be retained as well. To avoid leaking the outer class, declare the Handler as a static nested class with a WeakReference to its outer class

在 Java 中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用,而静态的内部类不会持有外部类的引用。

要解决这样的问题,我们在继承 Handler 的时候,要么是放在单独的类文件中,要么直接使用静态内部类。当需要在静态内部类中调用外部的 Activity 的时候,我们可以直接采用弱引用进行处理,所以我们大概修改后的代码如下。

 private static final class MyHandler extends Handler{
        private final WeakReference<MainActivity> mWeakReference;
        
        public MyHandler(MainActivity activity){
            mWeakReference = new WeakReference<>(activity);
        }
        
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            MainActivity activity = mWeakReference.get();
            if (activity != null){
                // 开始写业务代码
            }
        }
    }
    
private MyHandler mMyHandler = new MyHandler(this);

其实在我们实际开发中,不止一个地方可能会用到内部类,我们都需要在这样的情况下尽量使用静态内部类加弱引用的方式解决我们可能出现的内存泄漏问题。

用过 HandlerThread 吗?它是干嘛的?

HandlerThread 是 Android API 提供的一个便捷的类,使用它可以让我们快速地创建一个带有 Looper 的线程,有了 Looper 这个线程,我们又可以生成 Handler,本质上也就是一个建立了内部 Looper 的普通 Thread

我们在上面提到的子线程建立 Handler 大概代码是这样。

new Thread(new Runnable() {
    @Override public void run() {
        // 准备一个 Looper,Looper 创建时对应的 MessageQueue 也会被创建
        Looper.prepare();
        // 创建 Handler 并 post 一个 Message 到 MessageQueue
        new Handler().post(new Runnable() {
            @Override 
            public void run() {
                  MLog.i("Handler in " + Thread.currentThread().getName());
            }
        });
        // Looper 开始不断的从 MessageQueue 取出消息并再次交给 Handler 执行
        // 此时 Lopper 进入到一个无限循环中,后面的代码都不会被执行
        Looper.loop();
    }
}).start();

而采用 HandlerThread 可以直接把步骤简化为这样:

// 1. 创建 HandlerThread 并准备 Looper
handlerThread = new HandlerThread("myHandlerThread");
handlerThread.start();

// 2. 创建 Handler 并绑定 handlerThread 的 Looper
new Handler(handlerThread.getLooper()).post(new Runnable() {
    @Override 
    public void run() {
          // 注意:Handler 绑定了子线程的 Looper,这个方法也会运行在子线程,不可以更新 UI
          MLog.i("Handler in " + Thread.currentThread().getName());
    }
});

// 3. 退出
@Override public void onDestroy() {
    super.onDestroy();
    handlerThread.quit();
}

其中必须注意的是,workerThread.start() 是必须要执行的。

至于如何使用 HandlerThread 来执行任务,主要是调用 Handler 的 API。

  • 使用 post 方法提交任务,postAtFrontOfQueue() 将任务加入到队列前端, postAtTime() 指定时间提交任务, postDelayed() 延后提交任务。
  • 使用 sendMessage() 方法可以发送消息,sendMessageAtFrontOfQueue() 将该消息放入消息队列前端,sendMessageAtTime() 指定时间发送消息, sendMessageDelayed() 延后提交消息。

HandlerThread 的 quit() 和 quitSafety() 有啥区别?

两个方法作用都是结束 Looper 的运行。它们的区别是,quit() 方法会直接移除 MessageQueue 中的所有消息,然后终止 MesseageQueue,而 quitSafety() 会将 MessageQueue 中已有的消息处理完成后(不再接收新消息)再终止 MessageQueue

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

推荐阅读更多精彩内容

  • 久违的晴天,家长会。 家长大会开好到教室时,离放学已经没多少时间了。班主任说已经安排了三个家长分享经验。 放学铃声...
    飘雪儿5阅读 7,395评论 16 21
  • 今天感恩节哎,感谢一直在我身边的亲朋好友。感恩相遇!感恩不离不弃。 中午开了第一次的党会,身份的转变要...
    迷月闪星情阅读 10,498评论 0 11
  • 可爱进取,孤独成精。努力飞翔,天堂翱翔。战争美好,孤独进取。胆大飞翔,成就辉煌。努力进取,遥望,和谐家园。可爱游走...
    赵原野阅读 2,681评论 1 1
  • 在妖界我有个名头叫胡百晓,无论是何事,只要找到胡百晓即可有解决的办法。因为是只狐狸大家以讹传讹叫我“倾城百晓”,...
    猫九0110阅读 3,224评论 7 3