Android 多线程之 Handler 源码分析

Android UI设计 立即下载

Android 多线程之 Handler 源码分析

前言

这主要是一个讲解 Android 中多线程的系列,全部文章如下:

我们在上篇文章Android 多线程之 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®:

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

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

然后调用 sendMessageDelayed(getPostMessage®, 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 的文章很早就想写来总结下,但是一直没有写,一忙起来就懒得写东西了,以后还得坚持记录。

Android 开发实战:从零到一集成 espeak-ng 实现中文离线 TTS(无需账号开箱即用) 本文详细介绍了如何在 Android 项目中集成 espeak-ng,实现中文离线语音播报。通过下载 so 库、编写 Java 接口、封装管理类,开发者可快速构建无需账号的 TTS 功能。文章覆盖从零到一的开发步骤、调试技巧及性能优化,适合中高级 Android 开发者参考。 阅读详情

相关推荐

手把手教你设计反激开关电源:从整流滤波到PCB布局的实战避坑指南

本文是一份反激开关电源的实战设计指南,重点解析了从整流滤波电路到PCB布局的关键细节与常见陷阱。文章深入探讨了输入保护元件协同设计、EMI滤波器优化、整流桥与滤波电容选型,并详细阐述了如何通过优化PCB布局来确保安规、最小化功率回路面积以控制噪声,旨在帮助工程师和爱好者规避设计误区,提升电源的可靠性与性能。

yhn45678901的博客 690

Android 概述

Android是一个广泛使用的开源操作系统,主要应用于移动设备,如智能手机、平板电脑、智能电视、智能手表等。它由Google主导开发,并构建。Android的开源特性、可定制性和丰富的生态系统,使其成为全球范围内最流行的操作系统之一。

DaPiCaoMin的博客 2442

MT 103 Single Customer Credit Transfer单笔客户转账

MT 103 Single Customer Credit Transfer单笔客户转账 MT103电文能通过三种方式交换,依赖电文使用的业务场景。 (1)   核心MT103电文是一个通用电文,发送和接收此电文不需要在电文用户组(MUG)注册。允许使用所有MT103电文域来交换单客户转账,除了77T域(信封内容)。MT103电文如果是根据预先约定的双边/多边规则的格式,则可以直通处理。 (

shuytu 1万+

Android Studio 实现登录注册-源代码 (连接MySql数据库)

先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。注意链接数据库的地址是jdbcmysql//10.0.2.23306/test。切换会Android视图。...

m0_59092234的博客 5135

从零到一:RF-DETR如何通过神经架构搜索重塑实时目标检测的边界

RF-DETR通过创新的权重共享神经架构搜索(NAS)机制,重新定义了实时目标检测的技术边界。这项技术在COCO等基准测试中表现卓越,成为首个AP超过60的实时检测器,同时保持跨领域稳定性和高效推理速度。RF-DETR的动态架构调整能力使其能够适应从嵌入式设备到云端服务器的各种硬件环境,为工业质检、自动驾驶等场景提供灵活解决方案。

weixin_29061531的博客 467

ENSP实验一:防火墙基础配置

配置client(内网)、FTP Server(外网)的IP地址。进入防火墙,输入密码:默认为admin@123。

Carohuan的博客 1万+

什么叫句柄(Handle)?

句柄 是windows编程的一个关键性的概念,编写windows应用程序总是要和各种句柄打交首,所谓句柄,就是一个4字节长的唯一的数,用以标识许多不同的对象类型,由于windwos是一个多任务操作系统,它可以同时运行多个程序或一个程序的多个副本,这些运行的程序称为一个实例,为了对同一程序的多个副本进行管理,windwos引入了实例句柄,windows为每个应用程序建立一张表,实例句柄就好像是这张表

baidu_18598927的博客 2万+

Photoscan+Smart3D+CC组合建模实战:如何用三款工具打造高精度实景模型

本文详细介绍了如何整合Photoscan、Smart3D和ContextCapture三款软件,构建高效高精度的实景三维建模工作流。通过利用Photoscan稳健的空三解算、Smart3D精准的刺点优化,以及ContextCapture强大的最终建模能力,该方案能有效应对复杂地形与海量数据,显著提升模型质量与生产效率,适用于测绘、文化遗产数字化等专业领域。

weixin_29230649的博客 103

Android多线程(三)——消息机制Handler的使用与源码解析

简介 handler是Android中的一种异步通信机制。因为Android不能在子线程中更新UI,当我们子线程执行异步任务时需要更新UI,这就可以使用在主线程中new Handler实例,在子线程中获取handler,实例使用sendMessage或者post方法通知主线程去更新UI。 使用 handler的应用很广,平时我们自己的继承Thread、实现Runable接口实现异步通信时都会使...

whale的博客 502

工业数据采集实战:用open62541库读写PLC的OPC UA节点(避坑指南)

本文详细介绍了如何使用open62541库实现工业PLC的OPC UA节点数据采集,涵盖环境搭建、节点读写优化及生产环境故障排查。通过订阅模式和多节点读写技术,显著提升数据采集效率与稳定性,适用于智能制造和工业物联网场景。

weixin_30598225的博客 398

PCL RANSAC拟合分割圆柱体模型【2026最新版】

使用RANSAC拟合分割圆柱的原理介绍、官网示例代码实现以及一个可视化拟合出来的圆柱的具体应用案例。博客长期更新,最近一次更新时间为:2026年1月28日。

点云侠的博客 40万+

Android :安卓学习笔记之 Handler机制 的简单理解和使用

Android Handler机制的简单理解和使用1、Handler使用引出2、背景和定义3、作用4、相关概念5、工作流程5.1、对应关系6、基本使用7、参考 1、Handler使用引出 现在作为客户,有这样一个需求,当打开Activity界面时,开始倒计时,倒计时结束后跳转新的界面(思维活跃的朋友可能立马想到如果打开后自动倒计时,就类似于各个APP的欢迎闪屏页面),如下图: 可能觉得直接开启一个包含倒序循环的子线程就ok了,具体实现如下: activity_main <?xml version="

JMW1407的博客 6775

6.4 直流无刷电机(BLDC):测试与性能评估

本文系统阐述了直流无刷电机(BLDC)的测试与性能评估方法,涵盖参数测量、性能测试、测试平台设计及可靠性评估。详细介绍了绕组电阻、电感和反电动势的测量原理与修正方法,以及转矩-转速特性、效率分析和动态响应测试的技术要点。通过搭建高精度测功机系统,结合自动化数据处理,实现对电机性能的全面评估。最后讨论了温升、振动、EMC等特殊测试方法,为BLDC电机的设计验证和质量控制提供了完整的技术路径。

技术工作者 440

Android多线程(二)Handler源码分析

在Android开发的多线程应用场景中,Handler机制十分常用 将工作线程中需更新UI的操作信息 传递到UI主线程,从而实现 工作线程对UI的更新处理,最终实现异步消息的处理 下面是网上找到Handler流程图 一:基础知识 二:主要流程 Handler,loop轮询检测发送消息到MessageQueue入列,Handler回调方法处理消息,重写handMe...

wang_8649的博客 213

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

Android 消息机制——Handler详解与常见问题分析

Android的消息机制主要是指Handler的运行机制 本文从Handler的使用流程开始,到框架简单分析,逐步到消息处理流程的源码分析,然后讲解了消息的处理,源码角度的阻塞与唤醒分析。 最后对Android机制中常见问题进行了总结,包括 为什么不能在子线程中更新UI,根本原因是什么? 为什么主线程用Looper死循环不会引发ANR异常? 为什么Handler构造方法里面的Looper不是直接new? MessageQueue为什么要放在Looper私有构造方法初始化? Handler.post()的逻辑

smile_shaco的博客 2982

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Framework篇 - Handler framework 源码分析

前几篇都是写 Binder 系列的文章,Binder 用于跨进程通信。而 Android 中,同一进程的不同线程如何通信呢,就要用到今天的主角 Handler 了。 目录: Handler 概念 为什么只能通过 Handler 更新 UI 子线程使用 Handler Handler framework 源码分析 1.Handler 概念 Handler 主要...

u014294681的博客 432

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

Android进阶知识:Handler相关

1、前言 Handler在Android中的地位不必说了,学习安卓开发必然跳不过Handler,讲解Handler的文章也是非常的多,这里我对我所了解的Handler这种Android中多线程间的通信方式的相关知识做一个总结。 2、Handler使用 Handler作为线程间通信的方式,最常使用的地方就是子线程更新UI。因为Android的UI控件不是线程安全的,如果在多线程下并发访问可能会导致U...

一个写代码的胖子的博客 366

Android Handler消息机制详解

如果大伙有什么好的学习方法或建议欢迎大家在评论中积极留言哈,希望大家能够共同学习、共同努力、共同进步。小编在这里祝小伙伴们在未来的日子里都可以 升职加薪,当上总经理,出任CEO,迎娶白富美,走上人生巅峰!!

2401_89791458的博客 313

Android 事件处理模型(三)消息传递机制: Handler Looper MessageQueue

最近的工作涉及了Android的Handler消息传递,总结如下: 1. Handler : 为了实现线程和Activity中widget之间的通信,比如为了进行某些耗时操作而新开的线程,在这些操作中如果要跟新UI则需要Handler来处理。它主要负责Message的分发和实现不同Message操作的自定义,相当于句柄。 2. MessageQuene: Message是线程和

1689

Android Handler消息机制详解(1)

Handler是一个消息分发对象。Handler是Android系统提供的一套用来更新UI的机制,也是一套消息处理机制,可以通过Handler发消息,也可以通过Handler处理消息。如果大伙有什么好的学习方法或建议欢迎大家在评论中积极留言哈,希望大家能够共同学习、共同努力、共同进步。小编在这里祝小伙伴们在未来的日子里都可以 升职加薪,当上总经理,出任CEO,迎娶白富美,走上人生巅峰!!

2401_87252417的博客 545

Android基础知识(九):Handler机制的原理分析

Handler机制的实现与原理分析 一、Handler机制简介 Handler机制是一套Android消息传递机制/异步通信机制。 Handler机制的作用在于:在多线程的应用场景下,将工作线程中需更新UI的操作信息传递到UI主线程,从而实现工作线程对UI的更新处理,最终实现异步消息的处理。此外,在多个线程并发更新UI的同时保证线程安全,从而避免线程操作不安全的问题。 二、Handler机制实现...

weixin_38196407的博客 1784

Android多线程消息处理机制(三) Handler部分源码分析

此文一起来看下Handler的部分源码,不用怕很简单,有英文的地方我都翻译了。 把里面相关的方法过一眼,你瞬间会明白很多。高手就不用看了,去过你的荣华富贵花天酒地。 我一致认为,想学好某一技术,首先是了解原理和熟练使用,其次是钻研其源码,看他是怎么做的。 万万不可随便在网上搜篇文章做一个demo完事,你可知有多少文章讲多么肤浅和曲解? 曾记得以前看过一个视频教程,那讲师说:“有两个队列,一

阿杜 1万+

android Handler机制源码详解

1、Handler介绍 Handler机制是android系统提供给我们的一种消息处理机制,最常见的应用就是在子线程更新UI操作。其实查看android源码可以发现,activity中所有的生命周期方法的回调都是应用的Handler机制。 Handler机制主要是为了解决安卓系统中多线程并发的问题,试想如果如果在没有加锁的情况下在多个子线程中更新主线程即UI线程界面,会造成界面错乱,我们也无法

wenqiaoqqq的博客 711

从源码角度看Handler原理

在Android中,有一个规定就是除了主线程,其他线程不能操作UI视图,因为不这样做的话会出现线程不安全问题。但还有一个规定UI线程在执行一个操作如果5秒内没有响应就会包ANR错误。所以UI线程中不允许访问网络这样的耗时操作,那么问题来了,子线程执行耗时操作,比如从网络获取图片,但子线程...

132

Android线程三、Handler

写在前面 Handler是什么 Handler是Android提供的: 一套更新UI的机制. 一套消息处理的机制,我们可以通过Handler发送和处理消息.为什么使用Handler Android在设计时,就封装了一套消息的创建、传递、处理机制,如果不遵循这样的机制,就没有办法更新UI信息,就会抛出异常信息.

简单记录 4163

源码解析(一) Handler

好记性不如烂笔头,古人诚不欺我啊 Handler其实在Android中一个很重要的对象,它是在Android中针对多线程执行时线程安全问题的一种防范机制,同时它也可以认为是一种在线程间的通信机制,因为在Android中,是支持多线程运行处理数据的,但是由于在线程中可能会提高效率,充分利用处理器等等很多好处,但是它不可避免的也会造成如下一些缺陷: 1、线程安全:多线程同时访问一块内存,很容易造成问...

衣知世 与 机智白的博客 482
上一篇: Android 多线程之 Handler 基本使用
下一篇: 从一个异常认识Android中的 commit() 和 commitAllowingStateLoss()
Sean_css
博客等级 码龄11年 32粉丝 84原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值