Handler系列---源码分析

Handler系列—源码分析

前言

我们在上篇文章Handler系列—基本使用中讲解了 Handler 的使用以及解决了在使用 Handler 中内存泄漏的问题。

对于我们程序员来说,面对一个知识点,我们不仅要知其然,还要知其所以然。

所以本篇文章主要讲下 Handler 的源码。

分析源码之前先来看下 Handler 的运行机制

一、Handler 的运行机制

实际上 Android 中的消息机制主要指的就是 Handler 的运行机制。

Handler 的运行需要以下对象的支持:

  • Message
  • MessageQueue
  • Loop
  • Handler

1.1 Message

来看下 Android Api 中关于 Message 的定义:

可以看到,官方给出的定义是:

定义和包含了一些能够发送给 Handler 的描述和任意数据的对象,这个对象包含两个额外的 int 类型的字段和一些额外的对象数据以便能够适应多种场景
虽然 Message 的构造方法是公共的,但是最好是通过 Message.obtain()方法或者Handler.obtainMessage()方法来获取 Message 对象

笼统的来说,Message 其实就是 Android 中在不同线程中通信的载体,Message 对象中存储了我们不同线程间通信的数据。

1.2 MessageQueue

看下官方的介绍:

保存了一个由 Looper 管理的消息列表,消息并不是直接被添加到 MessageQueue 中的,而是通过 Handler 和 Looper 的协作。
你可以通过 Looper 的myQueue() 方法获得 MessageQueue 对象。

可以看到,MessageQueue 保存了线程间通信消息列表,他由 Handler 和 Looper 共同管理。本身是一种数据结构,存储了线程间通讯的信息。

1.3 Looper

官方的介绍:

Looper这个类通常为线程(Thread)用于运行一个消息循环,线程默认是没有和 Looper 相关的消息循环,通过执行 Looper.prepare() 去运行消息循环,然后执行 Looper.loop() 方法去处理 Message ,直到当前的消息循环停止。

从官网的介绍我们可以看出来,Looper 通常是和线程相关的,用于在线程上开启一个消息循环(MessageQueue),然后不断的处理消息循环中的消息,一直到消息循环终止。

其实 Looper 主要有的两大功能:

  1. 消息获取 :通过循环 MessageQueue ,不断的取出里面的 Message,如果 MessageQueue中 没有 Message ,则阻塞。
  2. 消息分发:将取出的 Message 分发给 Handler 处理

1.4 Handler

依旧来看下官方描述:

一个 Handler 允许你发送和处理在线程 MessageQueue 里面的 Message 或者 Runnable 对象。每个 Handler 实例都与单个线程和该线程的消息队列相关联。当你创建一个新的 Handler 对象时,它将和创建它的线程和该线程的消息队列相绑定,从此刻开始,它将发送消息和 Runnable 对象到和它绑定的 MessageQueue,并在它们从 MessageQueue 中出来时处理它们。

官方已经说得很清楚了,Handler 对象可以发送和处理 MessageQueue 中的消息对象或者 Runnable 对象。并且一个 Handler 创建的时候,是要和当前所处的线程以及该线程中的 MessageQueue 相绑定的。也就是说一个 Handler 对象只能绑定一个线程。

1.5 Message、MessageQueue、Looper、Handler的对象关系

  1. MessageQueue 中存放的是 Message ,一个 MessageQueue 中有可以多个 Message,或者没有 Message(此时阻塞)
  2. 一个线程中有一个 Looper ,并且只有一个 MessageQueue ,Looper 去循环 MessageQueue,有消息就发给 Handler 处理。
  3. 一个 Handler 只能绑定一个 Looper,但是一个 Looper 可以和多个 Handler 相关联

好了,Handler 源码分析里面的几个关键对象已经大体上说了一遍,下面就一起来看看源码,看看 Handler 是怎么工作的吧。

二、Handler 源码分析

2.1 在子线程中创建 Handler

如果我们需要在子线程中创建 Handler ,需要如下代码:

new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();
        Handler threadHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };
        Message message = Message.obtain();
        message.what = 1;
        threadHandler.sendMessage(message);
        Looper.loop();
    }
}).start();

主要分为三步
1. 创建 Looper: Looper.prepare();
2. 创建 Handler:Handler threadHandler = new Handler()
3. Handler 发送消息
4. 开启 Looper 循环

创建 Looper

我们先从Looper.prepare()这里开始,prepare() 方法的源码如下:

public static void prepare() {
    prepare(true);
}
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));
}

prepare 方法的参数很明显,是否允许退出。

prepare 方法首先通过 sThreadLocal.get() 获取 Looper,如果当前线程中的 Looper 不为空,那么抛出异常,提示“每个线程只能有一个 Looper”

那么 sThreadLocal 是什么呢?我们看下定义:

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

sThreadLocal 实际上就是 一个指定类型的 ThreadLocal 对象,那么 ThreadLocal 的作用是什么呢?

实际上 ThreadLocal 可以在不同的线程之中互不干扰地存储并提供数据,通过 ThreadLocal 可以轻松的存储、获取每个线程的 Looper,这样就保证了 一个线程只能有一个 Looper 对象。

这里不做过多的讲解,想了解更多的话,请去这里

继续看关键代码:

sThreadLocal.set(new Looper(quitAllowed));

把通过 new Looper(quitAllowed) 的方式得到的 Looper 对象,放入到 ThreadLocal 的实例 sThreadLocal.set 中,继续看下 Looper 的构造方法:

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

在构造方法里面新建了一个 MessageQueue 对象,然后把给当前的线程赋值给 mThread。最终返回一个 Looper 对象。
再来看下sThreadLocal.set(new Looper(quitAllowed));方法:

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

通过getMap() 方法得到当前线程的 ThreadLocalMap 对象 map,顾名思义, ThreadLocalMap 存储是一个关于 ThreadLocal 的 Map,里面的

  • key 是 ThreadLocal 对象,
  • value 是 Object 对象。

接着判断 map 对象是否为空:

  • 如果不为空,那么把传进来的 Looper 对象作为 value,把当前的 ThreadLocal 作为 key,存储到当前线程的 ThreadLocalMap 对象中,
  • 如果为空,则新建一个 ThreadLocalMap 对象,把传进来的 Looper 对象作为 value,把当前的 ThreadLocal 作为 key,存储到当前线程的 ThreadLocalMap 对象中。

到此我们知道,我们新建了 Looper 对象后,是把当前线程的 ThreadLocal 和 Looper 对象建立映射关系,存储到 ThreadLocalMap 中的。

接下来看下第二步:创建 Handler 对象

创建 Handler 对象

分析代码:

Handler threadHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };

首先看到,我们 new 出了一个 Handler 对象。来看下 Handler 的构造方法:

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

很简单,无参构造方法调用有参构造方法,接下来看看有参构造方法:

public Handler(Callback callback, boolean async) {
    // 检查当前的类是否是匿名内部类、成员内部类或者是本地类,如果是的话,就打印 log 可能引起内存泄漏,鼠标悬浮的时候也会提示。
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }
    // 创建 Looper 对象
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    // 获取 MessageQueue
    mQueue = mLooper.mQueue;
    // 参数赋值
    mCallback = callback;
    // 参数赋值
    mAsynchronous = async;
}

首先要检查当前的类名,如果是匿名内部类、成员内部类或者是本地类,则给出可能有内存泄漏的提示

我们先看下这行代码:

mLooper = Looper.myLooper();

这行代码的作用主要是获取当前线程的 mLooper 对象。

我们去Looper.myMooper() 里面看下:

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

sThreadLocal 是不是很熟悉,前面在创建 Looper 的时候,已经把 Looper 和 ThreadLocal 作为映射关联起来,存储到 sThreadLocal 中了。

在这里调用了 sThreadLocal.get 的 get() 方法返回了创建 Looper 时存入 sThreadLocal 中的 Looper 对象。

再来具体看下 sThreadLocal.get()

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

可以看到首先通过 Thread.currentThread() 拿到了当前的线程对象 t,然后根据 getMap(t) 拿到 ThreadLocalMap 对象,

getMap(t) 源码:

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

就是返回当前线程的 t.threadLocals;

然后我们再通过 map 的 getEntry 方法拿到 ThreadLocalMap 的 Entry对象。那么这里的 ThreadLocalMap.Entry 里面存储的是什么呢?

Entry(ThreadLocal<?> k, Object v) {
    super(k);
    value = v;
}

对应我们这里就是:

  • key 存储的是当前线程的 ThreadLocal 对象
  • value 存储的 Object 是就是我们的 Looper 对象。

最后来说下 sThreadLocal.get() 的工作:

  1. 当在 ThreadLocalMap 不为空的时候,就从中取出对应当前 ThreadLocal 的 Looper 对象
  2. 当在 ThreadLocalMap 为空的时候,执行 setInitialValue() 方法去创建一个新的 ThreadLocalMap 赋值给 threadLocals,并且返回给 Looper 对象。

继续往下看:

if (mLooper == null) {
    throw new RuntimeException(
        "Can't create handler inside thread that has not called Looper.prepare()");
}

从这行代码可以看出,我们在 new 出一个 Handler 对象的时候,必须先有 Looper 对象,否则就会抛出异常。所以这就是在子线程中使用 Handler 的时候,必须先执行prepare、创建Handler、loop的原因了。

下面的就比较简单了:

// 把创建 Looper 时创建的 MessageQueue 赋值给 mQueue
mQueue = mLooper.mQueue;
// 把传入的callback 赋值给 mCallback
mCallback = callback;
// 把传入的async 赋值给 mAsynchronous
mAsynchronous = async;

Handler 发送消息

Message message = Message.obtain();
message.what = 1;
threadHandler.sendMessage(message);

首先我们创建了一个 Message 对象,然后通过 Handler 的 sendMessage 方法把 Message 发送出去。

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;
    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;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

我们看到 sendMessage 方法最终调用了 Handler 的 enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) 方法。

在 Handler 的 enqueueMessage 方法中:

通过 msg.target = this; 把 Message 的 target 属性设置为当前的 Handler,这样就把该消息和要处理该消息的 Handler 做了绑定。

然后调用 MessageQueue 的 enqueueMessage(msg, uptimeMillis) 方法将消息添加到 MessageQueue 队列中。

至此,我们只是把消息加入到了队列中,但是还不能通过 Handler 的 handleMessage(Message msg) 方法处理,因为我们只是把消息加入到了 MessageQueue 中,MessageQueue 只是一个队列,要用 Looper 不断的去从 MessageQueue 中取消息给 Handler 才能处理消息。

接下来就看最后一步,开启 Looper 循环。

开启 Looper 循环

对应代码:

Looper.loop();

我们看下源码:

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

    // 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 slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

        final long traceTag = me.mTraceTag;
        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }
        final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        final long end;
        try {
            msg.target.dispatchMessage(msg);
            end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        if (slowDispatchThresholdMs > 0) {
            final long time = end - start;
            if (time > slowDispatchThresholdMs) {
                Slog.w(TAG, "Dispatch took " + time + "ms on "
                        + Thread.currentThread().getName() + ", h=" +
                        msg.target + " cb=" + msg.callback + " msg=" + msg.what);
            }
        }

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

首先通过 myLooper() 拿到 Looper 对象 me,然后再根据 me 拿到 MessageQueue 对象 queue。

然后我们看到一个无限循环

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

这个无限循环用于不断的从 MessageQueue 中取消息,如果 MessageQueue 中没有消息,那么就阻塞。

再来看下关键代码:

msg.target.dispatchMessage(msg);

msg 是从 MessageQueue 里面取出的消息。那我 msg.target 是什么呢?在第三步,我们已经在Handler 的 enqueueMessage 方法中将 Handler 对象赋值给 Message 的 target 属性了。所以这里的 msg.target 指代的就是 Handler 对象。

public final class Message implements Parcelable {
    、、、//省略其他代码
    Handler target;
    、、、//省略其他代码
}

然后调用了调用了 msg.target(也就是 Handler 对象) 的 dispatchMessage() 方法。
来看下源码:

/**
 * Handle system messages here.
 */
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

一眼就看到了最后的 handleMessage(msg); 方法,是不是有点似曾相识呢?

我们再来看下我们最开始的在子线程中使用 Handler 的代码:

new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();
        Handler threadHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };
        Message message = Message.obtain();
        message.what = 1;
        threadHandler.sendMessage(message);
        Looper.loop();
    }
}).start();

看到我们重写了 Handler 的 handleMessage 方法,也就是上面我们分析的 dispatchMessage 中调用的 handleMessage 方法,这样我们就把所有的流程关联了起来。

到此,相信已经知道为什么我们通过 Handler 发送消息,然后再通过 Handler 处理了。

2.2 在主线程中使用 Handler

我们在主线程使用 Handler 的时候,并没有执行 Looper.prepare()、Looper.loop() 方法,比如下面:

/**
 * 主线程中的Handler
 */
private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
    }
};

这是为什么呢?

前面我们说过,Android 是单线程模型,其线程为 ActivityThread,主程序的入口是 main(); main() 方法源码如下:

public static void main(String[] args) {
    // 省略无关代码

    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false);

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

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();

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

可以看到,虽然我们没有主动的执行 prepare、loop方法,但是在 ActivityThread 的 main() 方法里面执行了:

Looper.prepareMainLooper();

Looper.loop();

main() 方法里面还有个特殊的 Handler:sMainThreadHandler

ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
    sMainThreadHandler = thread.getHandler();
}

继续看下 thread.getHandler():

final H mH = new H();

final Handler getHandler() {
    return mH;
}

返回的是一个 H,这个 H 是 ActivityThread 的内部类,并且继承于 Handler :

H 这个类定义了很多常量,标示一些消息类型,比如上图的第一个
LAUNCH_ACTIVITY :启动 Activity 等。主要包含了四大组件的启动和停止等过程。

AnctivityThread 通过 ApplicationThread 和 AMS(ActivityManagerService) 进行进程间通信,AMS 以进程间通信的方式完成 ActivityThread 的请求后回调 ApplicationThread 中的 Binder 方法,然后 ApplicationThread 会向 H 发送消息,H 收到消息后会将 ApplicationThread 中的逻辑切换到 ActivityThread 中去执行,也就是切换到主线程中去执行。

既然知道 Handler 的原理,那么我们就对上一节的六个实例做一个源码分析吧。

三、上篇文章实例源码分析

3.1 使用 Handler 的 post() 方法更新 UI

3.1.1 使用:

//使用post方法直接更新ui线程
new Thread(new Runnable() {
    @Override
    public void run() {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                mShow.setText("使用post方法直接更新ui线程");
            }
        });
    }
}).start();

3.1.2 分析

我们看下 mHandler.post() 方法:

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

首先通过 getPostMessage(r):

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

根据传入的 Runnable 创建一个 Message 对象。

然后调用 sendMessageDelayed(getPostMessage(r), 0) 方法,sendMessageDelayed 方法我们在上面见过,最终会调用 Handler 的 enqueueMessage(queue, msg, uptimeMillis); 方法。把消息放入 MessageQueue 中。由 Looper 去不断取消息给 Handler 处理。

3.2 使用 Handler 的 sendMessage() 方法更新 UI

3.2.1 实例

new Thread(new Runnable() {
    @Override
    public void run() {
        Message message = mHandler.obtainMessage(7, "子线程中发布消息,更新主线程");
        mHandler.sendMessage(message);
    }
}).start();

3.2.2 分析

关键代码在

mHandler.sendMessage(message);

看下 sendMessage方法:

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

我们看到,也是调用了 sendMessageDelayed(msg, 0) 方法,那么下面就和使用 Handler.post() 方法一样了,不在分析

3.3 使用 runOnUiThread() 方法更新 UI

3.3.1 实例

new Thread(new Runnable() {
    @Override
    public void run() {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mShow.setText("使用runOnUiThread更新ui线程");
            }
        });
    }
}).start();

3.3.2 分析

我们看下 runOnUiThread 的源码:

@Override
public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}

先判断是当前的线程是不是 ui 线程,如果不是 UI 线程,就执行 Activity 中的 Handler 对象 mHandler.post() 方法,在主线程中执行;如果是在主线程,则直接在主线程中执行 Runnable 中的 run() 方法。

3.4 使用 View 的 post() 方法更新 UI

3.4.1 实例

new Thread(new Runnable() {
    @Override
    public void run() {
        mButton5.post(new Runnable() {
            @Override
            public void run() {
                mShow.setText("通过View的post方法更新ui");
            }
        });
    }

3.4.2 分析

看下 View.post方法:

public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }
    // Postpone the runnable until we know on which thread it needs to run.
    // Assume that the runnable will be successfully placed after attach.
    getRunQueue().post(action);
    return true;
}

AttachInfo 的官方说明是:当 View 附加到父视图时向 View 提供的一组信息。

如果 attachInfo 不为空,执行 attachInfo 中的 mHandler post 方法,和 3.1 类似

如果 attachInfo 为空,执行 getRunQueue() 中的 mHandler post 方法,那 getRunQueue() 又是啥呢?

private HandlerActionQueue getRunQueue() {
    if (mRunQueue == null) {
        mRunQueue = new HandlerActionQueue();
    }
    return mRunQueue;
}

返回的是 HandlerActionQueue 对象,那么 HandlerActionQueue是啥?

/**
 * Class used to enqueue pending work from Views when no Handler is attached.
 *
 * @hide Exposed for test framework only.
 */
public class HandlerActionQueue {}

注释中描述的很清楚,这个类用于当 View 没有附加到父 View 的时候,把待执行的工作放进队列。然后再 View 附加到父 View 之后再去执行之前添加的任务,有兴趣的可以具体了解下工作流程
相关文章地址:地址

3.5 子线程中创建 Handler(handler1)发送消息,在子线程中的Handler(handler1) 中处理,然后发送给主线程(mHandler) 去更新 UI

这个就不在分析,在上面的第二部分已经说得很清楚了。

3.6 在子线程中更新 UI

3.6.1 实例

public class HandlerTestActivity extends BaseActivity {
    TextView mShow;
    Button mButton4;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentLayout(R.layout.a_activity_handler_test);
        mShow = findViewById(R.id.show);
        mButton4 = findViewById(R.id.button4);
        mButton4.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            beforeOnResumeClick();
            }
        });
        // 写在OnResume之前执行点击事件的话,可以在子线程更新UI线程
        mButton4.performClick();
    }
    /**
    * 测试在onResume之前调用Thread更新ui
    */
    private void beforeOnResumeClick() {
        new Thread() {
            @Override
            public void run() {
                super.run();
                mShow.setText("测试在onResume之前调用Thread更新ui");
            }
        }.start(); 
    }  
}

3.6.2 分析

可以看到,我们如果想在非 UI 线程更新 UI,就必须在 onResume 之前完成 View 的绑定,更新 UI 的操作,这是为什么呢?

我们先从在子线程中更新 UI 的异常说起:

我们看下异常的堆栈信息,可以知道异常产生的路径:

  1. View 的 setText
  2. View 的 checkForRelayout
  3. View 的 invalidate
  4. View 的 invalidate
  5. ViewGroup 的 InvalidateChild
  6. ViewRootImpl 的 invalidateChildInParent
  7. ViewRootImpl 的 CheckThread 方法。

我们看下异常堆栈里面的流程:

首先,我们是在 mShow.setText("测试在onResume之前调用Thread更新ui"); 这里崩溃的,来看下 setText(CharSequence text) 的源码:

public final void setText(CharSequence text) {
    setText(text, mBufferType);
}

public void setText(CharSequence text, BufferType type) {
    setText(text, type, true, 0);
    if (mCharWrapper != null) {
        mCharWrapper.mChars = null;
    }
}

private void setText(CharSequence text, BufferType type,
                     boolean notifyBefore, int oldlen) {
    // 省略前面代码
    if (mLayout != null) {
        checkForRelayout();   
    }
    // 省略后面代码 
}

看到这里有个 checkForRelayout() 方法:

private void checkForRelayout() {
    // If we have a fixed width, we can just swap in a new text layout
    // if the text height stays the same or if the view height is fixed.
    if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
            || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
            && (mHint == null || mHintLayout != null)
            && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
        // Static width, so try making a new text layout.
        int oldht = mLayout.getHeight();
        int want = mLayout.getWidth();
        int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
        /*
         * No need to bring the text into view, since the size is not
         * changing (unless we do the requestLayout(), in which case it
         * will happen at measure).
         */
        makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
                      mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
                      false);
        if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
            // In a fixed-height view, so use our new text layout.
            if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
                    && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
                autoSizeText();
                invalidate();
                return;
            }
            // Dynamic height, but height has stayed the same,
            // so use our new text layout.
            if (mLayout.getHeight() == oldht
                    && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
                autoSizeText();
                invalidate();
                return;
            }
        }
        // We lose: the height has changed and we have a dynamic height.
        // Request a new view layout using our new text layout.
        requestLayout();
        invalidate();
    } else {
        // Dynamic width, so we have no choice but to request a new
        // view layout with a new text layout.
        nullLayouts();
        requestLayout();
        invalidate();
    }
}

可以看到,checkForRelayout 这个方法不管怎样都会调用invalidate();方法。

看下 View 的 invalidate() 方法:

public void invalidate() {
    invalidate(true);
}

public void invalidate(boolean invalidateCache) {
    invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
        boolean fullInvalidate) {
        // 省略部分代码
        // Propagate the damage rectangle to the parent view.
        final AttachInfo ai = mAttachInfo;
        final ViewParent p = mParent;
        if (p != null && ai != null && l < r && t < b) {
            final Rect damage = ai.mTmpInvalRect;
            damage.set(l, t, r, b);
            p.invalidateChild(this, damage);
        }
        // 省略部分代码
    }
}

我们看到了 p.invalidateChild(this, damage); 这行代码,是使用了 ViewParent 对象的 invalidateChild 方法刷新布局,由于这里的 p 是 ViewParent 对象,然而 ViewParent 是个接口,而 ViewGroup 又实现了 ViewParent 接口(并且异常堆栈里面已经明确显示了是使用 ViewGroup 里面的 invalidateChild 方法),现在我们在 ViewGroup 中找下 invalidateChild() 方法:

@Deprecated
@Override
public final void invalidateChild(View child, final Rect dirty) {
        // 省略部分代码
        do {
            View view = null;
            if (parent instanceof View) {
                view = (View) parent;
            }

            if (drawAnimation) {
                if (view != null) {
                    view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
                } else if (parent instanceof ViewRootImpl) {
                    ((ViewRootImpl) parent).mIsAnimating = true;
                }
            }

            // If the parent is dirty opaque or not dirty, mark it dirty with the opaque
            // flag coming from the child that initiated the invalidate
            if (view != null) {
                if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
                        view.getSolidColor() == 0) {
                    opaqueFlag = PFLAG_DIRTY;
                }
                if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
                    view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;
                }
            }

            parent = parent.invalidateChildInParent(location, dirty);
            if (view != null) {
                // Account for transform on current parent
                Matrix m = view.getMatrix();
                if (!m.isIdentity()) {
                    RectF boundingRect = attachInfo.mTmpTransformRect;
                    boundingRect.set(dirty);
                    m.mapRect(boundingRect);
                    dirty.set((int) Math.floor(boundingRect.left),
                            (int) Math.floor(boundingRect.top),
                            (int) Math.ceil(boundingRect.right),
                            (int) Math.ceil(boundingRect.bottom));
                }
            }
        } while (parent != null);
    }
}

我们由找到了这两句关键代码:


((ViewRootImpl) parent).mIsAnimating = true;

parent = parent.invalidateChildInParent(location, dirty);

可以知道,parent.invalidateChildInParent(location, dirty) 这行代码调用的是 ViewRootImpl 的方法,

@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    checkThread();
    // 省略大量代码 
}   

而 checkThread() 代码又是如下:

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

可以看到,正是这里抛出的异常。从上面的分析可知,是我们在子线程中 setText 的时候,抛出的这个异常。

看下 mThread 是什么:

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
    final Thread mThread;
    public ViewRootImpl(Context context, Display disp
        mThread = Thread.currentThread();
    }
}

可以看到 mThread 是在生成 ViewRootImpl 对象的时候赋值的。

上篇文章我们讲了在子线程中更新 UI 必须在 onResume 之前,就是因为在 onResume 之前 ViewRootImpl 还没生成,所以没法检查更新 UI 操作是不是在主线程,所以不会报错。

鉴于篇幅原因,这里不再过多介绍 ViewRootImpl,建议大家看下这篇文章,里面讲到了 ViewRootImpl 相关的内容,后面我也会写下我对 ViewRootImpl 的认识。

四、总结

到此,希望你能对 Android 中的消息机制有了进一步的认识。另外,看源码的时候一定不能着急,并且不要每个地方都看明白,找到重点看就行了,不然你会哭的。。。

其实关于 Handler 的文章很早就想写来总结下,但是一直没有写,一忙起来就懒得写东西了,以后还得坚持记录。

猜你喜欢

转载自blog.csdn.net/Sean_css/article/details/79767196