Handler全解

671 阅读12分钟

1. 一句话解释Handler

Handler的作用是发送并处理一个线程关联的Message或Runable

2.基本使用方法

在UI线程中创建Handler

private  Handler handler=new Handler(){
      @Override
      public void handleMessage(Message msg) {
          super.handleMessage(msg);
          int arg1=msg.arg1;
          String info= (String) msg.obj;
          if (msg.what==1){
              textView.setText(info+arg1);
          }
      }
  };

在子线程中发送消息

Message message=handler.obtainMessage();
message.what=1;
message.arg1=i;
message.obj="子线程发送的消息";
handler.sendMessage(message);

3.分析Handler、Looper、MessageQueue三个类

Looper

ActivityThread类开始分析, 在ActivityThread 中我们可以看到最开始学习java的一个入口函数,main(String[] args)。千万不要被 ActivityThread 的名字所迷惑,ActivityThread 并不是一个线程,它只是一个开启主线程的类。

public static void main(String[] args) {
        ....

        // 创建 Looper 和 MessageQueue 对象,用于处理主线程的消息
        Looper.prepareMainLooper();

        // 创建 ActivityThread 对象
        ActivityThread thread = new ActivityThread(); 

        // 建立 Binder 通道 (创建新线程)
        thread.attach(false);

             // 消息循环运行
        Looper.loop(); 
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

看下Looper中重要方法的具体实现:

public static void prepareMainLooper() {
  
    prepare(false); //该方法用来创建一个不允许退出的Looper,并set给线程相关的ThreadLocal
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

注意调用的是 prepare(false) 不允许退出,这是为什么呢?因为退出的话,主线程就结束了,app还执行个毛线啊!


private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

看到没prepareMainLooper()一顿骚操作,最终就是给sThreadLocal变量set一个Looper对象,那么看下Looper的构造方法。(这里其实应该解释一下Looper为什么这么执着于ThreadLocal这类,非要把自己set到ThreadLocal中!有空补充吧!)

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

看到没有,MessageQueue的对象是Looper的一个属性,也就是创建Looper对象的时候在其构造方法中也创建了MessageQueue的对象。 Looper这个类里面重要的两个属性变量:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

final MessageQueue mQueue;

sThreadLocal这个静态变量是来存放Looper的,这样保证创建Looper对象的线程都有自己唯一的Looper,一个线程只能使用一个并且是同一个Looper。MessageQueue对象是在Looper的构造方法里创建的,所以也保证了一个线程只对应一个MessageQueue对象

在Looper类中比较重要的两个方法一个是prepare(),另一个就是loop(),下面来看看loop()方法!

/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the loop.
 */
public static void loop() {
    final Looper me = myLooper();
  ......

    final MessageQueue queue = me.mQueue;
  ......

    // 死循环
    for (;;) {
       // 可能会被阻塞
        Message msg = queue.next();
        if (msg == null) {
            // msg 为 null 会立即退出循环,这也是退出循环的唯一方法。
            return;
        }
   ......

        try {
          //开始分发消息(这里的target代表什么在后面MessageQueue里面会讲到)
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
   ......
    }
}

loop 方法是一个死循环,他的工作就是不断的检查 MessageQueue 是否有可以处理的消息,如果有这将消息分发给 Handler 处理。需要注意ActivityThread类的main()方法中,loop()是在最后执行的哦!如果对这句话没什么感觉,那就好好看看下面的几个问题!

  • Looper.loop() 为什么要使用死循环? 在 CPU 看来操作系统线程(这里的定义可以参见《Java基础》多线程和线程同步 —— 进程与线程一节 ) 只不过是一段可以执行的代码,CPU 会使用 CFS 调度算法,保证每一个 task 都尽可能公平的享用 CPU 时间片。既然操作系统线程是一段可以执行的代码,当可执行的代码结束之后,线程生命周期也就终止,线程将会退出。但是对于 Android 主线程这种场景,我们绝对不希望执行一段时间之后主线程就自己停止掉,那么如何保证线程一直执行下去呢?简单的做法就是在线程中执行死循环,让线程一直工作下去不会停止退出。总的来说,在线程中使用死循环想要解决的问题就是防止线程自己退出。所以对于 Looper 而言,他的死循环就是希望不断的从 MessageQueue 中获取消息,而不希望线程线性执行之后就退出。
  • Android 的主线程为什么没有被 Looper 中的死循环卡死

首先 Android 所有的 UI 刷新和生命周期的回调都是由 Handler消息机制完成的,就是说 UI 刷新和生命周期的回调都是依赖 Looper 里面的死循环完成的,这样设计的目的上文已经阐述清楚。这篇文章里面贴了 AndroidTread 对于 Handler 的实现类 H 的源码(进入文章后搜索:内部类H的部分源码) 源码太长,我就不贴了。

其次Looper 不是一直拼命干活的傻小子,而是一个有活就干没活睡觉的老司机,所以主线程的死循环并不是一直占据着 CPU 的资源不释放,不会造成过度消耗资源的问题。这里涉及到了Linux pipe/epoll机制,简单说就是在主线程的 MessageQueue 没有消息时,便在 loop 的 queue.next() 中的 nativePollOnce() 方法里让线程进入休眠状态,此时主线程会释放CPU资源,直到下个消息到达或者有事务发生才会再次被唤醒。所以 Looper 里的死循环,没有一直空轮询着瞎忙,也不是进入阻塞状态占据着 CPU 不释放,而是进入了会释放资源的等待状态,等待着被唤醒

经过上面的讨论可以得知:

  1. Looper 中的死循环是 Android 主线程刷新 UI 和生命周期回调的基石。
  2. Looper 中的死循环会根据消息分别进入等待和唤醒状态,并不会一直持有资源,所以就不会有卡死的问题。

那么唤醒 Looper 的消息是从哪里来的呢?

  • 唤醒 Looper 的消息从何而来

目光回到 AndroidThread 类中的这几行代码

public static void main(String[] args) {
        ....

        // 创建ActivityThread对象
        ActivityThread thread = new ActivityThread(); 

        //建立Binder通道 (创建新线程)
        thread.attach(false);

        Looper.loop(); //消息循环运行
    }

在创建 ActivityThread 后会通过thread.attach(false)方法在 ActivityThread 中创建 Binder 的服务端用于接收系统服务AMS发送来的事件,然后通过 ActivityThread 的内部类 ApplicationThread 中 sendMessage 方法

......

public final void scheduleStopActivity(IBinder token, boolean showWindow,
                int configChanges) {
           sendMessage(
                showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE,
                token, 0, configChanges);
        }

        public final void scheduleWindowVisibility(IBinder token, boolean showWindow) {
            sendMessage(
                showWindow ? H.SHOW_WINDOW : H.HIDE_WINDOW,
                token);
        }

        public final void scheduleSleeping(IBinder token, boolean sleeping) {
            sendMessage(H.SLEEPING, token, sleeping ? 1 : 0);
        }

        public final void scheduleResumeActivity(IBinder token, int processState,
                boolean isForward, Bundle resumeArgs) {
            updateProcessState(processState, false);
            sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0);
......

将消息发送给 AndroidThread 的 Handler 实现内部类 H。从而完成了ActivityThread 到 UI 线程即主线程的切换,唤醒 Looper 进行 dispatchMessage 的动作。

唤醒的具体操作在下面「MessageQueue -> enqueueMessage -> nativeWake」

MessageQueue

MessageQueue 主要有两个操作:插入和读取。读取操作也会伴随着删除,插入和读取的方法分别对应的是:enquequeMessage() 和 next()MessageQueue 并不是像名字一样使用队列作为数据结构,而是使用单链表来维护消息。单链表在插入和删除上比较有优势。

  • 首先看下next()
Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            // 可见只有在调用 quit() 方法之后才会返回空
            return null;
        } 

   ......

        // 一个死循环 !
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            // 一个 native 方法,此方法在没有消息或者消息没有到执行时间的时候会让线程进入等待状态。
            // 有点类似于 Object.wait 但是 nativePollOnce 可以自定等待时间
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
   ......
                     if (!keep) {
                    synchronized (this) {
                      // 获取消息后从列表中移除
                        mIdleHandlers.remove(idler);
                    }
                }
        }
    }

最关键的是三点内容

  1. 死循环
  2. nativePollOnce()
  3. 获取到消息之后从列表中移除

nativePollOnce 是一个 native 方法,如果单列表中没有消息或者等待的时间没有到,那么当前线程将会被设置为 wait 等待状态 ,直到可以获取到下一个 Message 线程更详细的内容可以参见 StackOverflow 上关于 nativePollOnce的回答而这个死循环的目的就是不让 next方法退出,等待 nativePollOnce 的响应。等到获取到消息之后再将这个消息从消息列表中移除。

Looper的loop方法不已经死循环了吗,为什么MessageQueue的next方法还要再搞个死循环? 其实loop的死循环是为了无限读取MessageQueue的Message,next的死循环是为了读取延迟的Message消息,第一次执行循环可以计算出需要等待的时间,然后第二次循环的时候就等待这么多时间。

   nativePollOnce(ptr, nextPollTimeoutMillis)中的nextPollTimeoutMillis
   零,立即返回,没有阻塞;
   负数,一直阻塞,直到事件发生;
   正数,表示最多等待多久时间;

那么又有一个新的疑问,如果send一个延迟消息后,在延迟消息未执行前,再send一个正常的消息,那内部是如何保证既立即执行新消息又保证延迟消息到达时间后继续执行?

详情请参见Handler是怎么做到消息延时发送的 下面再抄一部分结论:

在 next 方法中如果头部的这个 Message 是有延迟而且延迟时间没到的(now < msg.when),会计算一下时间(保存为变量 nextPollTimeoutMillis), 然后在循环开始的时候判断如果这个 Message 有延迟,就调用nativePollOnce (ptr, nextPollTimeoutMillis)进行阻塞。nativePollOnce()的作用类似与 Object.wait(), 只不过是使用了 Native 的方法对这个线程精确时间的唤醒。

  1. postDelay()一个10秒钟的 Runnable A、消息进队,MessageQueue 调用nativePollOnce()阻塞,Looper 阻塞;
  2. 紧接着 post() 一个 Runnable B、消息进队,判断现在A时间还没到、正在阻塞,把B插入消息队列的头部(A的前面),然后调用 nativeWake()方法唤醒线程;
  3. MessageQueue.next() 方法被唤醒后,重新开始读取消息链表,第一个消息B无延时,直接返回给 Looper
  4. Looper 处理完这个消息再次调用 next() 方法,MessageQueue 继续读取消息链表,第二个消息A还没到时间,计算一下剩余时间(假如还剩 9秒)继续调用 nativePollOnce()阻塞;直到阻塞时间到或者下一次有Message 进队;
  • 再看下enqueueMessage()

enqueueMessage 方法的主要工作就是向单链表中插入数据,当线程处于等待状态则调用 nativeWake 唤醒线程,让 next 方法处理消息。

boolean enqueueMessage(Message msg, long when) {
    ......
            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

Handler

Handler.java中handler常用的构造方法 

public Handler(@Nullable Callback callback, boolean async) {
    ......
    //重点1
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
Looper.java文件中的静态方法

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

重点1解释:利用threadLocal线程相关性获取当前线程对应的Looper,也就是说通过这个构造方法实例化的Handler,在哪个线程中创建,他就获取的那个线程的Looper。

  • 消息是如何发送出去的
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);
    }

    public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageAtTime(msg, uptimeMillis);
    }

    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);
    }
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
      //重点2
      msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

重点2分析,Handler对象将自己的引用放入msg中,这样每个message在被回调的时候,就能找到对应的Handler,至此Handler将消息发送给MessageQueue对象后handler的任务就完成了

而后 enqueueMessage()中的nativeWake 将会唤醒等待的线程,MessageQueue的next()将会在Looper.loop()中将这条消息返回,Looper.loop()在收到这条消息之后最终会交由 Handler的dispatchMessage()处理

/** 
 * Looper 的 loop 方法
 */
public static void loop() {
  ......

    // 死循环
    for (;;) {
   ......
      // 开始分发消息  msg.target 指的就是发送消息的 Handler
       msg.target.dispatchMessage(msg);
   ......
    }
}
/**
     * Handle 的 dispatchMessage 方法
     */
    public void dispatchMessage(Message msg) {
       // 首先检查 msg 的 callback 是否为 null
        if (msg.callback != null) {
          // 不为 null 使用 msg 的 callback 处理消息
            handleCallback(msg);
        } else {
            // mCallback 是否为 null
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
          // 都没有指定则交由开发者重写的 handleMessage 处理
            handleMessage(msg);
        }
    }

mCallback指的是一个接口 , 可以使用 Handler handler = new Handler(Callback)的方式指定回调,这种方式可以由外部传递进来会回调方法,更加灵活。

* Callback interface you can use when instantiating a Handler to avoid
 * having to implement your own subclass of Handler.
 */
public interface Callback {
    /**
     * @param msg A {@link android.os.Message Message} object
     * @return True if no further handling is desired
     */
    public boolean handleMessage(Message msg);
}

4.知识延伸---在子线程中创建Handler

new Thread(new Runable(){
    @override
    public void run(){
    
    Looper.prepare();
    Handler h = new Handler(){
        @override
        handleMessage(Message m){
        
        Log.d("wxy", Thread.currentThread().getName());
        
        }
    }
    Log.d("wxy", "1");
    h.sendEmptyMessage(1);
    Log.d("wxy", "2");
    Looper.loop();
    Log.d("wxy", "3");
    }
    
}).start

问题:

1.loop方法执行完以后这个线程有没有被销毁?

2.Handler的handleMessage方法被回调时是执行在哪个线程?

看了日志就什么都清楚了,该程序执行后打印的日志:

1
2
Thread-2

  • Looper 如何退出

Looper 内部提供了两种退出的方法,分别是 quit、quitSafely。从本质上来讲 quit 调用后会立即退出 Looper,而 quitSafely 只是设定一个退出标记,等待消息队列中的已有消息处理完毕后,再退出。

Looper 退出后,通过 Handler 发送的消息会失败,这个时候 Handler send 方法会返回 false。在子线程中,如果手动为其创建了 Looper,那么在所有的逻辑完成后理应手动调用 quit 方法终止 Looper 内部的循环,否则这个子线程会一直处于等待状态,而退出 Looper 之后,子线程也就会随之终止,因此在子线程中使用 Looper,必须在恰当的时机终止它

/**
* Quits the looper.
*/
public void quit() {
    mQueue.quit(false);
}

/**
 * Quits the looper safely.
 */
public void quitSafely() {
    mQueue.quit(true);
}

如果是主线程开发者就退出不了,要是退出了,就麻烦大了。

public static void prepareMainLooper() {
  // fasle 不允许退出
    prepare(false);
 ....
}
  • 退出的本质

在 Looper.quit 的源码中可以清晰看到,本质上调用的是 MessageQueue 的 quite 方法。而在调用 MessageQueue.quite 之后 再次调用 MessageQueue.next()会返回 null

Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            // 可见只有在调用 quit() 方法之后才会返回空
            return null;
        } 

   ......

Looper.loop()在调用 queue.next()得的结果为 null 的时候会立即跳出死循环, 这也是退出死循环的唯一方式。

public static void loop() {
……
    for (;;) {
    Message msg = queue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return;
    }
……