Android的Handler消息机制详解

我们知道,Android里面的线程做了这样的分工:主线程不做耗时操作,子线程不更新UI。
这是因为Android系统是个单线程模型,系统给App分配的进程里,只有一个主线程,主线程是App进程的核心,所有的组件都在主线程里实现、调度和管理,组件的绘制、互动操作和生命周期回调都是主线程处理的,这个线程如果出现阻塞、或多线程死锁、或CPU饥饿,导致事件处理时间过长,甚至没有机会得到处理,系统就会处理为ANR,强制关闭App。
所以主线程不能做耗时操作,耗时操作需要我们在子线程里处理。同时,Android不允许子线程刷新UI,因为Android无法保证多个线程同步操作,这样子线程和主线程之间就需要做协调,比如子线程从网络服务器拿来数据要展示到UI,就需要告知主线程去向UI里刷新数据,这就需要Handler消息机制了。

主线程、Looper和消息队列

Handler消息机制是围绕一个消息队列展开的,系统启动App时,App进程会启动ActivityThread主线程,主线程启动时,会创建一个Looper来循环处理消息,ActivityThread的main函数主要就是让这个Looper做死循环,如果消息循环停止了,App也就停止了:

//ActivityThread源码
Looper.loop();
//主线程会一直loop,如果出了looper,就直接抛异常,关闭app
throw new RuntimeException("Main thread loop unexpectedly exited");

Looper自己会去创建和管理一个消息队列,然后就开始不断地处理队列里的消息,子线程向消息队列里发消息,就可以和主线程通信了。
消息队列为空时,主线程会休眠,释放CPU资源。(主线程大部分时间都在休眠)


主线程和子线程通信

但是,Android为了确保主线程安全,Looper的消息队列是ThreadLocal的,禁止其他线程访问,所以子线程不能直接向消息队列里发消息。


线程安全禁止直接操作

这样,就需要在主线程里放一个Handler,Handler在创建时,会引用所在线程的Looper,进而获得Looper中的消息队列,这样,我们就可以用Handler来帮忙处理消息了。
通过Handler处理消息

这样一来,处理消息的主力就是Handler了。

Message

为了配合Handler,Message里面有这样几个主要属性:

  1. what:int值,存放消息ID(Send方式)
  2. callback:Runnable对象(Post方式,所以如果消息的callback非空,就直接处理runnable)
  3. target:Handler对象,指向Handler(消息还是丢给Handler处理)
  4. data: Bundle对象,存放业务数据
  5. next:Message对象,存放队列中的下一个Message(链式)

这几个主要的属性在Handler消息机制中非常重要,Handler要使用这几个属性才能正常运转。

Handler

Handler有两个作用:

  1. 发消息,子线程通过Handler,向消息队列发消息
    发消息有两种方式,send一个消息ID(msg.what),或者post一个Runnable(msg.callback),这两种方式最终都是包装为一个Message,通过一个sendMessageAtTime函数,把消息推进队列里。
  2. 处理消息,Looper通过Handler,逐个处理队列中的消息
    Looper在逐个处理消息时,拿到每个msg后,会去执行msg.target.dispatchMessage(msg),这里面的target其实就是Handler,这里会做一个判断,如果msg.callback不为空,说明消息里有Runnable(post进来的),就调用msg.callback.run,整个过程其实就是在Handler里调用Runnable的run函数;
 mHandler.post(new Runnable() {
       @Override
       public void run() {
       }
 });

如果msg.callback为空,说明没有Runnable(send进来的),就调用handlemessage,也就是执行Handler的handlemessage函数;

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

这样,Handler消息机制就能正常运转了。
Handler消息机制多见于主线程UI刷新,不过这并不是它的主业,它其实是用来实现Android线程间通信的,所以只要是跨线程的通信,都需要用到Handler,我们可以自己在子线程里写Looper。
如果我们要在子线程里使用Looper,可以使用HandlerThread,它会帮我们维护一个Looper;如果我们直接用Thread,就需要自己在run函数里做Looper的prepare和loop。

HanderThread

HanderThread就是handler+thread+looper,它有这么几个特点:
1.本质上是一个Thread
2.已经准备好了Looper,可以做Looper循环
3.本身是工作线程,其looper不在UI主线程,可以在handleMessage方法中执行异步任务
4.虽然不能并发处理多任务(因为是一个线程,不是线程池),但是性能损耗小
5.因为已经有Looper,所以可以在线程里创建和使用Handler

其他机制

Android的其他异步处理机制,其实都与Handler机制有关:

  • View.post(Runnable)/View.postDelayed(Runnable, long),这个机制一般也是走主线程的Handler处理,不过,如果View所在的视图树没有attach到window,就会自己维护一个RunQueue,在视图树performTraversals时,把RunQueue里的消息全部取出来,让视图树集中处理掉。
  • Activity.runOnUiThread(Runnable),这个函数其实是也是走的Handler机制,如果当前线程就是UI线程,则立即执行;如果当前线程不是UI线程,就把Runnable发给UI线程的消息队列,排队执行。runOnUiThread的源码大概是这样的:
if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
  • AsyncTask,AsyncTask实际上是个封装了线程池和handler的轻量级异步框架,它虽然用一个线程池做异步处理,但是处理结果总要提交回主线程,实际上就是使用一个Handler(AsyncTask有一个InternalHandler对象)来向主线程发消息(MESSAGE_POST_RESULT),并在主线程处理消息(handleMessage),这就要求把Handler放在主线程里,以便绑定主线程的Looper和消息队列,所以AsyncTask需要在主线程里创建和调用。
    有时候,AsyncTask持有的Activity对象可能会被销毁重建(如屏幕旋转),这就会导致AsyncTask指向的Activity对象无效,新的Activity无法接收AsyncTask的onPostExecute(),出现结果丢失的现象。

Handler的内存泄露

我们使用Handler时,如果用内部类或匿名内部类的方式去使用,就会遇到内部类持有外部对象导致的内存泄露(非静态内部类,在编译后的构造函数中会引用所在的外部类),解决方式有两种:

  1. 使用静态内部Handler类(+弱引用)。
  2. 在组件的onDestroy中,调用handler.removecallback()方法。

同样的,由于AsyncTask封装了handler,也会有同样的内存泄露问题,解决方法也是使用静态内部类+弱引用,或在onDestroy时及时调用asyncTask.cancel()方法,否则就会内存泄露,甚至导致崩溃(AsyncTask找不到要处理的View对象)。

引用

从Handler.post(Runnable r)再一次梳理Android的消息机制(以及handler的内存泄露)

Android中Thread、Handler、Looper、MessageQueue的原理分析

源码解析Android中AsyncTask的工作原理

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