相信每个 Android 开发者都听说过 主线程的 Looper 消息循环,但是真正能把它彻底搞明白的人可能不多。作为一个拥有 10 年经验的 Android 后端架构师,我经常遇到一些性能问题,追根溯源,很多都跟对 Looper 机制理解不透彻有关。今天,我们就来彻底扒一扒 Android 主线程 Looper 消息循环的底层原理,再结合具体的代码和配置解决方案,帮你构建更流畅的 Android 应用,并总结一些实战避坑经验。
问题场景重现:ANR 异常的罪魁祸首
在开发过程中,我们经常会遇到 ANR (Application Not Responding) 异常。ANR 的根本原因就是主线程被阻塞,无法及时处理消息队列中的消息。例如,我们在 onCreate 方法中执行了一个耗时的网络请求,导致主线程卡顿超过 5 秒,就会触发 ANR。 又或者,在 View 的 onTouchEvent 中进行了复杂的计算,同样容易导致 ANR。 这些场景都与 Looper 消息循环息息相关,理解 Looper 的运作方式,才能更好地避免这些问题。
Looper 消息循环底层原理深度剖析
Looper 消息循环是 Android 应用程序的核心机制之一,它负责管理和调度应用程序中的消息。Android 的 UI 更新、事件处理等都依赖于主线程的 Looper 消息循环。它主要包含以下几个关键类:
- Looper: 负责管理消息队列 (MessageQueue) 和线程,每个线程最多只能有一个 Looper。
- MessageQueue: 消息队列,用于存储待处理的消息。采用先进先出 (FIFO) 的顺序。
- Message: 消息对象,包含需要处理的数据和 Handler 对象。
- Handler: 消息处理器,负责将消息发送到消息队列,并在 Looper 线程中处理消息。
Looper 的创建和初始化
在 Android 应用程序启动时,ActivityThread 会创建一个 Looper 实例并将其关联到主线程。具体来说,Looper.prepareMainLooper() 方法用于为主线程创建一个 Looper 实例。Looper.loop() 方法则启动消息循环,从消息队列中不断取出消息并进行处理。
public class Looper {
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class
final MessageQueue mQueue;
final Thread mThread;
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
/**
* Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this Looper, before actually starting the loop.
*
* Be sure to call {@link #loop()} after calling this method, and
* end it by calling {@link #quit()}.
*
* @throws RuntimeException if the Looper has already been prepared.
*/
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));
}
/**
* Initialize the current thread as a looper that will not permit
* the Looper to be quit. This is used for threads that run for a
* long period of time and are not associated with an activity. It
* prevents the Looper from being accidentally quit. The caller should
* be careful to ensure that the thread is stopped when no longer needed.
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
/**
* Return the Looper associated with the current thread.
* Returns null if the current thread is not a Looper.
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
/**
* Start looping the message queue in the current thread.
* Be sure to call {@link #quit()} to end the 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 traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getComponentName() + " " +
msg.what);
}
try {
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
} catch (Exception exception) {
if (logging != null) {
logging.println("!!!!! Exception to " + msg.target + " " + msg.callback + ": " + exception);
}
throw exception;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
final int end = (userStart == 0) ? 0 : Trace.traceEndSection();
if (end != 0) {
synchronized (sLock) {
if (sTrimForeground) {
sTrimForeground = false;
MemoryTrimRequest.dispatchTrimMemory();
}
}
}
msg.recycleUnchecked();
}
}
/**
* Return the main Looper, which lives in the main thread of the process.
*/
public static @NonNull Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
/**
* Quits the looper. Causes the {@link #loop} method to terminate
* when it next tries to obtain a message. Pending messages may not
* be delivered before the Looper terminates.
*
* You should only call this method on a Looper that you've created.
*/
public void quit() {
MessageQueue queue = mQueue;
if (queue == null) {
return;
}
queue.quit(false);
}
/**
* Like {@link #quit()}, but safely discards any pending messages in the
* message queue.
*
* You should only call this method on a Looper that you've created.
*/
public void quitSafely() {
MessageQueue queue = mQueue;
if (queue == null) {
return;
}
queue.quit(true);
}
/**
* Return the Thread associated with this Looper.
*/
public @NonNull Thread getThread() {
return mThread;
}
/**
* Return the {@link MessageQueue} associated with this Looper.
*/
public @NonNull MessageQueue getQueue() {
return mQueue;
}
/**
* Add a custom logging printer that prints the messages that the looper
* is about to dispatch.
*/
public void setLogging(Printer printer) {
mLogging = printer;
}
/** @hide */
public void setTraceTag(long traceTag) {
mTraceTag = traceTag;
}
/** @hide */
public @Nullable Printer getLogging() {
return mLogging;
}
@Override
public String toString() {
return "Looper (" + mThread.getName() + ") {" + Integer.toHexString(System.identityHashCode(this)) + "}";
}
private final Printer mLogging;
private final long mTraceTag;
}
MessageQueue 的enqueueMessage和next
MessageQueue 中有两个核心方法:enqueueMessage 和 next。
- enqueueMessage: 用于将消息添加到消息队列中。Handler 通过该方法将消息发送到 Looper 线程的消息队列。
- next: 用于从消息队列中取出消息。如果消息队列为空,该方法会阻塞,直到有新的消息到达或者 Looper 退出。
Handler 的作用
Handler 是 Looper 机制中非常重要的一个组件。它负责将消息发送到消息队列,并在 Looper 线程中处理消息。Handler 内部持有一个 Looper 实例,通过 Looper 实例将消息发送到消息队列。
public class Handler {
/*
* Create a new Handler whose posted messages and runnables are not associated with
* any particular Looper. This Handler will be bound to the Looper associated
* with the thread it is running in. If a Looper does not already exist for the
* thread, one will be created for you.
*/
public Handler() {
this(null, false);
}
/**
* Create a new Handler whose posted messages and runnables are sent to the
* message queue of a Looper on the current thread.
*
* @param callback The callback interface in which to handle the message, or null.
*/
public Handler(@Nullable Callback callback) {
this(callback, false);
}
/**
* Create a new Handler whose posted messages and runnables are sent to the
* message queue of a Looper on the current thread.
*
* @param async If true, the {@link #sendMessage(Message)} methods will behave as if
* {@link Message#setAsynchronous(boolean)} is true for all messages posted to
* this handler. The {@link #post(Runnable)} methods and the
* {@link #sendMessageAtFrontOfQueue(Message)} methods will not be affected.
*/
public Handler(@Nullable Callback callback, boolean async) {
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());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
/**
* Create a new Handler that can call back into the specified Looper.
*
* @param looper The Looper, whose message queue to use.
*/
public Handler(@NonNull Looper looper) {
this(looper, null, false);
}
/**
* Create a new Handler that can call back into the specified Looper.
*
* @param looper The Looper, whose message queue to use.
* @param callback The callback interface in which to handle the message, or null.
*/
public Handler(@NonNull Looper looper, @Nullable Callback callback) {
this(looper, callback, false);
}
/**
* Create a new Handler that can call back into the specified Looper.
*
* @param looper The Looper, whose message queue to use.
* @param callback The callback interface in which to handle the message, or null.
* @param async If true, the {@link #sendMessage(Message)} methods will behave as if
* {@link Message#setAsynchronous(boolean)} is true for all messages posted to
* this handler. The {@link #post(Runnable)} methods and the
* {@link #sendMessageAtFrontOfQueue(Message)} methods will not be affected.
*/
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(@NonNull Message msg) {
}
/**
* Handle system messages here.
*/
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
/**
* Causes the Runnable r to be added to the message queue. The runnable will
* be run on the thread to which this handler is attached.
*
* @param r The Runnable that will be executed.
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
/**
* Causes the Runnable r to be added to the message queue. The runnable will
* be run on the thread to which this handler is attached.
*
* @param r The Runnable that will be executed.
* @param token A token identifying this task. This can be used to remove
* future posts of this Runnable from the queue using
* {@link #removeCallbacksAndMessages(Object)}.
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
* @hide
*/
public final boolean post(@NonNull Runnable r, @Nullable Object token) {
return sendMessageDelayed(getPostMessage(r, token), 0);
}
/**
* Causes the Runnable r to be added to the message queue, to be run
* at a specific time given by <var>uptimeMillis</var>.
*
* <p><b>Note:</b> The system imposes a maximum wake-up alarm frequency.
* To reduce power consumption, the system may batch alarms together.
* As a result, the time at which the Runnable executes may be delayed
* (possibly significantly) behind the requested time. See
* {@link android.os.PowerManager#setAlarmClock(android.app.AlarmManager.AlarmClockInfo, android.app.PendingIntent) setAlarmClock()}
* for more information about time tolerances.</p>
*
* @param r The Runnable that will be executed.
* @param uptimeMillis The absolute time at which the callback should run,
* using the {@link SystemClock#uptimeMillis()} clock.
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
* Note that the implementation may enqueue the runnable even in cases
* where the looper has shut down so always check the return value.
*/
public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis) {
return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}
/**
* Causes the Runnable r to be added to the message queue, to be run
* at a specific time given by <var>uptimeMillis</var>.
*
* <p><b>Note:</b> The system imposes a maximum wake-up alarm frequency.
* To reduce power consumption, the system may batch alarms together.
* As a result, the time at which the Runnable executes may be delayed
* (possibly significantly) behind the requested time. See
* {@link android.os.PowerManager#setAlarmClock(android.app.AlarmManager.AlarmClockInfo, android.app.PendingIntent) setAlarmClock()} for more information about time tolerances.</p>
*
* @param r The Runnable that will be executed.
* @param token A token identifying this task. This can be used to remove
* future posts of this Runnable from the queue using
* {@link #removeCallbacksAndMessages(Object)}.
* @param uptimeMillis The absolute time at which the callback should run, using the
* {@link SystemClock#uptimeMillis()} clock.
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
* @hide
*/
public final boolean postAtTime(@NonNull Runnable r, @Nullable Object token, long uptimeMillis) {
return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}
/**
* Causes the Runnable r to be added to the message queue, to be run
* after the specified amount of time elapses.
*
* @param r The Runnable that will be executed.
* @param delayMillis The delay (in milliseconds) until the Runnable
* will be executed.
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
/**
* Causes the Runnable r to be added to the message queue, to be run
* after the specified amount of time elapses.
*
* @param r The Runnable that will be executed.
* @param token A token identifying this task. This can be used to remove
* future posts of this Runnable from the queue using
* {@link #removeCallbacksAndMessages(Object)}.
* @param delayMillis The delay (in milliseconds) until the Runnable
* will be executed.
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
* @hide
*/
public final boolean postDelayed(@NonNull Runnable r, @Nullable Object token, long delayMillis) {
return sendMessageDelayed(getPostMessage(r, token), delayMillis);
}
/**
* Remove any pending posts of Runnable r that are in the message queue.
*/
public final void removeCallbacks(@NonNull Runnable r) {
mQueue.removeMessages(this, r, null);
}
/**
* Remove any pending posts of Runnable r with Object token that are in
* the message queue.
*
* @hide
*/
public final void removeCallbacks(@NonNull Runnable r, @Nullable Object token) {
mQueue.removeMessages(this, r, token);
}
/**
* Remove any pending posts of callbacks and clear sent messages whose
* {@link Message#what what} value is token.
*
* @param token The value to match against {@link Message#what what}.
*/
public final void removeMessages(int what) {
mQueue.removeMessages(this, what, null);
}
/**
* Remove any pending posts of callbacks and clear sent messages whose
* {@link Message#what what} value is token.
*
* @param what The value to match against {@link Message#what what}.
* @param object to match against.
*/
public final void removeMessages(int what, @Nullable Object object) {
mQueue.removeMessages(this, what, object);
}
/**
* Remove any pending posts of callbacks and sent messages whose
* {@link Message#obj obj} is <var>object</var>.
* @param object The object to match against.
*/
public final void removeCallbacksAndMessages(@Nullable Object object) {
mQueue.removeCallbacksAndMessages(this, object);
}
/**
* Check if there are any pending posts of callbacks/messages with token in
* the message queue.
*/
public final boolean hasMessages(int what) {
return mQueue.hasMessages(this, what, null);
}
/**
* Check if there are any pending posts of callbacks/messages with token in
* the message queue.
*/
public final boolean hasMessages(int what, @Nullable Object object) {
return mQueue.hasMessages(this, what, object);
}
/**
* Check if there are any pending posts of the runnable r in the message queue.
*/
public final boolean hasCallbacks(@NonNull Runnable r) {
return mQueue.hasMessages(this, r, null);
}
/**
* Send a Message to the Handler.
*
* @param msg The Message to send.
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
/**
* Send a Message to the Handler, arranging for it to be delivered after the
* specified amount of time elapses.
*
* @param msg The Message to send.
* @param delayMillis The delay (in milliseconds) until the message will be delivered.
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
/**
* Send a Message to the Handler, arranging for it to be delivered at the
* given uptimeMillis. You can think of uptimeMillis as the absolute time
* at which the message should be delivered.
*
* @param msg The Message to send.
* @param uptimeMillis The absolute time at which the message should be delivered,
* using the {@link SystemClock#uptimeMillis()} clock.
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public boolean sendMessageAtTime(@NonNull 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);
}
/**
* Enqueue a message into the MessageQueue after all pending messages
* before the front of the queue.
* @param msg The Message to send.
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {
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, 0);
}
/**
* Perform enqueuing of message into the message queue.
*
* This is separated from the sendMessageAtTime function so that
* we can prevent the end-user from overriding it.
*
* @param queue The MessageQueue to enqueue into.
* @param msg The Message to enqueue.
* @param uptimeMillis The absolute time at which the message should be delivered,
* using the {@link SystemClock#uptimeMillis()} clock.
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
* Note that the implementation may enqueue the runnable even in cases
* where the looper has shut down so always check the return value.
*
* @hide
*/
protected boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) {
msg.target = this;
msg.asynchronous = mAsynchronous;
return queue.enqueueMessage(msg, uptimeMillis);
}
/**
* Callback interface you can use when instantiating a Handler to avoid
* having to subclass it to define the handling behavior.
*/
public interface Callback {
/**
* @param msg A {@link Message Message} object
* @return True if no further handling is desired
*/
boolean handleMessage(@NonNull Message msg);
}
/**
* Field mLooper
*/
private final @NonNull Looper mLooper;
/**
* Field mQueue
*/
final @NonNull MessageQueue mQueue;
/**
* Field mCallback
*/
final @Nullable Callback mCallback;
final boolean mAsynchronous;
/**
* Field FIND_POTENTIAL_LEAKS
*/
private static final boolean FIND_POTENTIAL_LEAKS = false;
private static final String TAG = "Handler";
private static Handler.Callbacks[] sCallbacks = null;
/**
* @hide
*/
protected Handler getHandler() {
return this;
}
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;
}
/**
* @hide
*/
public interface Callbacks {
void binderDied(Handler h);
}
}
具体的代码/配置解决方案:优化消息处理
避免在主线程执行耗时操作: 这是最重要的一点。任何可能阻塞主线程的操作,例如网络请求、I/O 操作、复杂计算等,都应该放在子线程中执行。可以使用
AsyncTask、ExecutorService、IntentService等方式来创建子线程。
使用 HandlerThread 处理后台任务: 如果需要在后台线程中执行一些循环任务,可以使用
HandlerThread。HandlerThread会创建一个带有 Looper 的线程,可以避免手动管理线程的生命周期。HandlerThread handlerThread = new HandlerThread("MyHandlerThread");
handlerThread.start(); Handler handler = new Handler(handlerThread.getLooper()) { @Override public void handleMessage(Message msg) { // 在后台线程中处理消息 } };
// 发送消息到后台线程
handler.sendMessage(message);
```
使用 Message 的 target 属性减少 Handler 创建: 创建大量的 Handler 实例会消耗资源。可以通过复用 Message 对象,并设置其
target属性来减少 Handler 的创建。// 创建一个 Handler
Handler handler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { // 处理消息 } };
// 创建一个 Message
Message message = Message.obtain();
message.target = handler; // 设置 Message 的 target 为 Handler
message.what = 1;
message.obj = "Hello";
// 将消息发送到消息队列 message.sendToTarget(); // 调用 sendToTarget() 方法
```
- 使用线程池优化并发: 使用
ExecutorService可以更灵活地管理线程,避免频繁创建和销毁线程带来的开销。 配合Future对象可以获取异步任务的结果。 在实际应用中,线程池的配置(核心线程数、最大线程数、队列容量等)需要根据应用的负载情况进行调整,监控 CPU 利用率、线程数量等指标, 达到最佳性能。
实战避坑经验总结
- 不要在 Handler 的
handleMessage方法中执行耗时操作:handleMessage方法运行在 Looper 线程中,如果在该方法中执行耗时操作,会导致 Looper 线程阻塞,影响 UI 的响应速度。 - 注意 Handler 的内存泄漏问题: 如果 Handler 是非静态内部类,并且持有 Activity 的引用,可能会导致 Activity 无法被回收,造成内存泄漏。可以将 Handler 定义为静态内部类,并使用 WeakReference 来持有 Activity 的引用。
- 合理设置 Message 的优先级: 可以通过
Message.when属性来设置消息的优先级,确保重要的消息能够及时被处理。 - 避免过度使用 postDelayed: 大量使用
postDelayed可能导致消息堆积,影响消息的及时处理。 应该尽量使用精确的定时器, 例如AlarmManager。 - 使用 TraceView 和 Systrace 分析性能瓶颈:当应用出现性能问题时,可以使用 TraceView 和 Systrace 等工具来分析性能瓶颈,找出导致主线程阻塞的原因。这些工具能够提供详细的 CPU 使用情况、函数调用栈、线程状态等信息, 帮助开发者快速定位问题。
掌握 主线程的 Looper 消息循环 的原理,熟练运用 Handler、MessageQueue 和 ThreadLocal 等关键类,并结合实战经验,才能写出高性能、高可靠性的 Android 应用。 希望这篇文章能帮助你对 Looper 机制有更深入的理解。
冠军资讯
键盘上的咸鱼