Handler、Looper、MessageQueue、HanlderThread的详细解读

1. 创建Handler对象需要Looper:

在主线程中可以直接创建Handler对象,而在子线程中需要先调用Looper.prepare()才能创建Handler对象。
看源码:Handler的无参构造函数中有这样一个判断:

    mLooper = Looper.myLooper();  
        if (mLooper == null) {  
            throw new RuntimeException(  
                "Can't create handler inside thread that has not called Looper.prepare()");  
        }  
   mQueue = mLooper.mQueue; 

显然,构造Handler对象,必须保证所在线程中存在一个Looper对象,同时依靠looper得到MessageQueue对象。
在主线程中,本身就含有Looper对象,因为在程序启动的时候,已经自动调用了Looper.prepare()方法。

public static final void prepare() {  
    if (sThreadLocal.get() != null) {  
        throw new RuntimeException("Only one Looper may be created per thread");  
    }  
    sThreadLocal.set(new Looper());  
} 

Looper的prepare方法的作用就是判断sThreadLocal中是否已经存在Looper了,如果还没有则创建一个新的Looper设置进去。

2. Looper的作用:

Looper类用来为一个线程开启一个消息循环。

  1. 默认情况下android中新诞生的线程是没有开启消息循环的。(主线程除外,主线程系统会自动为其创建Looper对象,并开启消息循环Looper.loop()
    Looper对象通过MessageQueue来存放消息和事件。一个线程只能有一个Looper,MessageQueue是在Looper的构造函数中创建的,因此一个Looper对应一个MessageQueue。

  2. 通常是通过Handler对象来与Looper进行交互的。Handler向指定的Looper发送消息。
    默认情况下Handler会与其被定义时所在线程的Looper绑定,比如,Handler在主线程中定义,那么它是与主线程的Looper绑定。
    new Handler() 等价于 new Handler(Looper.myLooper())。

  3. Looper.myLooper(): 用户获取当前进程的looper对象。
    Looper.getMainLooper(): 用于获取主线程的Looper对象。
    Looper.loop(): 让Looper开始工作,从消息队列里取消息,处理消息。

注意:写在Looper.loop()之后的代码不会被执行,这个函数内部是一个死循环,当调用mHandler.getLooper().quit()后,loop才会中止,其后的代码才能得以运行。

3. 线程间发消息:

不论Handler对象在哪个线程中发出消息,最终消息都会回到创建Handler对象的那个线程中取处理。

4. 消息传递机制

Hnadler从sendMessge到handleMessage的过程。

clipboard.png
  1. Handler调用自身的sendMessageAtTime(Message msg, long uptimeMillis)方法,msg被放入MessageQueue对象中去。

  2. MessageQueue调用其自身的enqueMessage()方法,将所有放入的Message对象按时间排序。方法内部主要过程有:msg.when表示该条Messge的入队时间,msg.next表示下一条准备出队的Message。

  3. Looper调用自身的loop()方法,依次将MessageQueue中的Message取出。MessageQueue的next()方法,就是消息队列的出队方法。

    public static final void loop() {
    Looper me = myLooper();
    MessageQueue queue = me.mQueue;
    while (true) { // 一个死循环
    Message msg = queue.next(); // might block
    if (msg != null) {
    if (msg.target == null) {
    return;
    }
    msg.target.dispatchMessage(msg); // 处理消息
    msg.recycle();
    }
    }

  1. 每当有一个消息出队,就将它传递到msg.target(就是发这条Message的Handler)的dispatchMessage()方法中。如果mCallback不为空,则调用mCallback的handleMessage()方法,否则直接调用Handler的handleMessage()方法,并将消息对象作为参数传递过去。

    public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
    handleCallback(msg);
    } else {
    if (mCallback != null) {
    if (mCallback.handleMessage(msg)) {
    return;
    }
    }
    handleMessage(msg); // 回到handler自身的handleMessage方法
    }

5. 最终由谁处理消息?

在Looper的loop()方法中,取出从MessageQueue中取出下一位Message之后,就进入了处理消息阶段。

public static final void loop() {  
    Looper me = myLooper();  
    MessageQueue queue = me.mQueue;  
    while (true) {
        Message msg = queue.next(); 
        if (msg != null) {  
            if (msg.target == null) {  
                return;  
            }   
            msg.target.dispatchMessage(msg);
            msg.recycle();  
        }  
    }
}    

msg.target是谁?

回到最开始的Handler的sendMessage方法:

public boolean sendMessageAtTime(Message msg, long uptimeMillis)  {  
    boolean sent = false;  
    MessageQueue queue = mQueue;  
    if (queue != null) {  
        msg.target = this;  
        sent = queue.enqueueMessage(msg, uptimeMillis);  
    } else {  
        RuntimeException e = new RuntimeException(  
            this + " sendMessageAtTime() called with no mQueue");  
    }  
    return sent;  
}  

再来看看Message类的属性:

public final class Message implements Parcelable {  
    public int what;  
    public int arg1;   
    public int arg2;  
    public Object obj;  
    int flags;  
    long when;  
    Bundle data;  
    Handler target;         // target处理  
    Runnable callback;      // Runnable类型的callback  
    // sometimes we store linked lists of these things  
    Message next;           // 下一条消息,消息队列是链式存储的  
    // 代码省略 ....  
} 

所以,msg.target就是发送这条msg的Handler对象。
这就是“不论Handler对象在哪个线程中发出消息,最终消息都会回到创建Handler对象的那个线程中取处理。”的原理。饶了一圈,最终处理消息的还是这条MessageHandler对象,然后调用自身的dispatchMessage(msg)方法,消息对象作为这个其参数。

6. 处理消息的方式有几种?

深入看一下消息的最终处理方式:Handler的dispatchMessage(msg)方法。

public void handleMessage(Message msg) {  
}  

private final void handleCallback(Message message) {  
    message.callback.run();  
}  

public void dispatchMessage(Message msg) {  
    if (msg.callback != null) {  
        handleCallback(msg);   // 设置了callback,调用callback(Runnable)的run方法
    } else {  
        if (mCallback != null) {  
            if (mCallback.handleMessage(msg)) {  
                return;  
            }  
        }  
        handleMessage(msg);   // 没有设置callback,直接调用handleMessage
    }  
}

Message类的属性中可知,msg.callback指的是一个Runnable对象。

Handler分发消息有两种情况,一种情况是直接sendMessage,这种情况不会设置callback。另一种情况是诸如post(Runnable r)postDelayed(Runnable r, long l)等方法,这种情况会设置callback。

public final boolean post(Runnable r)  {  
   return  sendMessageDelayed(getPostMessage(r), 0);  
}  

private final Message getPostMessage(Runnable r) {  
    Message m = Message.obtain();  
    m.callback = r;  
    return m;  
}  

public final boolean sendMessageDelayed(Message msg, long delayMillis)  {  
    if (delayMillis < 0) {  
        delayMillis = 0;  
    }  
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);  
} 

post()的使用场景举例:

Handler handler = new Handler();  
        new Thread(new Runnable() {  
            @Override  
            public void run() {  
                handler.post(new Runnable() {  
                    @Override  
                    public void run() { 

                    }  
                });  
            }  
        }).start(); 

另外,View的post()方法, Activity的runOnUiThread()方法,均是对Handler的post()方法进行了包装。

7. 总结:Handler、Looper、MessageQueue分别存在于哪里,如何相互工作?

Handler的创建必须保证其所在线程有且只有一个Looper对象的存在。Looper构造的时候,MessageQueue会随之一同创建。一个线程中可以有多个Handler的存在,但与之对应的线程、Looper和MessageQueue只有一个。

随后,Handler对象不论在哪个线程中发Message,都会被与之对应的MessageQueue存放到自身队列当中去。并且根据所发送的Message的target属性,标记发送这条Message从属于哪个Handler。

随后,通过Looper和MessageQueue的按时间先后依次取出Message后,再根据Message的target属性,识别这条Message是哪个Handler发送的,交由这个Handle回到创建时候的线程中去处理这个Message。

8. 引申类:HandlerThread

  • 构造:
    HandlerThread继承于Thread,所以它本质就是个Thread。与普通Thread的差别就在于,它在执行run()方法的时候,会自动创建Looper对象,并持有其作为自己的一个成员变量。

    @Override
    public void run() {
    
      mTid = Process.myTid();
      Looper.prepare();
    
      synchronized (this) {
         mLooper = Looper.myLooper();
         notifyAll();
      }
    
     Process.setThreadPriority(mPriority);
     onLooperPrepared();
     Looper.loop();
     mTid = -1;
    
    }
    
  • 用途:
    Handler最终处理消息所在的线程是一开始创建这个Handler的所在的线程。假如这个处理消息过程是一个耗时的过程,那么放在主线程中是不合适的。HandlerThread的作用是让耗时工作在这个线程中处理。

  • 用法:

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

推荐阅读更多精彩内容