从源码的角度解析Handler、Looper、Message和MessageQueue
发布日期:2021-07-19 12:30:04 浏览次数:12 分类:技术文章

本文共 18123 字,大约阅读时间需要 60 分钟。

原文链接:

作为一名Android程序猿,想必在最开始都碰到这么一个问题,就是在子线程中弹出一个 Toast,会抛出以下的异常:

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()    at android.os.Handler.
(Handler.java:121) at android.widget.Toast$TN.
(Toast.java:322) at android.widget.Toast.
(Toast.java:91) at android.widget.Toast.makeText(Toast.java:238) at com.example.testapp.MyActivity$MyAsyncTask.doInBackground(MyActivity.java:25) at com.example.testapp.MyActivity$MyAsyncTask.doInBackground(MyActivity.java:21)

按传统的说法就是 Toast 只能在UI线程中显示,实际上不是的,应该是 Toast 只能在带有 Looper 的线程中显示。

另一个常见的场景,就是在子线程创建一个 Handler 也会抛出与上述一样的异常:

Java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()    at android.os.Handler.
(Handler.java:121)

而在主线程为什么不会有该异常呢?这就是源码的东西了,在程序入口方法已经调用过以下代码,来创建一个主线程了:

Looper.prepareMainLooper();ActivityThread thread = new ActivityThread();thread.attach(false);Looper.loop();

先来看一张图,初步了解一下 HandlerLooperMessageMessageQueue 之间的关系

对于一个线程来说,Handler 允许发送和处理与该线程的 MessageQueue 相关联的 Message 或 Runnable 对象。每一个 Handler 实例与单个线程的 MessageQueue 相关联。

当我们创建一个 Handler 实例的时候,它将会绑定到当前所处的线程及其对应的消息队列,然后就是往消息队列里面传递消息或者 Runabble对象,并在消息队列中取出消息来处理,或者取出 Runnable 对象进行执行!

Handler

从本质上来讲,Handler 主要有以下两个作用

调度消息和runnable对象去被执行,换句话说,就是在同一个线程中处理一些消息

使得某个消息动作在不同线程中执行,也就是往其他线程的消息队列里插入消息
消息的调度主要有以下几种方式:

  • post(Runnable)
  • postAtTime(Runnable,long)
  • postDelayed(Runnable,long)
  • sendMessage(Message)
  • sendMessageAtTime(Message,long)
  • sendMessageDelayed(Message,long)
  • sendEmptyMessage(int)
  • sendEmptyMessageAtTime(int, long)
  • sendEmptyMessageDelayed(int, long)

最后实际上都是调用 sendEmptyMessageAtTime(Message,long) 方法

从上面的这些方法中可以看出:

post开头的几个方法,允许将 Runnable 对象插入到消息队列以便调用。

sendMessage 对应的几个方法,可以将 Message 插入到 MessageQueue,然后通过 Handler 的 handleMessage 方法来处理相应的消息。
 

Message

Message 结构

Message 类主要包含以下几个参数

public int what; // sendEmptyMessage 里面的 what,在 ```handleMessage``` 方法可以对不同的 Message.what 值做相应处理。public Object obj; // Message 可以携带一个对象Handler target; // 处理该消息的HandlerMessage next;Runnable callback; // 消息处理动作

1、从next参数可知,消息队列实际上是一个链表结构;

2、来看一下 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);    }}

从中我们可以知道,如果 Message 有定义 callback,那么消息处理会交由callback 去执行,否则,交由 Handler 的 handleMessage 去执行。

Message 创建及发送

一般发送一条消息,我们会调用以下代码:

handler.obtainMessage(int what, Object obj).sendToTarget();

那么,我们就简单分析一下消息创建的流程

1、Handler.obtainMessage

public final Message obtainMessage(int what, Object obj){    return Message.obtain(this, what, obj);}

2、 Message.obtain 创建消息

public static Message obtain(Handler h, int what, Object obj) {    Message m = obtain();    m.target = h; // 指定了处理消息的Handler    m.what = what;    m.obj = obj;    return m;}

3、 Message.sendToTarget 发送消息

public void sendToTarget() {    target.sendMessage(this); // 调用 Handler的sendMessage}

4、Handler.sendMessage(Message) 发送消息,最后实际上是调用sendMessageAtTime方法,往MessageQueue里面插入一条消息

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

至此,消息就发送完毕,也就是插入到了消息队列里面,接下来就是由消息队列处理了。

MessageQueue

MessageQueue 结构

private final boolean mQuitAllowed; // 是否允许MessageQueue退出;private long mPtr; // MessageQueue 是通过调用 C++ native MessageQueue 实现的,这个 mPtr 就是指向 native MessageQueue;Message mMessages; // 表示存储消息链表的 Headprivate boolean mQuitting; // 当前MessageQueue是否正在终止;private boolean mBlocked; // 表示next()方法调用是否被block在timeout不为0的pollOnce上;

MessageQueue 主要包含两个操作:插入和读取。读取操作本身会伴随着删除操作,插入和读取对应的方法分别是 enqueueMessage()和 next()

插入消息

boolean enqueueMessage(Message msg, long when) {    if (msg.target == null) {        throw new IllegalArgumentException("Message must have a target.");    }    if (msg.isInUse()) {        throw new IllegalStateException(msg + " This message is already in use.");    }    synchronized (this) {        if (mQuitting) {            IllegalStateException e = new IllegalStateException(                    msg.target + " sending message to a Handler on a dead thread");            Log.w(TAG, e.getMessage(), e);            msg.recycle();            return false;        }        msg.markInUse();        msg.when = when;        Message p = mMessages;        boolean needWake;        // 插入到链表的头部,条件:链表为null或者当前消息的对应的触发时间比链表头的触发时间小,也就是比链表头早执行        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 {            // 通过触发时间,将消息插入到队列中合适的位置            // 如果需要唤醒线程处理则调用C++中的nativeWake()函数.            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;            prev.next = msg;        }        // We can assume mPtr != 0 because mQuitting is false.        if (needWake) {            nativeWake(mPtr);        }    }    return true;}

读取消息

消息循环读取,是在 Looper.loop() 方法调用之后,最后来执行 MessageQueue.next() 方法,我们来看一下该方法:

Message next() {    // Return here if the message loop has already quit and been disposed.    // This can happen if the application tries to restart a looper after quit    // which is not supported.    final long ptr = mPtr;    if (ptr == 0) {        return null;    }    int pendingIdleHandlerCount = -1; // -1 only during first iteration    int nextPollTimeoutMillis = 0;    // 无限循环,往消息队列里面取消息    for (;;) {        if (nextPollTimeoutMillis != 0) {            Binder.flushPendingCommands();        }        nativePollOnce(ptr, nextPollTimeoutMillis);        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) {                // 过滤掉同步消息                do {                    prevMsg = msg;                    msg = msg.next;                } while (msg != null && !msg.isAsynchronous());            }            if (msg != null) {                if (now < msg.when) {                    // 还未达到下一条消息的触发时间,为下一条待处理的消息设置就绪时间                     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();                    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.            if (pendingIdleHandlerCount < 0                    && (mMessages == null || now < mMessages.when)) {                pendingIdleHandlerCount = mIdleHandlers.size();            }            if (pendingIdleHandlerCount <= 0) {                // No idle handlers to run.  Loop and wait some more.                mBlocked = true;                continue;            }            if (mPendingIdleHandlers == null) {                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];            }            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);        }        // Run the idle handlers.        // We only ever reach this code block during the first iteration.        for (int i = 0; i < pendingIdleHandlerCount; i++) {            final IdleHandler idler = mPendingIdleHandlers[i];            mPendingIdleHandlers[i] = null; // release the reference to the handler            boolean keep = false;            try {                keep = idler.queueIdle();            } catch (Throwable t) {                Log.wtf(TAG, "IdleHandler threw exception", t);            }            if (!keep) {                synchronized (this) {                    mIdleHandlers.remove(idler);                }            }        }        // Reset the idle handler count to 0 so we do not run them again.        pendingIdleHandlerCount = 0;        // While calling an idle handler, a new message could have been delivered        // so go back and look again for a pending message without waiting.        nextPollTimeoutMillis = 0;    }}

通过上面源码可知:

首先会去判断handler是否为null,是的话就跳过所有的同步消息,查找到需要最先处理的异步消息。如果第一个待处理的消息还没有到要触发时间,则设置激活等待时间;否则这个消息就是需要处理的消息,将该消息设置为 inuse状态,并将队列设置为非 blocked 状态,然后返回该消息。

next() 方法是一个无限循环的方法,如果消息队列中没有消息,那么 next() 方法会一直阻塞,当有新消息到来时,next() 会将这条消息返回同时也将这条消息从链表中移除。

Looper

首先,在理解 Looper 之前,我们需要稍微了解一下 ThreadLocal 这个类。

ThreadLocal 是用于为每个线程创建一个单独的变量副本,提供了保持对象的方法和避免参数传递的复杂性。ThreadLocal 类有一个泛型参数,设置了保存到 ThreadLocal 容器中的数据类型。

实际上在 ThreadLocal 类中有一个静态内部类 ThreadLocalMap (其类似于Map),用键值对的形式存储每一个线程的变量副本,ThreadLocalMap 中元素的key为当前 ThreadLocal 对象,而value对应线程的变量副本,每个线程可能存在多个 ThreadLocal。

那么,在 Looper中,也存储该着为每个线程单独创建的 ThreadLocal,里面保存着该线程对应的 Looper。

Looper 创建

我们来看一下 Looper.prepare() 方法:

static final ThreadLocal
sThreadLocal = new ThreadLocal
();public static void prepare() { prepare(true);}private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { // 这也就意味着 prepare 方法,创建了当前线程的一个 Looper,并且每个线程 只能创建一次 throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed));}

Looper 开启循环

来看一下 Looper.loop() 方法:

/** * Run the message queue in this 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.getTraceName(msg));        }        try {            // 分发消息给 Handler 处理            msg.target.dispatchMessage(msg);        } finally {            if (traceTag != 0) {                Trace.traceEnd(traceTag);            }        }        if (logging != null) {            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);        }        // 回收释放        msg.recycleUnchecked();    }}

线程阻塞问题

细心的童鞋就会有个疑问,主线程对应的这个 Looper,在调用 Looper.loop() 方法之后,开启了无限死循环,那么为什么不会造成线程阻塞,导致 UI 动不了?

这个问题实际上就需要了解一下 Activity 的启动过程了。这里就不细说了,主要先了解一下 ActivityThread.H 这个类,它是继承于 Handler,我们可以看一下他的消息处理:

 

private final class H extends Handler {    public void handleMessage(Message msg) {        if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + msg.what);        switch (msg.what) {            case LAUNCH_ACTIVITY: {                ActivityClientRecord r = (ActivityClientRecord)msg.obj;                r.packageInfo = getPackageInfoNoCheck(                        r.activityInfo.applicationInfo);                handleLaunchActivity(r, null);            } break;            case RELAUNCH_ACTIVITY: {                ActivityClientRecord r = (ActivityClientRecord)msg.obj;                handleRelaunchActivity(r, msg.arg1);            } break;            case PAUSE_ACTIVITY:                handlePauseActivity((IBinder)msg.obj, false, msg.arg1 != 0, msg.arg2);                maybeSnapshot();                break;            case PAUSE_ACTIVITY_FINISHING:                handlePauseActivity((IBinder)msg.obj, true, msg.arg1 != 0, msg.arg2);                break;            case STOP_ACTIVITY_SHOW:                handleStopActivity((IBinder)msg.obj, true, msg.arg2);                break;            case STOP_ACTIVITY_HIDE:                handleStopActivity((IBinder)msg.obj, false, msg.arg2);                break;            case SHOW_WINDOW:                handleWindowVisibility((IBinder)msg.obj, true);                break;            case HIDE_WINDOW:                handleWindowVisibility((IBinder)msg.obj, false);                break;            case RESUME_ACTIVITY:                handleResumeActivity((IBinder)msg.obj, true,                        msg.arg1 != 0);                break;            case SEND_RESULT:                handleSendResult((ResultData)msg.obj);                break;            case DESTROY_ACTIVITY:                handleDestroyActivity((IBinder)msg.obj, msg.arg1 != 0,                        msg.arg2, false);                break;            case BIND_APPLICATION:                AppBindData data = (AppBindData)msg.obj;                handleBindApplication(data);                break;            case EXIT_APPLICATION:                if (mInitialApplication != null) {                    mInitialApplication.onTerminate();                }                Looper.myLooper().quit();                break;            case NEW_INTENT:                handleNewIntent((NewIntentData)msg.obj);                break;            case RECEIVER:                handleReceiver((ReceiverData)msg.obj);                maybeSnapshot();                break;            case CREATE_SERVICE:                handleCreateService((CreateServiceData)msg.obj);                break;            case BIND_SERVICE:                handleBindService((BindServiceData)msg.obj);                break;            case UNBIND_SERVICE:                handleUnbindService((BindServiceData)msg.obj);                break;            case SERVICE_ARGS:                handleServiceArgs((ServiceArgsData)msg.obj);                break;            case STOP_SERVICE:                handleStopService((IBinder)msg.obj);                maybeSnapshot();                break;            case REQUEST_THUMBNAIL:                handleRequestThumbnail((IBinder)msg.obj);                break;            case CONFIGURATION_CHANGED:                handleConfigurationChanged((Configuration)msg.obj);                break;            case CLEAN_UP_CONTEXT:                ContextCleanupInfo cci = (ContextCleanupInfo)msg.obj;                cci.context.performFinalCleanup(cci.who, cci.what);                break;            case GC_WHEN_IDLE:                scheduleGcIdler();                break;            case DUMP_SERVICE:                handleDumpService((DumpServiceInfo)msg.obj);                break;            case LOW_MEMORY:                handleLowMemory();                break;            case ACTIVITY_CONFIGURATION_CHANGED:                handleActivityConfigurationChanged((IBinder)msg.obj);                break;            case PROFILER_CONTROL:                handleProfilerControl(msg.arg1 != 0, (ProfilerControlData)msg.obj);                break;            case CREATE_BACKUP_AGENT:                handleCreateBackupAgent((CreateBackupAgentData)msg.obj);                break;            case DESTROY_BACKUP_AGENT:                handleDestroyBackupAgent((CreateBackupAgentData)msg.obj);                break;            case SUICIDE:                Process.killProcess(Process.myPid());                break;            case REMOVE_PROVIDER:                completeRemoveProvider((IContentProvider)msg.obj);                break;            case ENABLE_JIT:                ensureJitEnabled();                break;            case DISPATCH_PACKAGE_BROADCAST:                handleDispatchPackageBroadcast(msg.arg1, (String[])msg.obj);                break;            case SCHEDULE_CRASH:                throw new RemoteServiceException((String)msg.obj);        }        if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + msg.what);    }}

那么,对于创建一个Service来说,我们看一下 ApplicationThread 的调度:

public final void scheduleCreateService(IBinder token,        ServiceInfo info, CompatibilityInfo compatInfo, int processState) {    updateProcessState(processState, false);    CreateServiceData s = new CreateServiceData();    s.token = token;    s.info = info;    s.compatInfo = compatInfo;    // 发送创建 Service 的消息    sendMessage(H.CREATE_SERVICE, s);}

不难看出,实际上创建 Service、包括执行其生命周期,最后都是交由 ActivityThread.H 处理,包括 Activity 的生命周期,也是一样,所以 Looper 虽然死循环,但是本质上我们UI的展示、更新,也是通过 Handler 来处理了,所以并不会造成真正的UI阻塞。

所以,简单来讲,ActivityThread 实际上就是开启了一个消息循环,来执行 Activity、Service 等等的相关操作,一旦这个消息循环停止了,则意味着App进程也结束了。

但是,如果 handlerMessage 是在主线程执行,其处理尽可能不要执行耗时操作,避免UI卡顿或发生 ANR。

 

 

转载地址:https://blog.csdn.net/jdsjlzx/article/details/97803175 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:从源码解析-Activity的setContentView是怎么加载XML视图的
下一篇:Android系统启动流程—— init进程zygote进程SystemServer进程启动流程

发表评论

最新留言

关注你微信了!
[***.104.42.241]2024年04月23日 16时18分53秒