Android线程通信之Handler

一、Handler简介

       我们知道,Android为了确保UI操作的线程安全,规定所有的UI操作都必须在主线程(UI线程)中执行,比如更新界面元素的显示,响应用户的点击事件等,但是有时候我们必须执行一些耗时的操作,比如网络请求或者读写文件等,当耗时操作完成后我们需要更新UI提示用户,在这种情况下,我们是不能将耗时操作直接在UI线程中执行的,因为这样会阻塞UI线程,导致UI线程无法及时更新界面元素或者响应用户,这样就会造成界面卡顿,如果阻塞时间过长,还会导致ANR,Android规定,Activity如果5秒之内无法响应用户的操作,或者BroadcastReceiver如果10秒之内还未执行完操作,就会出现ANR。也就是说,我们即不能在UI线程中执行耗时操作,又必须在UI线程中执行UI相关的操作,这样就必须让线程之间能够通信,我们开启一个子线程来执行耗时操作,在耗时操作完成后,子线程向UI线程发送一个消息通知UI线程,UI线程接受到消息后,执行相关的UI操作。在Android中,线程之间的通信采用的机制就是Handler机制,Handler能够方便的切换执行任务的线程。

二、Handler的基本用法

下面模拟开启一个子线程执行耗时任务后更新UI的操作,代码如下:

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.liunian.androidbasic.R;
import java.lang.ref.WeakReference;

public class HandlerTestActivity extends AppCompatActivity {
    public static String TAG = "HandlerTestActivity";
    TextView mShowTextView;
    Button mStartDownLoadButton;
    private Handler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_test);
        mHandler = new MyHandler(this);
        mShowTextView = (TextView) findViewById(R.id.show_text);
        mStartDownLoadButton = (Button) findViewById(R.id.start_download);
        mStartDownLoadButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new DownloadImageThread(mHandler).start();
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 在Activity销毁后移除Handler中未处理完的消息,避免内存泄露或者发生引用错误
        mHandler.removeMessages(DownloadImageThread.DOWNLOAD_IMAGE_FINISH);
    }

    // Handler类,声明为static,避免持有外部类的直接引用,防止内存泄露
    private static class MyHandler extends Handler {
        private WeakReference activityWeakReference; // 持有外部类的弱引用
        public MyHandler(HandlerTestActivity activity) {
            activityWeakReference = new WeakReference(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case DownloadImageThread.DOWNLOAD_IMAGE_FINISH: // 处理下载完成消息,更新UI
                    HandlerTestActivity handlerTestActivity = activityWeakReference.get();
                    if (handlerTestActivity != null) {
                        handlerTestActivity.mShowTextView.setText((String)msg.obj);
                    }
                    break;
            }
        }
    }

    // 模拟下载图片的子线程
    public static class DownloadImageThread extends Thread { // 将Thread声明为static,非静态内部类或者匿名类默认持有外部类对象的引用,容易造成内存泄露
        public static final int DOWNLOAD_IMAGE_FINISH = 1;
        private WeakReference handlerWeakReference;
        public DownloadImageThread(Handler handler) { // 采用弱引用的方式持有Activity中Handler对象的引用,避免内存泄露
            handlerWeakReference = new WeakReference(handler);
        }
        @Override
        public void run() {
            Log.i(TAG, "子线程开始下载图片");
            // 模拟下载图片
            for (int i=1; i<=100; i++) {
                try {
                    Thread.sleep(60);
                    Log.i(TAG, "下载进度:" + i + "%");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            Log.i(TAG, "子线程下载图片完成");
            // 发送消息通知UI线程下载完毕
            Message message = Message.obtain();
            message.what = DOWNLOAD_IMAGE_FINISH;
            message.obj = "图片下载完成";
            Handler handler = handlerWeakReference.get();
            if (handler != null) {
                handler.sendMessage(message);
            }
        }
    }
}

上面是使用Handler更新UI的典型用法,要注意两点:

1、不要使用匿名类或者非静态内部类,因为它们默认持有外部类对象的引用,这样当Activity调用onDestory后,如果Handler中的消息还没有处理完毕(消息对象持有Handler的引用),或者Thread没有执行完毕,Activity对象就会被内部类或匿名类的对象所引用,从而导致Activity对象无法被GC回收,产生内存泄露。所以我们要采用静态内部类,用弱引用的方式持有外部类的引用,避免内存泄露。

2、在onDestroy中调用Handler的removeMessages或者removeCallbacks方法,移除Handler中未处理完的消息,避免在 onDestroy 方法中释放了一些资源,此时Handler 执行到 handlerMessage 方法,但相关资源已经被释放,从而引起异常,或者我们可以给handlerMessage加上try catch来避免空指针异常。

匿名类或者非静态内部类使用Handler造成内存泄露的引用链如下:

主线程Thread对象->主线程Looper对象->Looper对象持有的MessageQueue对象->Message对象->Handler对象->Activity(或其他外部类)对象

只有Message从消息队列取出被消耗了,引用链才会断开。

三、Hander机制

      Android的消息机制主要是指Handler机制,Handler的运行需要底层的MessageQueue和Looper的支撑。MessageQueue即消息队列,它的内部存储了一组消息,以队列的形式对外提供插入和删除的操作;MessageQueue只是一个消息的存储单元,它并不会去处理消息,而Looper就填补了这个功能,其内部会以无限循环的方式去查找消息队列里面是否有消息,如果有的话就处理消息,否则就一直等待。我们知道,Handler创建的时候会采用当前线程的Looper来构造消息循环系统,我们在创建Handler时,必须为Handler指定Looper对象,后面该Handler发送的消息就都由Looper对象处理,如果没有指定Looper,默认使用当前线程的Looper对象,如果当前线程没有Looper对象,则创建Handler将会报错,线程默认是没有Looper的,之所以可以在UI线程中创建Handler不指定Looper,是因为UI线程在ActivityThread的入口函数中已经初始化了Looper。下面我们从源码入手,一起来分析一下Handler机制。

先来看一下Handler机制的图解:

Android线程通信之Handler_第1张图片

1、创建Handler

首先,我们开启一个子线程,并且在子线程中new 一个Handler对象,如下:

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

运行程序,发现直接闪退,报错内容如下:

07-21 17:31:52.275 31302-616/? E/AndroidRuntime: FATAL EXCEPTION: Thread-1307
                                                 Process: com.liunian.androidbasic, PID: 31302
                                                 java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
                                                     at android.os.Handler.(Handler.java:200)
                                                     at android.os.Handler.(Handler.java:114)
                                                     at com.liunian.androidbasic.handlertest.HandlerTestActivity$2.run(HandlerTestActivity.java:37)
                                                     at java.lang.Thread.run(Thread.java:818)

提示不能在没有调用Looper.prepare()的线程中创建Handler,为什么会这样呢?打开Handler源码:

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


    public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class 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());
            }
        }

        mLooper = Looper.myLooper(); // 调用Looper.myLooper(),获得当前线程的Looper对象
        if (mLooper == null) { // 如果没有Looper对象则会抛出异常
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue; // 存储当前线程Looper的MessageQueue对象
        mCallback = callback;
        mAsynchronous = async;
    }

很明显,通过Looper.myLooper()获得当前线程的Looper对象时,没有找到当前线程的Looper对象,所以创建Handler时就抛出了异常。Looper.myLooper()的代码:

    /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get(); // 从ThreadLocal对象中取出当前线程的Looper
    }

我们继续分析Looper.prepare()的代码:

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

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) { // 如果当前线程已经有了Looper对象,再次调用prepare会抛出异常
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed)); // 生成一个Looper对象,并与当前线程关联起来
    }

可以看到,Looper.prepare()会生成一个Looper对象,通过ThreadLocal与当前线程关联起来,ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,后面我们会分析它的原理,通过代码我们还可以知道,一个线程只能调用一次Looper.prepare(),多次调用会抛出异常。

2、ThreadLocal的工作原理

ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储后,只有在指定线程中可以获得到存储的数据,对于其他线程来说则无法获取。一般来说,当某些数据是以线程为作用域并且不同线程有不同的数据副本时,就可以考虑采用ThreadLocal。比如对于Looper来说,它的作用域就是线程,不同的线程可以拥有0个到1个Looper对象,通过ThreadLocal,我们可以很轻松的获得当前线程的Looper对象。下面看一下ThreadLocal的用法,以Looper为例:

a、创建ThreadLocal对象

    static final ThreadLocal sThreadLocal = new ThreadLocal();

b、设置当前线程的Looper对象

    sThreadLocal.set(new Looper(quitAllowed));

c、获得当前线程的Looper对象

    sThreadLocal.get()

代码非常简单,下面我们分析一下ThreadLocal的源码:

1、构造方法

    public ThreadLocal() {
    }

构造方法没有做任何处理

2、set方法

    public void set(T value) {
        Thread t = Thread.currentThread(); // 获得当前线程对象
        ThreadLocalMap map = getMap(t); // 获得当前线程对应的ThreadLocalMap
        if (map != null)
            map.set(this, value); // 如果当前线程有ThreadLocalMap对象,则直接将数据存储在map中
        else
            createMap(t, value); // 如果当前线程没有ThreadLocalMap对象,则创建一个ThreadLocalMap对象并将数据存储在map中
    }

通过上面的代码我们发现,ThreadLocal在存储数据时,会先获得当前线程的ThreadLocalMap对象,并且将数据存储在其中,存储是以ThreadLocal对象为key,以实际存储数据为value,不同的线程有不同的ThreadLocalMap对象,这样就确保了每个线程的数据不会相互干扰。

3、getMap方法

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals; // 返回线程对象中的threadLocals
    }

    public class Thread implements Runnable {

        ThreadLocal.ThreadLocalMap threadLocals = null; // 在线程类中有声明ThreadLocalMap对象
    }

从这里可以看到,getMap方法其实就是获得线程中声明的ThreadLocalMap对象,每一个线程都维护着自己的的ThreadLocalMap

4、createMap方法

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    
    ThreadLocalMap(ThreadLocal firstKey, Object firstValue) { // 已ThreadLocal对象作为key,以需要存储的值作为value保存数据
        table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }

createMap方法会创建一个ThreadLocalMap对象并且将值存储在其中

5、get方法

    public T get() {
        Thread t = Thread.currentThread(); // 获得当前线程
        ThreadLocalMap map = getMap(t); // 获得当前线程的ThreadLocalMap对象
        if (map != null) { // 从当前线程中的ThreadLocalMap对象中取出对应的值并返回
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

6、ThreadLocalMap存储数据的方式

在上面的代码中,我们知道ThreadLocal会将数据保存在当前线程的ThreadLocalMap对象中,保存时会以ThreadLocal对象作为key,以要保存的对象作为value,取出数据时,则会以ThreadLocal对象作为索引,找到当前的线程的ThreadLocalMap对象中保存的数据。

3、Looper.loop()方法

下面我们开启一个线程,在该线程下创建Looper和Handler,并且开启另外一个线程给其发送一个消息,代码如下:

    private HandlerTestThread mHandlerTestThread;
    private class HandlerTestThread extends Thread {
        public Handler mHandler;
        @Override
        public void run() {
            Log.i(TAG, "HandlerTestThread start"); // 线程开始
            Looper.prepare();
            mHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    Log.i(TAG, "handlerMessage"); // 处理消息
                }
            };
            Log.i(TAG, "HandlerTestThread end"); // 线程结束
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_test);

        mHandlerTestThread = new HandlerTestThread();
        mHandlerTestThread.start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000); // 休眠一秒在发送消息,确保HandlerTestThread线程已经启动
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Message message = Message.obtain();
                mHandlerTestThread.mHandler.sendMessage(message); // 发送一个消息
            }
        }).start();
    }

运行程序,打印日志如下:


07-26 14:30:42.043 324-398/? I/HandlerTestActivity: HandlerTestThread start
07-26 14:30:42.043 324-398/? I/HandlerTestActivity: HandlerTestThread end

通过日志可以发现,HandlerTestThread在启动之后立马就结束了,并没有处理另外一个子线程发送过来的消息,我们可以想一下,既然HandlerTestThread要处理消息,那么其必然不能结束,并且应该处于等待状态,一旦有消息过来立马处理,我们在调用Looper.prepare()时仅仅是为当前线程创建了一个Looper对象,而要让Looper正式开始工作,还需要调用Looper.loop()方法,如下:

    private class HandlerTestThread extends Thread {
        public Handler mHandler;
        @Override
        public void run() {
            Log.i(TAG, "HandlerTestThread start");
            Looper.prepare();
            mHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    Log.i(TAG, "handlerMessage");
                }
            };
            Looper.loop(); // 让Looper开始工作
            Log.i(TAG, "HandlerTestThread end");
        }
    }

打印日志如下:

07-26 14:43:04.890 2841-3133/? I/HandlerTestActivity: HandlerTestThread start
07-26 14:43:05.891 2841-3133/? I/HandlerTestActivity: handlerMessage

通过日志,可以看到Handler成功处理了发送过来的消息,并且线程没有结束,这是因为Looper.loop()会构造一个循环系统,随时监控消息队列中是否有消息需要处理,如果有则立马处理,如果没有,则会阻塞线程直到有消息需要处理;除非我们调用相应的退出循环的方法,线程才能结束。

下面我们一起分析一下Looper.loop()的源码:

    public static void loop() {
        final Looper me = myLooper(); // 获得当前线程的Looper对象
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue; // 获得消息队列

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        // 开启循环
        for (;;) {
            Message msg = queue.next(); // might block从队列中获得消息,如果没有消息将会阻塞
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what); // 开始处理消息的日志打印
            }

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            try {
                msg.target.dispatchMessage(msg); // 处理消息
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); // 处理消息完成的日志打印
            }

            // Make sure that during the course of dispatching the
            // 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(); // 回收消息对象,可以利用Message.obtain()重复利用消息对象
        }
    }

可以看到,loop方法会构造一个循环,然后从消息队列中取出消息并处理,处理完成后回收消息对象,如果消息队列中没有消息则MessageQueue.next会阻塞线程直到有新的消息需要处理。

这里有一个注意的点,我们可以看到在调用dispatchMessage处理消息前后都会打印日志,打印对象可以自行设置,Looper提供了如下方法:

    public void setMessageLogging(@Nullable Printer printer) {
        mLogging = printer;
    }

注意,这是一个public方法,也就是开发者也可以调用,那么我们是不是可以利用这一点来统计每一个Message的处理时间呢?获得消息处理前的系统时间和处理后的系统时间,两者相减就能知道本次消息的处理时间,这样,我们就能知道哪些消息的处理是比较耗时的操作,我们可以利用这一方式来检测UI线程的卡顿情况,主要原理就是利用Looper的Printer获得每一个消息的处理时间,如果发现某个消息处理太耗时了,那么肯定会造成主线程卡顿,卡顿检测框架BlockCanary就是这个原理,下面是它的核心代码:

class LooperMonitor implements Printer { // 自定义Looper的Printer,检测卡顿
  @Override
  public void println(String x) {
      if (!mPrintingStarted) {
          mStartTimestamp = System.currentTimeMillis(); // 记录当前时间为处理消息前时间
          mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
          mPrintingStarted = true;
          startDump(); // 收集主线程堆栈、CPU等信息,在子线程中处理
      } else {
          final long endTime = System.currentTimeMillis(); // // 记录当前时间为处理消息后时间
          mPrintingStarted = false;
          if (isBlock(endTime)) { // 判断处理当前消息是否耗时,如果耗时则显示通知
              notifyBlockEvent(endTime);
          }
          stopDump(); // 停止收集主线程堆栈、CPU等信息
      }
  }

  private boolean isBlock(long endTime) {
      return endTime - mStartTimestamp > mBlockThresholdMillis; // 处理消息时间是否大于某个阈值
  }

  // ignore other codes...
}


Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor); // 给主线程Looper对象设置自定义的Printer对象,mBlockCanaryCore.monitor是LooperMonitor类的对象

 

4、MessageQueue

顾名思义,MessageQueue的中文含义表示消息队列,它的作用是用来存储消息对象的,并且提供相应的插入、取出操作,首先我们看一下Handler的sendMessage方法:

    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 boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue; // mQueue是创建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, uptimeMillis); // 将消息插入到消息队列
    }

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { // 最终都是调用这个方法插入消息
        msg.target = this; // 让消息对象的target指向当前Handler对象,这里需要注意,Message就是在这里持有Handler的引用的
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis); // 将消息插入到消息队列中
    }

可以看到,Handler的sendMessage方法最终都是调用了MessageQueue的enqueueMessage方法,将消息插入到消息队列中。

1、MessageQueue.enqueueMessage

    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) { // 每个消息都需要有target对象,这个target就是最终处理消息的Handler对象
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) { // 加上控制锁,保证线程安全
            if (mQuitting) { // 如果消息循环已经退出,将不会将消息插入到队列中
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse(); // 标记该消息对象已经插入到了消息队列中
            msg.when = when;
            Message p = mMessages; // 消息队列中第一个要处理的消息(优先级最高的消息),消息队列是以链表的形式存储的
            boolean needWake;
            if (p == null || when == 0 || when < p.when) { // 如果消息队列为空,或者当前消息的优先级大于消息队列中优先级最高的消息,则将当前消息设置为优先级最高的消息
                // New head, wake up the event queue if blocked.
                msg.next = p; // 指向下一个消息
                mMessages = msg; // 最为优先级最高的消息
                needWake = mBlocked; // 如果线程被阻塞了,则需要将其唤醒
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) { // 找到消息队列中第一个优先级小于当前消息的消息
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                // 将消息插入按照优先级顺序插入到消息队列中
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) { // 判断线程是否需要重新被唤醒,如果需要则将线程唤醒
                nativeWake(mPtr);
            }
        }
        return true;
    }

从上面的代码中,可以看出,消息队列其实是以链表的形式来存储消息,每次插入消息时,都会判断队列中消息的优先级,并将消息插入到对应的位置,链表的优点是插入和删除数据效率高,只需要修改指针的引用关系即可。

2、MessageQueue.next()

在Looper中会调用MessageQueue.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; // 这个是和native方法交流的指针
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis); // 调用native方法阻塞线程nextPollTimeoutMillis毫秒,-1表示一直阻塞直到调用了唤醒方法

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis(); // 获得当前时间
                Message prevMsg = null;
                Message msg = mMessages; // 队列中优先级最高的消息
                if (msg != null && msg.target == null) { // 如果优先级最高的消息没有处理它的目标(Handler),则表明其是一个异步消息,我们通过Handler发送的消息都是同步消息,这个在Handler的enqueueMessage方法中可以看到,这里会一直循环知道找到一个同步消息
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) { // 如果当前时间小于消息开始执行的时间,则设置阻塞时间为开始执行的时间减去当前时间
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else { // 否则从队里中取出这条消息并返回
                        // Got a message.
                        mBlocked = false; // 设置阻塞状态为非阻塞,因为马上会调用return方法结束该方法
                        // 从队里中移除该消息并返回
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else { // 没有消息
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) { // 如果退出了Looper循环则直接返回空
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

从上面的代码中,可以看出,Message的next()方法也是一个循环,退出循环的条件是有符合条件的消息出现,或者Looper结束了工作,在循环中,首先会找到消息队列中优先级最高的消息,如果没有消息,则会一直阻塞线程直到有新的消息加入唤醒线程才会继续工作;如果有消息,则会将消息的开始执行时间和当前时间做比较,如果消息的开始执行时间小于或者等于当前时间,说明消息应该要执行了,则会将消息从队列中移除并返回该消息,loop循环中则会执行这条消息;如果消息的开始执行时间大于当前时间,则说明还没有到达消息开始执行的时间,这个时候就会用优先级最高的消息的开始执行时间减去当前时间,然后阻塞线程对应的时间。

MessageQueue的代码就分析到这里,MessageQueue是消息的存储单元,其内部是通过链表来实现的,并且提供了插入和取出消息的方法;在插入消息时,MessageQueue会计算消息的优先级(按照开始执行的时间),优先级越高的消息越靠前,在插入消息后,如果处理消息的线程阻塞,则会唤醒处理消息的线程。在取出消息时,MessageQueue会判断消息队列中是否有消息,如果没有消息则直接阻塞线程,如果有消息,则会取出当前优先级最高的消息,然后将消息的开始执行时间和当前时间做比较,如果消息的开始执行时间小于或者等于当前时间,说明消息应该要执行了,则会将消息从队列中移除并返回该消息,loop循环中则会执行这条消息;如果消息的开始执行时间大于当前时间,则说明还没有到达消息开始执行的时间,这个时候就会用优先级最高的消息的开始执行时间减去当前时间,然后阻塞线程对应的时间。这里要注意一点,MessageQueue插入消息可以在任何线程中执行,但是取出消息则只会在执行loop循环的线程中执行。

5、消息的处理

通过对Looper.loop的代码分析,我们知道,取出消息后,Looper是调用如下代码处理消息的:

  msg.target.dispatchMessage(msg); // 处理消息

而msg.target就是我们发送消息的Handler,这个在Handler的enqueueMessage中可以看到:

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this; // 让消息的target指向当前Handler对象
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

我们继续查看Handler的dispatchMessage方法:

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) { // 如果message设置了callback,则优先调用callback处理消息
            handleCallback(msg);
        } else {
            if (mCallback != null) { // 如果Handler设置了callback,则会调用Handler的callback处理消息,并且根据返回状态来决定是否要执行handleMessage方法
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg); // 调用Handler的handleMessage方法处理消息
        }
    }

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

    // 定义处理消息的接口
    public interface Callback {
        public boolean handleMessage(Message msg);
    }

可以看到,Handler在处理消息时,首先会判断当前Message对象有没有设置callback,如果有设置,这直接调用Message的callback的run方法来处理消息;如果消息没有设置callback,则首先会判断自己是否有设置Callback,如果有,则调用自身Callback的handlerMessage方法来处理消息,并且根据返回状态来觉得是否继续执行Handler的HandlerMessage方法。

Handler处理消息流程图:

Android线程通信之Handler_第2张图片

我们在调用Handler的post方法来发送消息时,其实就是构造了一个带callback的Message对象,然后插入到消息队列中,代码如下:

    mHandlerTestThread.mHandler.post(new Runnable() {
        @Override
        public void run() {

        }
    });


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

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r; // 构造一个带callback的Message对象
        return m;
    }

6、退出Looper循环

通过前面的分析我们知道,在线程中调用Looper.loop()开启当前线程的循环系统后,程序将会执行一个循环,如果不调用相应的退出方法,则无法退出循环,线程也无法结束,Looper提供了quit和quitSafely方法来退出循环:

    public void quit() {
        mQueue.quit(false);
    }

    public void quitSafely() {
        mQueue.quit(true);
    }

可以看到,这两个方法都是调用了MessageQueue的quit方法来退出循环:

    void quit(boolean safe) {
        if (!mQuitAllowed) { // 表示是否能够退出循环系统,只有主线程无法退出,其他线程都可以退出
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true; // 设置退出标识位为true

            if (safe) { // 如果是安全退出,则会将消息队列中能够执行的消息都执行完
                removeAllFutureMessagesLocked();
            } else { // 如果不是安全退出,则直接清空消息队列中的消息
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr); // 唤醒线程
        }
    }
    private void removeAllFutureMessagesLocked() { // 执行消息队列中当前时间可以执行的消息
        final long now = SystemClock.uptimeMillis(); // 获取系统当前时间
        Message p = mMessages; // 消息队列中优先级最高的消息
        if (p != null) {
            if (p.when > now) { // 如果优先级最高的消息的开始执行时间都大于当前时间,说明消息队列中当前时间没有可以执行的消息
                removeAllMessagesLocked(); // 清空消息队列
            } else {
                // 执行消息队列中当前时间可以执行的消息并清空消息队列
                Message n;
                for (;;) {
                    n = p.next;
                    if (n == null) {
                        return;
                    }
                    if (n.when > now) {
                        break;
                    }
                    p = n;
                }
                p.next = null;
                do {
                    p = n;
                    n = p.next;
                    p.recycleUnchecked();
                } while (n != null);
            }
        }
    }

    private void removeAllMessagesLocked() { // 清空消息队列
        Message p = mMessages;
        while (p != null) {
            Message n = p.next;
            p.recycleUnchecked();
            p = n;
        }
        mMessages = null;
    }

可以看到,quit和quitSafely的差别在于,quit会直接退出清空消息队列并退出Looper循环,而quitSafely则会先执行消息队列中当前时间可以执行的消息在退出Looper循环。

在MessageQueue的quit方法中,会将退出标识位mQuitting设置为true并唤醒线程,这个时候MessageQueue的next方法就会继续执行,而在next方法中,有如下代码:

                if (mQuitting) {
                    dispose();
                    return null;
                }

直接返回一个空对象,而在Looper的loop方法中,又有如下代码:

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) { // 如果消息返回为空,则直接退出循环
                // No message indicates that the message queue is quitting.
                return;
            }
            ...
        }

可以看到,如果MessageQueue.next()方法返回的消息对象为空,则loop方法会直接退出,这样就结束了循环系统。

四、主线程的循环系统

我们在主线程中使用Handler时,并没有调用Looper.prepare()方法来替主线程构造Looper对象,为什么主线程不用构造Looper对象呢?其实,主线程也是需要构造Looper对象并开启循环的,只不过这部分代码在主线程初始化的时候系统以及帮我们处理了,打开ActivityThread的源码,找到main方法:

    public static void main(String[] args) {
        ...
        Looper.prepareMainLooper(); // 构造主线程的Looper对象
        ...
        Looper.loop(); // 开启主线程的loop循环
        ...
    }

可以看到,主线程在初始化的时候就已经构造了循环系统,所有我们在主线程中使用Handler不需要再次构造循环系统,ActivityThread的main方法可以看做是一个应用的入口方法,其会初始化应用信息并开启主线程Looper循环,打开Looper.prepareMainLooper的源码:

    public static void prepareMainLooper() {
        prepare(false); // 这里传入的值是false,表示循环无法退出
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

主线程构造Looper对象时,也是调用的prepare(boolean)方法,在普通线程中,我们构造Looper对象使用的是Looper.prepare()方法:

    public static void prepare() {
        prepare(true); // 普通线程构造Looper对象传入的是true,表示可以退出循环系统
    }
    private static void prepare(boolean quitAllowed) { // 传入true表示可以退出循环系统,false表示无法退出
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

通过代码可以知道,主线程构造的Looper和普通线程构造的Looper的区别在于,主线程构造的Looper无法退出,除非应用被杀掉,而普通线程的Looper可以通过调用quit()或者quitSafely方法退出循环系统。从这点也可以看出,主线程其实就是一个Looper的死循环系统,不断的处理消息队列中的消息,主线程中所有进行的操作其实都是在Looper循环中执行的。比如界面的绘制、响应用户操作、Activity的生命周期回调方法执行等等。后面我们会详细分析Activity的启动过程和View的绘制过程。

五、Handler总结

1、Handler是Android中非常重要的消息通信机制,通过它可以方便的切换任务运行的线程;

2、Handler底层离不开Looper、MessageQueue、ThreadLocal的支撑,Looper用来构造循环系统,MessageQueue用来存储消息单元,ThreadLocal用来为每个线程创建独立的数据;

3、主线程运行是基于Handler机制,通过Looper构造一个无法退出的死循环,主线程中所有的操作都需要在Looper循环中执行,通过Handler,Android的主线程可以说是基于消息驱动的模式;

4、在平时的开发中,很多地方都需要使用Handler,比如网络请求、读写比较大的文件等等,为了确保主线程不被阻塞,这些耗时操作就必须在子线程中进行,而在任务完成后需要更新界面,而由于Android的UI线程保护机制,UI相关的操作必须在主线程中完成,所有这个时候就必须使用Handler了,所以一些常见的网络框架或者需要使用到线程切换的框架,都会用到Handler。

5、在创建Handler时,尽量不要用匿名类或者非静态内部类,因为它们默认持有外部类的引用,这样容易造成内存泄露。

六、拓展问题

上面我们对Handler进行了分析,知道了Handler的工作原理以及作用,这里有几个问题需要探讨一下:

1、主线程是一个无法退出的Looper循环,那么界面绘制、事件分发、Activity生命周期回调等操作是怎样交由给loop方法处理的?

根据前面了解的Handler机制,我们可以断定界面绘制、事件分发、Activity生命周期回调等所有需要主线程执行的操作最终都必须要封装成Message通过Handler交由给主线程的Looper对象的loop方法处理,那么这一流程是怎样的呢?

我们先来分析Activity的生命周期回调是怎样交由给主线程处理的,说到这个就需要我们了解Activity的启动流程了,我们通过startActivity启动一个Activity大致经历了如下过程:

Android线程通信之Handler_第3张图片

整个过程比较复杂,这里,我们只关心AMS处理完成后,通过Binder机制调用ApplicationThread的scheduleLaunchActivity之后的逻辑:

scheduleLaunchActivity是AMS通过Binder机制调用的,所有其运行在Binder线程池中


@Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
        ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
        CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
        int procState, Bundle state, PersistableBundle persistentState,
        List pendingResults, List pendingNewIntents,
        boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
    ...
    sendMessage(H.LAUNCH_ACTIVITY, r); // 发送一个消息给主线程处理
}
    private void sendMessage(int what, Object obj) {
        sendMessage(what, obj, 0, 0, false);
    }

    private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
        if (DEBUG_MESSAGES) Slog.v(
            TAG, "SCHEDULE " + what + " " + mH.codeToString(what)
            + ": " + arg1 + " / " + obj);
        Message msg = Message.obtain();
        msg.what = what;
        msg.obj = obj;
        msg.arg1 = arg1;
        msg.arg2 = arg2;
        if (async) {
            msg.setAsynchronous(true);
        }
        mH.sendMessage(msg); // 通过Handler发送一个消息
    }

来看一下mH的实现:

final H mH = new H(); // 创建H对象

private class H extends Handler {
    ...
    public void handleMessage(Message msg) {
        if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
        switch (msg.what) {
            case LAUNCH_ACTIVITY: { // 处理LAUNCH_ACTIVITY消息
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
                r.packageInfo = getPackageInfoNoCheck(
                        r.activityInfo.applicationInfo, r.compatInfo);
                handleLaunchActivity(r, null, "LAUNCH_ACTIVITY"); // 启动Activity
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            } break;
            ...
    }
}

通过之前分析Handler的代码我们知道,如果构造Handler时不传入Looper,Handler将会使用当前线程的Looper对象,这里,mH对象的构造就没有指定Looper,它作为ActivityThread的成员变量,会和ActivityThread对象一起构造,再来看一下ActivityThread的main方法:

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

        Looper.prepareMainLooper(); // 为主线程创建Looper对象

        ...

        ActivityThread thread = new ActivityThread(); // 创建ActivityThread对象
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        ...
        Looper.loop(); // 开启主线程Loop循环

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

可以看到,ActivityThread对象的创建是在主线程Looper对象创建之后,所以mH使用的Looper也就是主线程的Looper,也就是

handleLaunchActivity(r, null, "LAUNCH_ACTIVITY"); // 启动Activity

会在主线程中处理:

    private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
        ...
        Activity a = performLaunchActivity(r, customIntent); // 调用performLaunchActivity处理
        ...
    }
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");

        ...

        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader(); // 加载了apk的ClassLoader
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent); // 通过反射创建Activity实例
            ...
       
        } catch (Exception e) {
          ...
        }

        try {
                ...

                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState); // 调用Activity的onCreate方法
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state); // 调用Activity的onCreate方法
                }
                ...
            mActivities.put(r.token, r);

        } catch (SuperNotCalledException e) {
            throw e;

        } catch (Exception e) {
          ...
        }

        return activity;
    }

performLaunchActivity方法首先会通过反射创建Activity实例,然后调用其onCreate方法完成onCreate的生命周期回调,在Instrumentation中:

    public Activity newActivity(ClassLoader cl, String className,
            Intent intent)
            throws InstantiationException, IllegalAccessException,
            ClassNotFoundException {
        String pkg = intent != null && intent.getComponent() != null
                ? intent.getComponent().getPackageName() : null;
        return getFactory(pkg).instantiateActivity(cl, className, intent); // 创建Activity实例
    }

    public @NonNull Activity instantiateActivity(@NonNull ClassLoader cl, @NonNull String className,
            @Nullable Intent intent)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        return (Activity) cl.loadClass(className).newInstance(); // 通过反射创建类实例对象
    }
	
    public void callActivityOnCreate(Activity activity, Bundle icicle) {
        prePerformCreate(activity);
        activity.performCreate(icicle); // 调用Activity的performCreate方法
        postPerformCreate(activity);
    }
	

 继续看Activity的performCreate方法:

    final void performCreate(Bundle icicle) {
        performCreate(icicle, null);
    }

    final void performCreate(Bundle icicle, PersistableBundle persistentState) {
        mCanEnterPictureInPicture = true;
        restoreHasCurrentPermissionRequest(icicle);
        if (persistentState != null) {
            onCreate(icicle, persistentState); // 调用onCreate方法完成Activity创建生命周期回调
        } else {
            onCreate(icicle); // 调用onCreate方法完成Activity创建生命周期回调
        }
        writeEventLog(LOG_AM_ON_CREATE_CALLED, "performCreate");
        mActivityTransitionState.readState(icicle);

        mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
                com.android.internal.R.styleable.Window_windowNoDisplay, false);
        mFragments.dispatchActivityCreated();
        mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
    }

其他的生命周期函数的调用过程和onCreate是类似的,这里就不做分析了,通过上面的分析,我们知道了Activity的生命周期函数回调确实是在主线程中调用的,其实,了解了Activity的启动流程还是很有作用的,很多插件化框架比如VirtualApk就利用了Activity的启动流程,我们知道,正常情况下我们是不能启动未在AndroidManifest文件中注册的Activity的,我们安装一个应用时,其所有在AndroidManifest文件中注册的四大组件信息都会被PMS解析并保存,在启动Activity时,AMS会对要启动的Activity做校验,看该应用是否有权限启动对应的Activity,校验过程首先就是通过PMS查找对应的Activity,如果没有找到,则抛出错误信息给应用,应用就会抛出异常。了解Activity的启动流程后,我们就可以在这个流程上做一些文章来欺骗AMS启动未安装的Activity,大概有以下几个步骤:

1、注册代理Activity
首先,我们在主应用的AndroidManifest中注册代理Activity(Activity有四种启动模式,standard、singleTop、singleTask、singleInstance,其中标准模式每次都会创建新的Activity实例、其他三种模式都有可能要复用Activity实例,所以代理Activity standard模式只需要注册一个,其他三种模式需要注册多个,以免发生混乱);
2、欺骗AMS,绕过检测
应用启动Activity和AMS交互的流程是封装在Instrumentation的execStartActivity方法中,Instrumentation对象是在ActivityThread中创建的,并且所有的Activity都持有其引用,我们只需要在Application的onCreate方法中(这个时候还没有Activity创建)替换掉ActivityThread的Instrumentation对象,那么应用中所有Activity持有的Instrumentation都是我们替换过的Instrumentation,我们自定义一个Instrumentation类,重写其execStartActivity方法,在execStartActivity方法中获得要启动的Activity的启动模式,并根据启动模式找到合适的代理Activity,然后调用intent的setClassName将启动的Activity名称替换为代理Activity名,这样AMS检查时发现代理Activity是在主Apk中注册过的,也就能校验通过;
3、hook创建Activity的方法
Activity实例的创建封装在Instrumentation的newActivity方法中,我们重写自定义的Instrumentation的newActivity方法,将传入的Class(代理Activity类)替换成插件Activity类,这样在创建Activity时就会创建插件Activity的实例,于是真正启动的就是插件Activity了。Hook方式不仅可以Hook Instrumentation,还可以Hook AMS的代理等,总之只要是在这条线欺骗过了AMS即可。
 

插件化还是挺复杂的,上面只是介绍了其对AMS的欺骗过程,主要需要解决以下几个问题:

  • 需要加载未安装APK的代码;
  • 需要解读未安装APK中的资源;
  • 绕过AMS中的检查机制,启动未安装APK中的四大组件。

这里不做详细分析了。

上面我们分析了Activity的生命周期回调是在主线程中调用的,接下来分析界面绘制是如何通过Handler在主线程完成,我们知道,Activity的界面其实是交由给Window来处理的,而每一个Window都有对应着一个View和ViewRootImpl对象,其中View是控件树的根节点,界面的具体内容就是有控件树决定的,而ViewRootImpl可以看做是连接控件树和系统层(主要是WMS、Chrographer等)的桥梁,我们从Activity的setContentView开始分析:

    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID); // 其实是调用Window的setContentView
        initWindowDecorActionBar();
    }

Activity的setContentView其实是调用它对应的Window的setContentView,这里的Window唯一实现是PhoneWindow,其主要会生成Window对应的View(DecorView),并根据系统版本和Activity主题加载不同的布局到DecorView上,不管哪种布局,都会有一个id为"com.android.internal.R.id.content"的控件,我们通过setContentView设置的布局资源就是作为这个控件的子节点,现在,控件树已经构建完毕了,如图:

Android线程通信之Handler_第4张图片

控件树构建完毕后,还需要通过WindowManager的addView方法添加管理,WindowManager是一个抽象类,其实现类为WindowManagerImpl,WindowManagerImpl会调用WindowManagerGlobal的addView方法,下面看一下WindowManagerGlobal的addView方法:

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ...

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            ...

            root = new ViewRootImpl(view.getContext(), display); // 构造ViewRootImpl对象

            view.setLayoutParams(wparams); // 给View设置布局参数

            mViews.add(view); // 这是一个数组,里面存储着所有的窗口View
            mRoots.add(root); // 这个一个数组,里面存储着所有的窗口ViewRootImpl
            mParams.add(wparams); // 这个一个数组,里面存储着所有的窗口的布局参数

            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView); // 这里调用了ViewRootImpl的setView方法,将View和ViewRootImpl关联起来
            } catch (RuntimeException e) {
                ...
            }
        }
    }

WindowManagerGlobal的addView主要做了下面几件事:

  • 给窗口创建ViewRootImpl对象;
  • 给View设置布局参数
  • 将ViewRootImp、View、布局参数添加到数组中存储
  • 调用ViewRootImpl的setView方法,将View和ViewRootImpl绑定到一起,并通过Binder机制调用WMS添加窗口信息

来看一下ViewRootImpl的setView的实现:

  public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
                ...
                requestLayout(); // 调用requestLayout方法触发控件树测量、布局、绘制
                ...
                try {
                ...
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel); // 通过Binder机制将窗口信息传递给WMS
                } 
    }

来看看requestLayout的实现:

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) { // 一个VSync信号时间内只需要调用一次绘制流程
            checkThread(); // 检查线程是否为原始线程
            mLayoutRequested = true;
            scheduleTraversals(); // 通过Chrographer向系统注册下一个VSync信号到来后调用doTraversal方法,执行绘制流程
        }
    }

requestLayout主要做了两件事:

  • 检查线程是否为原始线程,这主要是为了确保UI操作的安全性,避免多个线程同时操作UI,发生混乱;
  • 通过Chrographer向系统注册下一个VSync信号到来后调用doTraversal方法,执行绘制流程

先看看checkThread方法,我们经常说的不能在子线程中进行UI操作就是这个函数限制的:

    void checkThread() {
        if (mThread != Thread.currentThread()) { // 判断当前线程是否等于mThread,如果不是则抛出异常
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

mThread是ViewRootImpl的成员变量,是在ViewRootImpl的构造函数中赋值的:

    public ViewRootImpl(Context context, Display display) {
        ...
        mThread = Thread.currentThread();
        ...
    }

也就是说,进行UI操作的线程必须要和创建ViewRootImpl的线程一致。那么Activity的ViewRootImpl是在什么时候创建的呢?这个其实是在ActivityThread的handleResumeActivity方法中,和handleLaunchActivity方法类似,handleResumeActivity也是AMS通过Binder机制回调的(在performLaunchActivity中也会调用),对应着Activity的Resume生命周期:

    final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ...

        // TODO Push resumeArgs into the activity for consideration
        r = performResumeActivity(token, clearHide, reason); // 调用Activity的onResume方法

        if (r != null) {
            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();
                
                ...

                if (a.mVisibleFromClient && !a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l); // 注意这里,调用WM的addView方法添加View
                }

            } 

        }
        ...
    }

通过源码可以看到,handleResumeActivity首先是调用了Activity的onResume方法,然后调用WindowManager的addView方法添加DecorView,之前我们分析过,WindowManager的addView方法最终会调用WindowManagerGlobal的addView方法,在这个方法中,我们会创建ViewRootImpl,并将其和View绑定,所以ViewRootImpl的创建线程应该和handleResumeActivity的调用线程一致,而前面我们分析过,handleResumeActivity是AMS调用ApplicationThread的方法后,通过Handler向主线程发送消息触发调用的,所以Activity的ViewRootImpl的是在主线程中创建的,这就解释了我们常说的只能在主线程中进行UI操作了。

再来看一下scheduleTraversals()方法,前面我们说过这个方法的作用是通过Chrographer向系统注册下一个VSync信号到来后调用doTraversal方法,源码如下:

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); // 通过Chrographer向系统注册下一个VSync信号到来后调用mTraversalRunnable
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

主要是调用Choreographer的postCallback向系统注册监听下一个Vsync,我们知道,Android的显示是交由给SurfaceFlinger控制的,SurfaceFlinger每隔16.6ms会产生一个VSync信号,表示屏幕刷新信号,我们的应用只有向系统注册了监听下一个VSync信号,才会收到下一个VSync,Choreographer就是负责获取Vsync同步信号并控制App线程(主要是主线程绘图)完成图像绘制的类,Android界面的绘制、属性动画等都离不开Choreographer,并且我们可以利用Choreographer监控App的性能,观察App是否卡顿,大致就是通过Choreographer注册监听VSync信号,看两次回调的间隔时间是否超时,如果时,则说明发生了卡顿。这里不分析Choreographer的源码了,只说结果,mTraversalRunnable会在创建ViewRootImpl的线程中调用,来看一下mTraversalRunnable的实现:

    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            
            ...

            performTraversals();

            ...
        }
    }


    private void performTraversals() {
        ...

        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); // 测量流程
        
        ...

        performLayout(lp, mWidth, mHeight); // 布局流程

        ...
        
        performDraw(); // 绘制流程
    }

最终会依次执行控件树的测量、布局、绘制三大流程,通过这一系列步骤,控件树的内容现在绘制到了窗口对应的Surface上,然后经过WMS交由给SurfaceFlinger显示。

上面我们通过源码分析了Activity的生命周期回调、UI绘制是如何交由给主线程处理的,下面我们分析另外一个问题:UI操作必须要在主线程中执行吗?

2、UI操作是必须在主线程中执行吗?

如果对上面的ViewRootImpl的分析理解了,这个问题应该是有答案了,首先,我们来看一下在子线程中进行UI操作抛出的异常信息:

    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7967)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1365)
        at android.view.View.requestLayout(View.java:22280)
        ...

这个异常大家应该都很熟悉了,意思是说只能在创建控件树的线程中操作控件,报错的位置是ViewRootImpl的checkThread方法:

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

前面我们说到过,ViewRootImpl是在WindowManagerGlobal的addView方法中创建的,考虑一下,如果我们在子线程中调用addView,那么ViewRootImpl也会在子线程中创建,其成员变量mThread记录的就是子线程,checkThread检查的时候判断的就是当前线程是否是创建ViewRootImpl的子线程,这种情况下,在主线程中进行UI操作反而会抛出异常。上面的逻辑是我们根据源码的猜想,下面我们写一个实际的例子来验证一下,我们新建一个Demo,在子线程中通过WindowManager添加View,然后尝试在子线程中更新它看是否会抛出异常:

public class MainActivity extends AppCompatActivity {
    private HandlerThread mHandlerThread;
    private Handler mHandler;
    private CustomView mRootView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
    }

    private void init() {
        // 打印主线程id
        Log.i("liunianprint:", "main thread id:" + Thread.currentThread().getId());
        // 创建子线程
        mHandlerThread = new HandlerThread("");
        // 启动子线程
        mHandlerThread.start();
        // 创建WindowHandler对象,并且传入子线程的Looper
        mHandler = new WindowHandler(mHandlerThread.getLooper());
        // 打印子线程id
        Log.i("liunianprint:", "HandlerThread id:" + mHandlerThread.getId());

        findViewById(R.id.add_window).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Message message = Message.obtain();
                message.what = WindowHandler.ADD_WINDOW;
                mHandler.sendMessage(message);
            }
        });

        findViewById(R.id.delete_window).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Message message = Message.obtain();
                message.what = WindowHandler.REMOVE_WINDOW;
                mHandler.sendMessage(message);
            }
        });

        findViewById(R.id.update_window_view).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Message message = Message.obtain();
                message.what = WindowHandler.UPDATE_WINDOW;
                mHandler.sendMessage(message);
            }
        });
    }

    private void updateViewFromWindow() {
        if (mRootView != null) {
            // 打印当前线程id
            Log.i("liunianprint:", "updateViewFromWindow thread id:" + Thread.currentThread().getId());
            mRootView.setText(System.currentTimeMillis() + "");
        }
    }
    private void addWindowView() {
        removeViewFromWindow();
        // 打印当前线程id
        Log.i("liunianprint:", "addWindowView thread id:" + Thread.currentThread().getId());
        Context context = getApplicationContext();
        // 通过WM添加View
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        WindowManager.LayoutParams lp;
        lp = new WindowManager.LayoutParams();
        lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
        lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
        lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        lp.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
        mRootView = new CustomView(context);
        ((CustomView) mRootView).setText("Button");
        mRootView.setPadding(100, 100, 100, 100);
        mRootView.setBackgroundColor(context.getResources().getColor(android.R.color.holo_blue_bright));

        mRootView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 打印处理点击事件的线程id
                Log.i("liunianprint:", "click thread id:" + Thread.currentThread().getId());
                Toast.makeText(MainActivity.this, "Click Me!", Toast.LENGTH_SHORT).show();

            }
        });
        wm.addView(mRootView, lp);
    }

    private void removeViewFromWindow() {
        if (null == mRootView) {
            return;
        }
        Log.i("liunianprint:", "removeViewFromWindow thread id:" + Thread.currentThread().getId());
        Context context = getApplicationContext();
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

        if (mRootView.getParent() != null) {
            wm.removeView(mRootView);
        }
        mRootView = null;
    }

    @Override
    protected void onDestroy() {
        mHandler.removeCallbacksAndMessages(null);
        mHandlerThread.stop();
        removeViewFromWindow();
        super.onDestroy();
    }


    /**
     * 定义操作Window的Handler类
     */
    private class WindowHandler extends Handler {
        public final static int ADD_WINDOW = 1;
        public final static int REMOVE_WINDOW = 2;
        public final static int UPDATE_WINDOW = 3;

        public WindowHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            if (msg != null) {
                switch (msg.what) {
                    case ADD_WINDOW:
                        addWindowView();
                        break;
                    case REMOVE_WINDOW:
                        removeViewFromWindow();
                        break;
                    case UPDATE_WINDOW:
                        updateViewFromWindow();
                        break;
                }
            }
        }
    }
}

在上面的代码中,我们将添加Window、更新Window上的View、删除Window的操作都放在子线程中执行,并且打印出线程id,运行效果如下:

Android线程通信之Handler_第5张图片

可以看到,在子线程中能够通过WindowManager添加、更新、删除Window, 打印日志如下:

2019-05-08 16:19:34.561 15049-15049/? I/liunianprint:: main thread id:2
2019-05-08 16:19:34.562 15049-15049/? I/liunianprint:: HandlerThread id:4721
2019-05-08 16:19:37.241 15049-15069/com.example.myapplication I/liunianprint:: addWindowView thread id:4721
2019-05-08 16:19:37.290 15049-15069/com.example.myapplication I/liunianprint:: draw thread id:4721
2019-05-08 16:19:38.862 15049-15069/com.example.myapplication I/liunianprint:: updateViewFromWindow thread id:4721
2019-05-08 16:19:38.893 15049-15069/com.example.myapplication I/liunianprint:: draw thread id:4721
2019-05-08 16:19:38.904 15049-15069/com.example.myapplication I/liunianprint:: draw thread id:4721
2019-05-08 16:19:40.105 15049-15069/com.example.myapplication I/liunianprint:: click thread id:4721
2019-05-08 16:19:42.277 15049-15069/com.example.myapplication I/liunianprint:: removeViewFromWindow thread id:4721

从日志中我们可以看出,添加、更新、删除Window都是在子线程中进行的,并且点击事件的回调处理也是在点击事件中完成的,我们再来确定一下控件的绘制线程,我们上面添加的View是自定义的View,名称为CustomView,我重写了它的onDraw方法,在其中打印绘制线程:

public class CustomView extends AppCompatButton {
	
	...

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.i("liunianprint:", "draw thread id:" + Thread.currentThread().getId());
    }
}

通过上面的日志,我们可以确定控件的绘制也是在子线程中执行的,和创建ViewRootImpl的线程保持一致,那么,假设我们现在想在主线程中更新窗口的UI,能操作成功吗?修改代码如下:

        findViewById(R.id.update_window_view).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
//                Message message = Message.obtain();
//                message.what = WindowHandler.UPDATE_WINDOW;
//                mHandler.sendMessage(message);
                updateViewFromWindow(); // 直接在主线程中更新窗口UI
            }
        });

点击更新按钮,直接抛出异常:

    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7967)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1365)
        at android.view.View.requestLayout(View.java:22280)
        ...

原因也很简单,不能在创建ViewRootImpl线程以外的线程进行UI操作,经过上面的分析,我们得出了结论:可以在子线程中更新UI,只要更新UI的子线程是创建ViewRootImpl的线程,我们经常说的只能在UI线程中进行UI操作,是因为Activity所对应的ViewRootImpl对象是在主线程中创建的,并且这个流程是系统定制好的,我们无法修改。

你可能感兴趣的:(android)