典型案例分析
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mTextView = findViewById(R.id.textView1);
// Restore TextView if there is a savedInstanceState bundle.
if (savedInstanceState != null) {
mTextView?.text = savedInstanceState.getString(TEXT_STATE);
}
mCountdown = findViewById(R.id.countdown)
Thread {
for (i in 5 downTo 1) {
mCountdown?.text = i.toString()
try {
Thread.sleep(1000)
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
//计时结束后跳转到其他界面
startActivity(Intent(this, SubActivity::class.java))
}.start()
}
这段代码似乎看上去很正常,但是当你运行时就会发现,它会报一个致命性的异常:
ERROR/AndroidRuntime(421): FATAL EXCEPTION: Thread-8
ERROR/AndroidRuntime(421): android.view.ViewRoot$CalledFromWrongThreadException:
Only the original thread that created a view hierarchy can touch its views.
原因在于,Android 系统中的 UI 组件并不是线程安全的,如果要更新 UI,必须在主线程中更新,不可以在子线程中执行更新的操作。
既然这样,我们就要在子线程中通知主线程,让主线程做更新操作。我们即需要使用到 Handler 对象通知主线程。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mTextView = findViewById(R.id.textView1);
// Restore TextView if there is a savedInstanceState bundle.
if (savedInstanceState != null) {
mTextView?.text = savedInstanceState.getString(TEXT_STATE);
}
mCountdown = findViewById(R.id.countdown)
Thread {
for (i in 5 downTo 1) {
// mCountdown?.text = i.toString()
val msg = Message()
msg.arg1 = i
handler1.sendMessage(msg)
try {
Thread.sleep(1000)
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
//计时结束后跳转到其他界面
startActivity(Intent(this, SubActivity::class.java))
}.start()
}
//消息处理者,创建一个Handler的子类对象,目的是重写Handler的处理消息的方法(handleMessage())
private val handler1: Handler = object : Handler() {
override fun handleMessage(msg: Message) {
mCountdown?.text = msg.arg1.toString()
}
}
通过上面这种方式,我们就可以解决线程安全的问题,把复杂的任务处理工作交给子线程去完成, 然后子线程通过 handler 对象告知主线程,由主线程更新视图,这个过程中消息机制起着重要的作用。
Handler 消息机制
Handler 用来发送和处理线程对应的消息队列 MessageQueue 中存储的 Message。每个 Handler 实例对应一个线程以及该线程的消息队列。
当你创建一个新的 Handler,它会绑定创建它的线程和消息队列,然后它会向消息队列发送 Message 或者 Runnable,并且在它们离开消息队列时执行。
构造函数
Handler 的构造函数大致上可以分为两类,先来看第一类:
public Handler() {
this(null, false);
}
public Handler(Callback callback) {
this(callback, false);
}
public Handler(Callback callback, boolean async) {
// 如果是匿名类、内部类、本地类,且没有使用 static 修饰符,提示可能导致内存泄漏
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());
}
}
// 从当前线程的 ThreadLocal获取 Looper
mLooper = Looper.myLooper();
if (mLooper == null) { // 创建 Handler 之前一定要先创建 Looper。主线程已经自动为我们创建。
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue; // Looper 持有一个 MessageQueue
mCallback = callback; // handleMessage 回调
mAsynchronous = async; // 是否异步处理
}
这一类构造函数最终调用的都是两个参数的方法,参数中不传递 Looper,所以要显式检查是否已经创建 Looper。
创建 Handler 之前一定要先创建 Looper,否则会直接抛出异常。在主线程中 Looper 已经自动创建好,无需我们手动创建,在 ActivityThread.java 的 main() 方法中可以看到。
Looper 持有一个消息队列 MessageQueue,并赋值给 Handler 中的 mQueue 变量。
Callback 是一个接口,定义如下:
public interface Callback {
public boolean handleMessage(Message msg);
}
可以看到,Handler 处理消息还可以通过构造器参数传入 CallBack 的方式。
看过 Handler 的第一类构造函数,第二类其实就很简单了,只是多了 Looper 参数而已:
public Handler(Looper looper) {
this(looper, null, false);
}
public Handler(Looper looper, Callback callback) {
this(looper, callback, false);
}
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
发送消息
-
几乎所有的 sendXXX() 最后调用的都是 sendMessageAtTime() 方法。
sendMessage(Message msg);//立刻发送消息 sendMessageAtTime(Message msg, long atTime);//在某个时间点发送消息 sendMessageDelayed(Message msg, long delayedTime);//在当前时间点延迟一段时间发送消息 -
所有的 postXXX() 方法都是调用 getPostMessage() 将 参数中的 Runnable 包装成 Message,再调用对应的 sendXXX() 方法。
private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; } private static Message getPostMessage(Runnable r, Object token) { Message m = Message.obtain(); m.obj = token; m.callback = r; return m; } -
殊途同归,发送消息的重任最后都落在了 sendMessageAtTime() 身上。
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); // 调用 Messagequeue 的 enqueueMessage() 方法 }可以看到 Handler 把发送消息的任务转手又交给了 MessageQueue 来处理。
-
MessageQueue
Message 的入队工作实际上是由 MessageQueue 通过 enqueueMessage() 函数来完成的。
boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { // msg 必须有 target throw new IllegalArgumentException("Message must have a target."); } if (msg.isInUse()) { // msg 不能正在被使用 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; }可以看出来,MessageQueue 是用链表结构来存储消息的,消息是按触发时间的顺序来插入的。 enqueueMessage() 方法是用来存消息的,而取靠的是 next() 方法。
-
next()
Message next() { // Return here if the message loop has already quit and been disposed. // This can happen if the application tries to restart a looper after quit // which is not supported. final long ptr = mPtr; if (ptr == 0) { return null; } int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } // 阻塞方法,主要是通过 native 层的 epoll 监听文件描述符的写入事件来实现的。 // 如果 nextPollTimeoutMillis = -1,一直阻塞不会超时。 // 如果 nextPollTimeoutMillis = 0,不会阻塞,立即返回。 // 如果 nextPollTimeoutMillis > 0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回。 nativePollOnce(ptr, nextPollTimeoutMillis); 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) { // Stalled by a barrier. Find the next asynchronous message in the queue. // msg.target == null表示此消息为消息屏障(通过postSyncBarrier方法发送来的) // 如果发现了一个消息屏障,会循环找出第一个异步消息(如果有异步消息的话), // 所有同步消息都将忽略(平常发送的一般都是同步消息) 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; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); // 标记 FLAG_IN_USE return msg; } } else { // No more messages. // 没有消息,会一直阻塞,直到被唤醒 nextPollTimeoutMillis = -1; } // Process the quit message now that all pending messages have been handled. if (mQuitting) { 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. // Idle handle 仅当队列为空或者队列中的第一个消息将要执行时才会运行 if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. // 没有 idle handler 需要运行,继续循环 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 置零保证不再运行 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; } }next() 方法是一个死循环,但是当没有消息的时候会阻塞,避免过度消耗 CPU。nextPollTimeoutMillis 大于 0 时表示等待下一条消息需要阻塞的时间。等于 -1 时表示没有消息了,一直阻塞到被唤醒。 这里的阻塞主要靠 native 函数 nativePollOnce() 来完成。可以看Gityuan博客【Android消息机制2-Handler(Native层)】
Looper
创建 Handler 之前必须先创建 Looper,而主线程已经为我们自动创建了 Looper,无需再手动创建,见 ActivityThread.java 的 main() 方法:
public static void main(String[] args) {
...
Looper.prepareMainLooper(); // 创建主线程 Looper
...
}
-
prepareMainLooper()
public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }可以注意到
sMainLooper只能被初始化一次,也就是说 prepareMainLooper() 只能调用一次,否则将直接抛出异常。 -
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(false),说明主线程 Looper 是不允许退出的。因为主线程需要源源不断的处理各种事件,一旦退出,系统也就瘫痪了。 而我们在子线程调用 prepare() 来初始化 Looper时,默认调动的是prepare(true),子线程 Looper 是允许退出的。每个线程的 Looper 是通过
ThreadLocal来存储的,保证其线程私有。回顾文章开头介绍的 Handler 的构造函数中 mLooper 变量的初始化:mLooper = Looper.myLooper(); public static @Nullable Looper myLooper() { // 通过当前线程的 ThreadLocal 来获取 return sThreadLocal.get(); } - 构造函数
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }对照 Handler 的构造函数:
public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; }其中的关系就很清晰了:
Looper持有MessageQueue对象的引用Handler持有Looper对象的引用以及Looper对象的MessageQueue的引用
-
loop()
子线程使用 Handler 的标准写法:
class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { // process incoming messages here } }; Looper.loop(); } }让消息队列转起来的核心就是 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."); } if (me.mInLoop) { Slog.w(TAG, "Loop again would have the queued messages be executed" + " before this one completed."); } me.mInLoop = true; 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(); // Allow overriding a threshold with a system prop. e.g. // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start' final int thresholdOverride = SystemProperties.getInt("log.looper." + Process.myUid() + "." + Thread.currentThread().getName() + ".slow", 0); boolean slowDeliveryDetected = false; 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); } // Make sure the observer won't change while processing a transaction. final Observer observer = sObserver; final long traceTag = me.mTraceTag; long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs; long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs; if (thresholdOverride > 0) { slowDispatchThresholdMs = thresholdOverride; slowDeliveryThresholdMs = thresholdOverride; } final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0); final boolean logSlowDispatch = (slowDispatchThresholdMs > 0); final boolean needStartTime = logSlowDelivery || logSlowDispatch; final boolean needEndTime = logSlowDispatch; if (traceTag != 0 && Trace.isTagEnabled(traceTag)) { Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); } final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0; final long dispatchEnd; Object token = null; if (observer != null) { token = observer.messageDispatchStarting(); } long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid); try { msg.target.dispatchMessage(msg); if (observer != null) { observer.messageDispatched(token, msg); } dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; } catch (Exception exception) { if (observer != null) { observer.dispatchingThrewException(token, msg, exception); } throw exception; } finally { ThreadLocalWorkSource.restore(origWorkSource); if (traceTag != 0) { Trace.traceEnd(traceTag); } } if (logSlowDelivery) { if (slowDeliveryDetected) { if ((dispatchStart - msg.when) <= 10) { Slog.w(TAG, "Drained"); slowDeliveryDetected = false; } } else { if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery", msg)) { // Once we write a slow delivery log, suppress until the queue drains. slowDeliveryDetected = true; } } } if (logSlowDispatch) { showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg); } 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(); } }这里我们看到,
mLooper()方法里我们取出了当前线程的looper对象,然后从 looper 对象开启了一个死循环 不断地从 looper 内的MessageQueue中取出Message,只要有 Message 对象,就会通过 Message 的target调用dispatchMessage去分发消息,通过代码可以看出 target 就是我们创建的 handler。简单说就是一个死循环不停的从 MessageQueue 中取消息,取到消息就通过 Handler 来进行分发,分发之后回收消息进入消息池,以便重复利用。 从消息队列中取消息调用的是 MessageQueue.next() 方法,之前已经分析过。在没有消息的时候可能会阻塞,避免死循环消耗 CPU。 取出消息之后进行分发调用的是 msg.target.dispatchMessage(msg),msg.target 是 Handler 对象,最后再来看看 Handler 是如何分发消息的。public void dispatchMessage(Message msg) { if (msg.callback != null) { // callback 是 Runnable 类型,通过 post 方法发送 handleCallback(msg); } else { if (mCallback != null) { // Handler 的 mCallback参数 不为空时,进入此分支 if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); // Handler 子类实现的 handleMessage 逻辑 } } private static void handleCallback(Message message) { message.callback.run(); }- Message 的 callback 属性不为空时,说明消息是通过 postXXX() 发送的,直接执行 Runnable 即可
- Handler 的 mCallback 属性不为空,说明构造函数中传入了 Callback 实现,调用 mCallback.handleMessage(msg) 来处理消息
- 以上条件均不满足,只可能是 Handler 子类重写了 handleMessage() 方法。这好像也是我们最常用的一种形式
Message
Message 字段
public int what; // 消息标识
public int arg1; // 可携带的 int 值
public int arg2; // 可携带的 int 值
public Object obj; // 可携带内容
public long when; // 超时时间
public Handler target; // 处理消息的 Handler
public Runnable callback; // 通过 post() 发送的消息会有此参数
-
obtain()
Message 有 public 修饰的构造函数,但是一般不建议直接通过构造函数来构建 Message,而是通过 Message.obtain() 来获取消息。
public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } return new Message(); }sPool是消息缓存池,链表结构,其最大容量 MAX_POOL_SIZE 为 50。obtain() 方法会直接从消息池中取消息,循环利用,节约资源。当消息池为空时,再去新建消息。 -
recycleUnchecked()
Looper.loop()方法中最后会调用 msg.recycleUnchecked() 方法吗?这个方法会回收已经分发处理的消息,并放入缓存池中。void recycleUnchecked() { // Mark the message as in use while it remains in the recycled object pool. // Clear out all other details. flags = FLAG_IN_USE; what = 0; arg1 = 0; arg2 = 0; obj = null; replyTo = null; sendingUid = -1; when = 0; target = null; callback = null; data = null; synchronized (sPoolSync) { if (sPoolSize < MAX_POOL_SIZE) { next = sPool; sPool = this; sPoolSize++; } } }
总结
- Android 通过 Looper、Handler 来实现消息循环机制。Android 的消息循环是针对线程的,每个线程都可以有自己的消息队列和消息循环。
- Android 系统中的 Looper 负责管理线程的消息队列和消息循环。通过 Looper.myLooper() 得到当前线程的 Looper 对象, 通过 Looper.getMainLooper() 得到当前进程的主线程的 Looper 对象。 Handler 也持有 Looper 对象的引用,通过 Looper.loop() 方法让消息队列循环起来。
- Handler 被用来发送消息,但并不是真正的自己去发送。它持有 MessageQueue 对象的引用,通过 MessageQueue 来将消息入队。
- Looper 持有 MessageQueue 对象应用,在 loop() 方法中会调用 MessageQueue 的 next() 方法来不停的取消息。
- loop() 方法中取出来的消息最后还是会调用 Handler 的 dispatchMessage() 方法来进行分发和处理。
补充
内存泄漏
Handler在使用过程中,需要注意的问题之一便是内存泄漏问题。
首先Handler使用是用来进行线程间通信的,所以新开启的线程是会持有Handler引用的, 如果在Activity等中创建Handler,并且是非静态内部类的形式,就有可能造成内存泄漏。
- 首先,非静态内部类是会隐式持有外部类的引用,所以当其他线程持有了该Handler,线程没有被销毁,则意味着Activity会一直被Handler持有引用而无法导致回收。
- 同时,MessageQueue中如果存在未处理完的Message,Message的target也是对Activity等的持有引用,也会造成内存泄漏。
解决的办法
- 使用静态内部类+弱引用的方式:
静态内部类不会持有外部类的的引用,当需要引用外部类相关操作时,可以通过弱引用还获取到外部类相关操作,弱引用是不会造成对象该回收回收不掉的问题。
private Handler sHandler = new TestHandler(this);
static class TestHandler extends Handler {
private WeakReference<Activity> mActivity;
TestHandler(Activity activity) {
mActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Activity activity = mActivity.get();
if (activity != null) {
//TODO:
}
}
}
- 在外部类对象被销毁时,将MessageQueue中的消息清空。例如,在Activity的onDestroy时将消息清空
@Override protected void onDestroy() { handler.removeCallbacksAndMessages(null); super.onDestroy(); }