Handler

Posted by Cc1over on July 10, 2018

0、Android线程检查机制

void checkThread(){
    if(mThread!=Thread.currentThread()){
        throw new CalledFromWrongThreadException("Only the original thread that create a view hierarchy can touch its views.");
    }
}

基于Android的checkThread机制的存在,如果需要从服务端拉取数据并显示在UI上,这时就需要Handler去实现工作线程到UI线程的切换。

1、对Handler机制的简单总结

  • 创建与线程绑定的Looper,同时会创建一个与之关联的MessageQueue用于存放消息
  • 开启消息循环,从MessageQueue中获取待处理消息,若无消息会阻塞线程
  • 通过Handler发送消息,此时会将Message入队列到MessageQueue中,并且唤醒等待的Looper
  • Looper获取的消息会投递给对应的Handler处理

2、Handler的基本用法

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

最普通的发送消息,其内部使用了延时发送,延时设为0。这是Android里面常用的模式,这样做的好处是当你不需要传参数的时候可以直接使用无参的,使用方便,而且内部减少了代码量,避免再重复写一个发送消息的实现。

    public final boolean sendEmptyMessage(int what)
    {
        return sendEmptyMessageDelayed(what, 0);
    }

发送一个空消息只需要传递一个信号,用消息类型就足够携带我们要传递的信息。同样调用了它的延时发送方法,延时为0。

    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }

这是一个定时在某个确定的时刻发送空消息的方法,内部同样调用了发送消息的对应方法。而这个确定时刻发送同样是用延时发送消息的方法来实现,我们继续看crtl+左键点开看看延时发送消息这个方法。

    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);
    }

上面发送消息的方法最后都会来到这里调用这个方法,而在这里又调用了这个enqueueMessage(queue, msg, uptimeMillis)方法,接着来看这个方法的实现。

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

在这个方法里面,会给Message的target赋值,作为一个handler给自己发消息的标识,而mAsynchronous这个值是用来判断是否同步的,这个值是在handler的构造中传进来,然后再传给Message,再去调用MessageQueue的enqueueMessage方法,到这里Handler消息的发送过程也就结束了。在这个地方也符合我们的单一责任原则,把消息塞给队列的工作交给MessageQueue,而MessageQueue通过传进来的这个时间对所有插入Message进行排队,构成一个单链表。存放消息和循环用。(关于链表结构:在这里由于需要对数据不停的再插入删除,所以使用链表结构优势也会很大。

    public final boolean sendMessageAtFrontOfQueue(Message msg) {
        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, 0);
    }

这个和上面的方法基本一样就是设置了时间为0,表示在排队时把它塞到队列的最前端。 post系列的方法:

    public final boolean postAtTime(Runnable r, long uptimeMillis)
    {
        return sendMessageAtTime(getPostMessage(r), uptimeMillis);
    }
 
    public final boolean postAtTime(Runnable r, Object token, long uptimeMillis)
    {
        return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
    }
 
    public final boolean postDelayed(Runnable r, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }
 
    public final boolean postAtFrontOfQueue(Runnable r)
    {
        return sendMessageAtFrontOfQueue(getPostMessage(r));
    }

这一系列的post方法的最后都是调用了send系列的方法,而通过getPostMessage()方法把传进来的Runnable转化成一个message对象,继续crtl+左键看看源码。

private static Message getPostMessage(Runnable r, Object token) {
        Message m = Message.obtain();
        m.obj = token;
        m.callback = r;
        return m;
    }

在这可见,Message里面有一个callback的Runable属性,无论是send还是post最终都会去send一个Message对象,然后把Runnale放在Message的Runnnable属性里面,接到Message再拿出来。附加在这里顺便点进去看看这个Message.obtain()方法。

    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

从obtain()的源代码中我们可以知道,它是静态方法,而且只有在spool = null 的情况下才会new出一个Message(),返回一个Message对象,如果在不为空的情况下,Message的对象都是从Message对象池里面拿的实例从而重复使用的,这也为了Android中的Message对象能够更好的回收。

3、handler消息处理的过程

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处理消息时首先回去检查Messgae的callback是否为null,不为null就直接handleCallback来处理消息,上面也写到了callback是一个Runnable对象,所以handleCallback就是启动线程。

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

其次会检查mCallback是否为null,不为null就调用mCallback的handleMessage方法处理消息,Callback是个接口,源码如下:

 public interface Callback{
    public boolean handleMessage(Message msg);
 }

Q:为什么要这样做?
A:我们平时使用handler最常见的用法其实就是派生一个Handler的子类然后重新它的handleMessage方法,而handler还有其他的创建形式:如1)Handler handler = new Handler(callback) 2)Handler handler = new Handler(looper),1)这种创建的方式就可以不派生子类而又可以创建Handler,而2)这种创建方式可以用于自定义一个与子线程关联的handler,(ps:handler的handleMessage属于哪个线程就看Handler绑定哪个线程,也就是看looper属于哪个线程)而消息处理的最后就会执行handleMessage方法。

4、Looper、MessageQueue的关系

ThreadLocal机制

ThreadLocal是一个线程内部的数据存储类,利用了一种大内存分成小内存的思想,每一个线程持有各自一块的小内存就可以达到一种互不相干的效果,与Map相似,它也通过set和get方法进行存值和取值,而ThreadLocal的内部会从各自线程中取出一个数组,然后从数组中根据当前ThreadLocal的索引去找出对应的value,而基于上面的原理,不同线程的数组是不一样,这样就可以在不同线程维护一套数据的副本。 ** 而Looper就是基于ThreadLocal机制去实现在线程中的存取,而且也正因如此,每条线程最多只能有一个Looper对象(这样也有利于handler绑定相应的线程)**

关于Looper

Looper内部包含MessageQueue并且MessageQueue的创建在Looper的构造方法中执行:

  private Looper(boolean quitAllowed){
      mQueue = new MessageQueue(quitAllowed);
      mThread = Thread.currentThread();
  }

Looper中的Looper.loop()方法是一个死循环,不断地从MessageQueue中取出消息,类似于生产者消费者模式,有消息就取出并处理消息,没有就堵塞队列。

Looper的创建: 1)可以手动的在子线程Loop.prepareLooper()方法创建Looper对象。 2)关于主线程中的Looper:在创建Activity之前,系统会通过AMS初始化一些信息,而在启动一个应用程序的时候,首先是会在AMS中,调用ActivityThread的main方法。

public static void main(String[] args) {
        SamplingProfilerIntegration.start();
        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);
        Environment.initForCurrentUser();
        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());
        Security.addProvider(new AndroidKeyStoreProvider());
        Process.setArgV0("<pre-initialized>");
	    //构建主线程中的MessageQueue和Looper对象
        Looper.prepareMainLooper();
        //构建ActivityThread对象
        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");
    }

而在这个过程中会调用 Looper.prepareMainLooper()和 Looper.loop()方法,去创建主线程的Looper以及在开启Looper的轮循,这也解释了为什么默认情况下在主线程创建handler不会报错,而在子线程创建handler就会报错。

5、HandlerThread

HandlerThread的基本总结

  • HandlerThread本质上是一个线程类,它继承了Thread;
  • HandlerThread有自己的内部Looper对象,可以进行looper循环;
  • 通过获取HandlerThread的looper对象传递给Handler对象,可以在handleMessage方法中执行异步任务。
  • 创建HandlerThread后必须先调用HandlerThread.start()方法,Thread会先调用run方法,创建Looper对象。

HandlerThread的使用步骤

1)创建实例

 HandlerThread handlerThread = new HandlerThread("asynctask's name");

2).启动HandlerThread线程

 //必须先开启线程
  handlerThread.start();

3)构建循环消息处理机制

   class ChildCallback implements Handler.Callback {
        @Override
        public boolean handleMessage(Message msg) {
            //在子线程中进行相应的异步请求

            //通知主线程去更新UI
            mUIHandler.sendMessage(msg1);
            return false;
        }
    }

4)构建异步handler

//子线程Handler
Handler childHandler = new Handler(handlerThread.getLooper(),new ChildCallback());

HandlerThread的退出方法

public boolean quit() {
    Looper looper = getLooper();
    if (looper != null) {
        looper.quit();
        return true;
    }
    return false;
}
public boolean quitSafely() {
    Looper looper = getLooper();
    if (looper != null) {
        looper.quitSafely();
        return true;
    }
    return false;
}
  • quit和quitSafely都是退出HandlerThread的消息循环。其分别调用Looper的quit和quitSafely方法。 quit方法会将消息队列中的所有消息移除(延迟消息和非延迟消息)。
  • quitSafely会将消息队列所有的延迟消息移除,非延迟消息派发出去让Handler去处理。quitSafely相比于quit方法安全之处在于清空消息之前会派发所有的非延迟消息

HandlerThread的应用场景

  • IntentService
  • 创建了两个Handler,一个用于更新UI线程的mainHandler和一个用于异步下载图片的asyncHandler。最终的结果是asyncHandler会每个隔1秒钟通过sendEmptyMessageDelayed方法去通知asyncCallback的回调函数handleMessage方法去下载图片并通知mainHandler去更新UI界面。