源码分析 -- 异步消息处理机制

0概述

UI是线程不安全的,如果在子线程中直接进行UI操作,程序就有可能会崩溃。解决的方案即创建一个Message对象,然后借助主线程的Handler发送出去,之后在HandlerhandleMessage()方法中获得刚才发送的Message对象,然后在这里进行UI操作就不会再出现崩溃了。

也就是子线程是通过handler给主线程发送消息,然后由主线程在消息处理函数中处理刷新界面的操作。

1 Handler的创建过程

如果在子线程中直接new一个handler会崩溃:

代码如下:

 

 new Thread(new Runnable() {  

             @Override   

              public void run() {  

                     handler2 = new Handler();  

             }  

          }).start(); 

报错如下:

 Can't create handler inside thread that has not called Looper.prepare()

 //正确使用方式

new Thread(new Runnable() {  
 
      @Override  

     public void run() {  

         Looper.prepare();     // 创建一个looper并和当前子线程关联,只能调用一次,Looper的构造函数里会创建消息队列,如果无参构造的Handler,必须调用创建对应的消息队列 

         handler2 = new Handler(); 
         

         Looper.loop();  //必须调用。否则。只是把消息发送到了消息队列中,没有取出来。从这个函数里才会循环的去消息队列中取消息然后分发给handleMessage处理


。不然用户自己收不到消息。

 
      }  

 }).start(); 

 
 
 
 

看下原码直接new一个Handler为什么会崩溃:

<pre name="code" class="java">public Handler() {  

      mLooper = Looper.myLooper();  //创建Handler的时候如果mLooper 为null就会报告这个异常

      if (mLooper == null) {  

         throw new RuntimeException(  

             "Can't create handler inside thread that has not called Looper.prepare()");  

     }  
     <span style="color:black;"></span>
 
 

    再看下myLooper的实现:

public static final Looper myLooper() {  

     return (Looper)sThreadLocal.get();  //从sThreadLocal里面取出Looper,这个值是prepare函数里设置进去的。

}  

 
public static void prepare() {
     prepare(true);
}

private static void prepare(boolean quitAllowed) {    <pre name="code" class="java">     //这里可以看出每个线程中最多只能创建一个Looper对象和这个线程绑定
    
 if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
     }
     sThreadLocal.set(new Looper(quitAllowed)); //如果当前线程没有对应的Looper,则创建新的Looper,创建Looper的时候会创建消息队列


}
 

在主线程可以直接创建handler是因为由于在程序启动的时候,系统已经帮我们自动调用了Looper.prepare()方法。查看ActivityThread中的main()方法

 public static void main(String[] args) {  

     Looper.prepareMainLooper();  

2 消息的发送流程

Handler属于创建的线程。在哪个线程中创建的这个handler,这个hanlder就和哪个线程是绑定的。也就是和那个线程的消息队列是绑定的

涉及的源码类 Handler, Looper, MessageQueue

1      用法

Message message = new Message();  

message.arg1 = 1;  

Bundle bundle = new Bundle();  

bundle.putString("data", "data");  

message.setData(bundle);  
handler.sendMessage(message) // 消息会发送到创建这个handler的线程的消息队列中。

问题:

Handler到底是把Message发送到哪里去了呢?   //发送到了这个handler对应的线程(创建这个handler的线程)的消息队列中

HandlerhandleMessage()方法中怎么重新得到发送的Message呢?      //通过loop函数重新获取

Handler中提供了很多个发送消息的方法,其中除了sendMessageAtFrontOfQueue()方法之外,其它的发送消息方法最终都会辗转调用到sendMessageAtTime()方法中

//uptimeMillis参数则表示发送消息的时间,它的值等于自系统开机到当前时间的毫秒数再加上延迟时间,如果你调用的不是sendMessageDelayed()方法,延迟时间就为0

public final boolean sendMessage(Message msg)
 {
        return sendMessageDelayed(msg, 0);
 }

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

看下sendMessageAtTime()源码的实现:实现在Hanlder.java中

 public boolean sendMessageAtTime(Message msg, long uptimeMillis)  
  {  

         boolean sent = false;  

         MessageQueue queue = mQueue;  //消息队列。用于将所有收到的消息以队列的形式进行排列,并提供入队和出队的方法。<pre name="code" class="java">mQueue是在Looper的构造函数中创建的,因此一个Looper也就对应了一个MessageQueue。看后面Looper的源码     
// 如果当前的handler没有对象的消息队列,则抛出异常。 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, 0); }
 
 

Handler.java中:

发送消息的时候把message和handler绑定,通过Message对象的target属性指定对应的handler

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;  // 这里是消息和发送的handler对象关联。之后分发消息的时候通过这个字段就知道这个消息应该分给哪个handler的消息处理函数去处理。因为一个线程可以创建多个handler.每个handler有自己的消息处理函数。但是一个线程只有一个消息队列,这个线程所有的handler的消息都是在同一个消息队列里,所以分发消息的时候需要区分是哪个handler的消息
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis); // 调用消息队列类提供的入队的方法,把要发送的消息添加到消息队列里
 }


现在看下Handler对应的消息队列mQueue是怎么来的:

Handler.java中

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

public Handler(Callback callback, boolean async) {
        mLooper = Looper.myLooper();  // 这里就是前面说的直接创建时如果没有Looper会崩溃
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue; // 消息队列是Looper对象中的mQueue
        mCallback = callback;
        mAsynchronous = async;
  }

看下Looper.java中的实现:

// 返回的是个Looper对象

 public static Looper myLooper() {
     return sThreadLocal.get();
 }

而Looper中的mQueue的创建如下:

private Looper(boolean quitAllowed) {
      mQueue = new MessageQueue(quitAllowed);
      mThread = Thread.currentThread();
}
可见创建Looper对象的时候会创建这个Looper对象的消息队列。

而Looper对象的创建是在Looper.parpare()中。

33消息队列的入队操作

MessageQueue.java提供的对外接口,用于把发送的消息保存到消息队列中,其实最关键的就是根据消息的执行时间找到这个消息要插入在消息队列中的位置。
类似单向链表的插入流程

//  when是发送这个消息的延时时间

final boolean enqueueMessage(Message msg, long when) {  
    //如果一个消息已经在消息队列中,但是还没有被执行掉,又重复的发送了这个消息就会报告下面的错误。遇到过。
    if (msg.when != 0) {  //如果消息执行完了,这个字段应该是0,后来的源码这里已经变了。
        throw new AndroidRuntimeException(msg + " This message is already in use.");  
    } 
    // 没有对应要处理的handler对象,则抛出异常 
    if (msg.target == null) {  
        throw new RuntimeException("Message must have a target");  
    }  
    synchronized (this) {  
        if (mQuiting) {  
            RuntimeException e = new RuntimeException(msg.target + " sending message to a Handler on a dead thread");  
	            Log.w("MessageQueue", e.getMessage(), e);  
            return false;  
        } else if (msg.target == null) {  
            mQuiting = true;  
        }  
        msg.when = when;  
        // 取出来当前队列最前头的消息。也就是待处理的消息,根据要发送的时间的先后找到当前消息要插入到队列中的位置
        Message p = mMessages;  // mMessages就是消息队列中下一个要处理的消息
<pre name="code" class="java">        if (p == null || when == 0 || when < p.when) {  //如果消息队列为空或者这个消息是要立即执行的或者当前消息的执行时间小于队列中最前面的消息,则把这个消息放到消息队列的最前头。
msg.next = p; //指向此消息的下一个待处理的消息 mMessages = msg; this.notify(); } else { // 如果这个消息的执行时间比当前队列里的最开头的消息要晚,则往后找到这个消息应该插入的位置插入到消息队列中去。相当于单向链表插入算法。 Message prev = null;
//找到要插入的位置。P指的是当前的节点,当当前的节点大于要插入的节点的时候,则之前的那个节点后面就是要插入的位置。 while (p != null && p.when <= when) { prev = p; p = p.next; } //插入这个消息。 msg.next = prev.next; prev.next = msg; this.notify(); }
} return true; }
 

43消息队列的循环

在一个线程中调用了Looper.prapare创建了和这个线程对应的一个消息队列和Looper对象以后

要通过调用Looper.looper才能让消息队列运转起来

下面看下looper的源码:

Looper.java

public static final void loop() {  
     Looper me = myLooper();  // 获取当前线程对应的Looper对象
     MessageQueue queue = me.mQueue;  // 获取当前线程对应的消息队列
     //这是一个死循环,不停的从消息队列中取出来消息。然后把这个消息分发给对应的hanlder处理。如果没有消息就一直等待新的消息。
     while (true) {  
         Message msg = queue.next(); // 消息队列的出队函数
         if (msg != null) {  
	    if (msg.target == null) {  
            return;  
         }  
         msg.target.dispatchMessage(msg); // 这是分发的重点。因为一个线程可以有多个hanlder.这里会根据消息的target字段,把消息分发给对应的hanlder去处理。所以各自的hanlder只会处理自己发送出去的消息。
         msg.recycle();  // 处理完这个消息后,释放,则对应的when字段肯定就成0了。
         }  
    }  
}  

查看Message的源码可以看到target属性就是Handler类型的变量。

Handler.java

public void dispatchMessage(Message msg) {  
    if (msg.callback != null) {    // 如果是通过Handler的post方法执行的runnable对象,则在这里直接回调。
        handleCallback(msg);  
    } else {  
        // 这里都是对message对象的处理,通过sendMessage方法发送的
        if (mCallback != null) {  
            if (mCallback.handleMessage(msg)) {  // 如果创建handler的时候有传回调函数,则调用的是回调中的方法
            return;  
            }  
        }
        handleMessage(msg);  // 一般创建hanlder()时无参数,就把消息分发到了自己重写的handleMessage()方法,
    }  
}  

 

53消息队列,Handler, 线程, Looper的关系

1.   一个线程只有一个Looper,一个消息队列,可以有多个Handler

2.  子线程默认是没有消息队列的,所以不能直接创建Handler,  因为Handler默认是和创建它的线程绑定的,而用Handler发送消息必须有对应的消息队列

3.  通过调用Looper.prepare来给当前子线程创建一个Looper对象。只能调用一次。也就是一个线程只能有一个Looper. 用于消息循环的。

4.  同一个线程的多个Handler对应同一个消息队列。根据msg.target属性来记录消息属于哪个Handler。

5.  通过Looper.loop进行消息循环,不停的从消息队列中取出消息分发给对应的Handler处理

6.  Handler有多个构造函数,可以指定Looper, 消息处理函数。 所以子线程创建的Handler也可以不创建自己单独的消息队列而绑定到主线程的消息队列上。但是默认的构造函数必须先给子线程创建对应的消息队列。目前没用过。

7. Handler 是用于发送消息和处理消息的,Looper是用于消息循环的

63标准的异步消息处理子线程的写法

默认创建的子线程是没有自己的消息队列的。

下面的代码就定义了一个有自己消息队列的子线程,可以在子线程中只处理自己线程的消息。

class LooperThread extends Thread {  
      public Handler mHandler;  
      public void run() {  
          Looper.prepare();  // 必须有,没有的话不能创建hanlder.
          mHandler = new Handler() {  
              public void handleMessage(Message msg) {   //必须重写。不然不能处理自己发送出去的消息
                  // process incoming messages here  
              }  
          };  
          Looper.loop();  //必须有。否则。只是把消息发送到了消息队列中,没有取出来。从这个函数里才会循环的去消息队列中取消息然后分发给handleMessage处理。不然用户自己收不到消息。
         }  
     }  



通过handler的sendMessage方法把消息发送到消息队列MessageQueue中

通过looper从消息队列里取出消息,然后分发给对应的handler处理

73子线程中更新UI的方法

只有主线程能直接更新UI。

在子线程中更新UI的原理最终都是一样的,都是用了异步消息处理机制

最终都是通过handler发送到了线程的消息队列中,消息队列中保存的都是Message对象

发送分为message对象和runnable对象

只要是runnable对象,最终都是调用的handler的post方法,会封装为Message对象


1. HandlersendMessage 

发送的是Message对象

需要的是主线程的Handler,这样才能把消息发送到主线程的消息队列中

// 主线程的handler重写这个方法

handleMessage() {

     // 主线程中这里直接更新UI

}

// 子线程中发送消息
Message message = new Message();  

message.arg1 = 1;  

Bundle bundle = new Bundle();  

bundle.putString("data", "data");  

message.setData(bundle);  

handler.sendMessage(message);  // 这个handler是主线程的handler

2. Handlerpost()方法  

发送的是Runnable对象,最终会封装为message对象放到消息队列里

handler = new Handler();  //主线程的handler
new Thread(new Runnable() {  
        @Override  
        public void run() {  
               handler.post(new Runnable() {  //在子线程中通过主线程的handle对象去执行一个异步的事件。则这个事件就会发送到主线程的消息队列中,子线程没有创建自己的hanlder和消息队列。执行的时候是在主线程中,所以可以刷新UI
                    @Override  
                    public void run() {  
                      // 在这里可以直接进行UI操作 
                      如果是发送的消息,用sendMessage方法,则需要重写handlerMessage方法。最终会把消息对象传递给handlerMessage方法处理。
                      如果是发送的runnable对象,用post方法。不需要重写handlerMessage方法,系统自动执行run里的函数。
                    }  
               });  
         }  
Handler.java 源码实现:
public final boolean post(Runnable r)  
{  
   return  sendMessageDelayed(getPostMessage(r), 0);  // 这和sendMessage的实现是一样的了 
}  

<div style="background:white;">
</div><span style="color:black;"> </span><pre name="code" class="java">private final Message getPostMessage(Runnable r) {  
    Message m = Message.obtain();  
    m.callback = r;    //把要执行的Runnable对象作为Message的callback字段封装为message对象。保存到消息队列中的都是Message对象
    return m; 

}
// 看下消息分发时的源码,上面讲消息循环时也讲到了
public void dispatchMessage(Message msg) {  
   if (msg.callback != null) {  //这个就是传入的runnable对象,这个也是从消息队列里分发出来的,如果有runnable对象则不会调用handleMessage的方法。
        handleCallback(msg);  
   } else {  
     //处理消息对象
        
   } 
} 

// 这里就是直接调用了runnable对象实现的run方法  
private final void handleCallback(Message message) {  
   message.callback.run();  
} 

 

3. 通过view的post方法

public boolean post(Runnable action) {  
     Handler handler;  
     if (mAttachInfo != null) {  
      handler = mAttachInfo.mHandler;  //获取这个view所在线程的hanlder
      } else {  
      ViewRoot.getRunQueue().post(action);  
        return true;  
     }  
     return handler.post(action);  //最终调用的还是handler的post的方法
}  

4. Activity中的runOnUiThread() 方法 ,

如果当前线程是UI线程,则立即执行Runnable的回调

如果当前线程是子线程,则需要发送消息到主线程的消息队列中再执行

public final void runOnUiThread(Runnable action) {
      if (Thread.currentThread() != mUiThread) {
            mHandler.post(action); //最终调用的还是handler的post的方法
      } else {
            action.run();
      }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值