Android--异步消息处理机制(Handler、Looper、Message、MessageQueue)

美女图集03
Handler的由来

当程序第一次启动的时候,Android会同时启动一条主线程(Main Thread)来负责处理与UI相关的事件,我们叫做UI线程
Android的UI操作并不是线程安全的(出于性能优化考虑),意味着如果多个线程并发操作UI线程,可能导致线程安全问题。
为了解决Android应用多线程问题——Android平台只允许U线程修改Activity里的UI组件,就会导致新启动的线程无法改变界面组件的属性值。
简单总结:当主线程队列处理一个消息超过5秒,android就会抛出一个ANR(无响应)的异常,所以,我们需要把一些要处理时间比较长的消息,放在一个单独线程里面进行处理,把处理以后的结果,返回给主线程运行,就需要用到Handler来进行线程组件的通信。

Handler的作用
  • 让线程延时执行,主要用到两个方法:
方法1:final boolean postAtTime(Runnable r, long uptimeMillis)
方法2:final boolean postDelayed(Runnable r, long delayMillis)```
* 让任务在其他线程中执行并返回结果,分为两个步骤:
**1.在新启动的线程中发送消息**
使用Handler对象的```sendMessage()```方法或者```sendEmptyMessage()```方法发送消息。
**2.在主线程中获取处理消息**
重写Handler类中处理消息的方法(void handleMessage(Message msg)),当新启动的线程发送消息时,消息发送到与之关联的MessageQueue。而Handler不断地从MessageQueue中获取并处理消息。
#####Handler更新UI线程一般使用
(1):首先要进行Handler声明,复用handleMessage方法(放在主线程中)

private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
// TODO 接收消息并且去更新UI线程上的控件内容
if (msg.what == UPDATE) {
// 更新界面上的textview
tv.setText(String.valueOf(msg.obj));
}
super.handleMessage(msg);
}
};```
(2):子线程发送Message给UI线程表示自己任务已经执行完成,主线程可以做相应的操作了。

new Thread() {
       @Override
       public void run() {
          // TODO 子线程中通过handler发送消息给handler接收
                  //,由handler去更新TextView的值
          try {
              //do something
            
              Message msg = new Message();
              msg.what = UPDATE;                    
              msg.obj = "更新后的值" ;
              handler.sendMessage(msg);
          } catch (InterruptedException e) {
           e.printStackTrace();
          }
       }
 }.start();```

#####Handler原理分析
* **Handler的构造函数**

1: public Handler()
2: public Handler(Callback callback)
3: public Handler(Looper looper)
4: public Handler(Looper looper, Callback callback) ```
第①个和第②个构造函数都没有传递Looper,这两个构造函数都将通过调用Looper.myLooper()获取当前线程绑定的Looper对象,然后将该Looper对象保存到名为mLooper的成员字段中。     
下面来看①②个函数源码:

    public Handler() {
        this(null, false);
    }

    public Handler(Callback callback) {
        this(callback, false);
    }

    //他们会调用Handler的内部构造方法
    public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() ||klass.isMemberClass()
                                         || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                  Log.w(TAG, "The following Handler class should be 
                                   static or leaks might occur: " +
                    klass.getCanonicalName());
             }
        }

        //重点:Looper.myLooper()获取了当前线程保存的Looper实例
        mLooper = Looper.myLooper();

        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not 
                                       called Looper.prepare()");
        }
        //重点:获取MessageQueue(消息队列)
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }```
**通过Looper.myLooper()获取了当前线程保存的Looper实例,又通过这个Looper实例获取了其中保存的MessageQueue(消息队列)。每个Handler对应一个Looper对象,产生一个MessageQueue**
第③个和第④个构造函数传递了Looper对象,这两个构造函数会将该Looper保存到名为mLooper的成员字段中。   
下面来看③④个函数源码:
public Handler(Looper looper) {
    this(looper, null, false);
} 

public Handler(Looper looper, Callback callback) {
    this(looper, callback, false);
}

//他们会调用Handler的内部构造方法
public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
第②个和第④个构造函数还传递了Callback对象,Callback是Handler中的内部接口,需要实现其内部的handleMessage方法,Callback代码如下:

public interface Callback {
public boolean More ...handleMessage(Message msg);
}```
Handler.Callback是用来处理Message的一种手段,如果没有传递该参数,那么就应该重写Handler的handleMessage方法,也就是说为了使得Handler能够处理Message,我们有两种办法:
方法1:向Hanlder的构造函数传入一个Handler.Callback对象,并实现Handler.Callback的handleMessage方法
方法2:无需向Hanlder的构造函数传入Handler.Callback对象,但是需要重写Handler本身的handleMessage方法        
也就是说无论哪种方式,我们都得通过某种方式实现handleMessage方法,这点与Java中对Thread的设计有异曲同工之处。

  • Handle发送消息的几个方法源码
//发送消息
public final boolean sendMessage(Message msg){
        return sendMessageDelayed(msg, 0);
 }```

//发送空消息,并且带有时间延迟
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}```

//发送消息,并且带有时间延迟
 public final boolean sendMessageDelayed(Message msg, long delayMillis){
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, 
                SystemClock.uptimeMillis() + delayMillis);
}```

//发送消息,并且带有具体的时间限定
public boolean sendMessageAtTime(Message msg, long uptimeMillis){
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}```
我们可以看出他们最后都调用了sendMessageAtTime(),然后返回了enqueueMessage方法,下面看一下此方法源码:

    private boolean enqueueMessage(MessageQueue queue
                                    , Message msg, long uptimeMillis) {
     //把当前的handler作为msg的target属性
          msg.target = this;
         if (mAsynchronous) {
              msg.setAsynchronous(true);
         }
         return queue.enqueueMessage(msg, uptimeMillis);
    }```
在该方法中有两件事需要注意:
**1. msg.target = this;**该代码将Message的target绑定为当前的Handler
**2. queue.enqueueMessage **
变量queue表示的是Handler所绑定的消息队列MessageQueue,通过queue.enqueueMessage(msg,uptimeMillis)我们将Message放入到消息队列中。
下面通过一张图,来看完整的方法调用顺序:
![Handler、Looper、Message、MessageQueue调用顺序](http://upload-images.jianshu.io/upload_images/3416944-d9d3d3505b4ba800.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

#####Looper原理分析
我们一般在主线程声明Handler,有时我们需要继承Thread类实现自己的线程功能,当我们在里面声明Handler的时候会报错。其原因是主线程中已经实现了两个重要的Looper方法,下面看一看ActivityThread.java中main方法的源码:

public static void main(String[] args) {
//......省略
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
throw new RuntimeException("Main thread loop
unexpectedly exited");
}```

  • 首先看prepare()方法
     public static void prepare() {
         prepare(true);
     }

     private static void prepare(boolean quitAllowed) {
     //证明了一个线程中只有一个Looper实例
         if (sThreadLocal.get() != null) {
             throw new RuntimeException("Only one Looper may be 
                                             created per thread");
         }
         sThreadLocal.set(new Looper(quitAllowed));
     }```
该方法会调用Looper构造函数同时实例化出MessageQueue和当前thread.
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
} 

public static MessageQueue myQueue() {
    return myLooper().mQueue;
}```

prepare()方法中通过ThreadLocal对象实现Looper实例与线程的绑定。(不清楚的可以查看 ThreadLocal的使用规则和源码分析

  • loop()方法
    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() 
                                       wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                return;
            }

            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " 
                                        + msg.callback + ": " + msg.what);
            }
            //重点****
            msg.target.dispatchMessage(msg);

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " 
                                                         + msg.callback);
            }

            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) 
                                        + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }
            msg.recycleUnchecked();
        }
    }```
首先looper对象不能为空,就是说loop()方法调用必须在prepare()方法的后面。
Looper一直在不断的从消息队列中通过MessageQueue的next方法获取Message,然后通过代码```msg.target.dispatchMessage(msg)```让该msg所绑定的Handler执行dispatchMessage()方法以实现对Message的处理。
Handler的dispatchMessage的源码如下:

public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}```
我们可以看到Handler提供了三种途径处理Message,erqie处理有前后优先级之分:首先尝试让postXXX()中传递的Runable执行,其次尝试让Handler构造函数中传入的Callback的handleMessage()方法处理,最后才是让Handler自身的handleMessage()方法处理Message。

如何在子线程中使用Handler

Handler本质是从当前的线程中获取到Looper来监听和操作MessageQueue,当其他线程执行完成后回调当前线程。
子线程需要先prepare()才能获取到Looper对象,是因为子线程只是一个普通的线程,其ThreadLoacl中没有设置过Looper,所以会抛出异常,而在Looper的prepare()方法中sThreadLocal.set(new Looper())是设置了Looper的。

  • 实例代码:定义一个类实现Runnable接口或继承Thread类(一般不继承)。
class Rub implements Runnable {  
        public Handler myHandler;  
        // 实现Runnable接口的线程体 
        @Override  
        public void run() {  
             /*1、调用Looper的prepare()方法为当前线程创建Looper对象并,
            创建Looper对象时,它的构造器会自动的创建相对应的MessageQueue*/
             Looper.prepare();  
            
             /*2、创建Handler子类的实例,重写HandleMessage()方法
                             ,该方法处理除当前线程以外线程的消息*/
             myHandler = new Handler() {  
                @Override  
                public void handleMessage(Message msg) {  
                    String ms = "";  
                    if (msg.what == 0x777) {  
                     
                    }  
                }  
            };  
            //3、调用Looper的loop()方法来启动Looper让消息队列转动起来
            Looper.loop();  
        }
    }```
**注意分成三步:**
1:调用Looper的prepare()方法为当前线程创建Looper对象,创建Looper对象时,它的构造器会创建与之配套的MessageQueue。
2:有了Looper之后,创建Handler子类实例,重写HanderMessage()方法,该方法负责处理来自于其他线程的消息。
3:调用Looper的looper()方法启动Looper。然后使用这个handler实例在任何其他线程中发送消息,最终处理消息的代码都会在你创建的Handler实例的线程中运行。

#####总结
* **Handler**
发送消息,它能把消息发送给Looper管理的MessageQueue,Looper分发给它消息。
* **Message**
Handler接收和处理的消息对象。
* **Looper**
每个线程只有一个Looper,它负责管理对应的MessageQueue,会不断地从MessageQueue中取出消息,并将消息Messsge分发给对应的Handler进行处理。
主线程中,系统已经初始化了一个Looper对象,因此可以直接创建Handler即可,就可以通过Handler来发送消息、处理消息。程序自己启动的子线程,程序必须自己创建一个Looper对象,bignqie启动它,调用Looper.prepare()方法。
* **prapare()方法**
保证每个线程最多只有一个Looper对象。
* **looper()方法**
启动Looper,使用一个死循环不断取出MessageQueue中的消息,并将取出的消息分发给对应的Handler进行处理。
* **MessageQueue**
由Looper负责管理,它采用先进先出的方式来管理Message。
* **Handler的构造方法**
会首先得到当前线程中保存的Looper实例,进而与Looper实例中的MessageQueue相关联。
* **Handler的sendMessage方法**
会给msg的target赋值为handler自身,然后加入MessageQueue中。

参考链接:
[线程通信基础流程分析(Handler、Looper、Message、MessageQueue)](https://github.com/GeniusVJR/LearningNotes/blob/master/Part1/Android/%E7%BA%BF%E7%A8%8B%E9%80%9A%E4%BF%A1%E5%9F%BA%E7%A1%80%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90.md)


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

推荐阅读更多精彩内容