Android 异步消息处理机制

前言

Android 的异步消息处理机制主要由 4 个部分组成:MessageHandlerMessageQueueLooper。本篇文章的结构如下:

  1. 通过例子来展示如何使用 Handler
  2. 通过对源码的分析,理解这 4 个部分之间存在着什么联系。
  3. Handler 可能会造成内存泄漏的原因进行分析以及解决方案。
  4. Loop.loop 方法为何不会阻塞主线程。

Handler 的使用

我们都知道在 Android 中,UI 是线程不安全的,如果不在主线程中更新 UI,就会抛出异常。而有时候,我们需要在子线程中处理一些耗时的逻辑,然后根据这些逻辑进行相应的 UI 更新。那么问题来了,子线程中是不能更新 UI 的,那么我们又要如何才能去更新 UI 呢?这时候就要用到 Handler 来进行处理了。

我们接下来写一个小例子,在这个例子中,我们有两个控件:TextViewButton。当我们按下按钮时,我们会开启一个子线程执行耗时操作,执行完成后 TextView 显示文字:下载完成! 代码如下:

public class MainActivity extends AppCompatActivity {

    private static final int DOWNLOAD_SUCCESS = 1;
    
    private TextView mTextView;
    private Button mButton;

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case DOWNLOAD_SUCCESS:
                    mTextView.setText("下载完成!");
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView) findViewById(R.id.text_view);
        mButton = (Button) findViewById(R.id.button);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 开启一个子线程
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            // 模拟耗时操作...
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        Message msg = new Message();
                        msg.what = DOWNLOAD_SUCCESS;
                        mHandler.sendMessage(msg);  // 发送消息给 Handler
                    }
                }).start();
            }
        });
    }
}

可以看到,在 onClick 方法中,我们开启了一个子线程,并在子线程中模拟下载 sleep 了 2s。接着创建了一个 Message 对象,并对 Message 对象的 what 成员赋值为 DOWNLOAD_SUCCESSDOWNLOAD_SUCCESS 在我们的开头进行了定义。接着调用 mHandlersendMessage 方法,将 Message 对象发送了出去。

那么发送出去的 Message 对象在哪里进行接收呢?答案就是第 8 行处我们的内部类 Handler。我们在这个 Handler 中重写了它的 handleMessage 方法,所有调用 mHandler 发送的消息最终都会在这个方法中进行处理。因为在 onClick 中我们是对 Messsagewhat 成员进行赋值的,那么在 handleMessage 中我们也是对 Messagewhat 成员进行值判断做出相应的 UI 更新,在这里我们就只是简单的对 TextView 进行了显示。

可以看到逻辑还是相对比较简单的吧,当然我们也可以使用 Handler 的 post 方法在子线程中进行 UI 的更新,它的代码如下:

public class MainActivity extends AppCompatActivity {

    private static final int DOWNLOAD_SUCCESS = 1;

    private TextView mTextView;
    private Button mButton;

    private Handler mHandler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView) findViewById(R.id.text_view);
        mButton = (Button) findViewById(R.id.button);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 开启一个子线程
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            // 模拟耗时操作...
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                mTextView.setText("下载完成!");
                            }
                        });
                    }
                }).start();
            }
        });
    }
}

可以看到,在这个例子中,我们就没有对 HandlerhandleMessage 方法进行重写,而是在 onClick 中,直接调用 mHandlerpost 方法,这个方法接收一个 Runnable 类型的参数,我们在 Runnablerun 方法中直接对我们的 UI 进行更新即可。它的实现效果和上面的例子是一样的。

在展示完 Handler 的例子之后,我们接下来就从源码的角度来分析 Handler 是如何完成我们的异步消息处理的。

源码分析

首先我们先来看一下 MessageMessageQueue 的相关源代码:

Message

Message 类的代码如下所示:

public final class Message implements Parcelable {

    public int what;

    public int arg1;

    public int arg2;

    public Object obj;
    
    ......

	/*package*/ long when;

	/*package*/ Handler target;
	
	......
}

首先看到前 4 个成员变量,我们会经常使用到这 4 个成员,其中 whatarg1arg2 均为 int 类型的,而 obj 则是 Object 类型的。如果我们仅仅需要传整型数据的话,我们应当尽可能的使用 arg1arg2,因为这两个的开销是比较小的。而如果是其他类型值的话,则应当使用 obj 来传递。

接着我们看到成员 when,它是 long 类型的,根据笔者的猜测它应该是用于记录 Message 执行时间的,它的作用会在 MessageQueue 中进行介绍。

最后看到成员 target,它是 Handler 类型的,它的作用是用于指定 Message 发送到哪个 Handler 上,它在调用 HandlersendMessagepost 等方法的时候 MessageQueue 被赋值。

还有一点,Message 对象的创建不建议使用 new 进行创建。Message 内部保存了一个缓存的消息池,我们可以使用 Messageobtain 方法从缓存池获得一个消息,Message 使用完后系统会调用 recycle 回收,如果 new 很多 Message,每次使用完后系统放入缓存池,会占用很多内存。所以我们上面的例子创建消息对象的方式其实不是比较好的一种方式,我们可以改成如下代码:

Message msg = Message.obtain(handler, DOWNLOAD_SUCCESS);

分析完 Message 之后,我们接下来看到 MessageQueue

MessageQueue

MessageQueue 顾名思义就是一个消息队列,它用于将我们的 Message 添加进队列中进行管理。

我们接下来重点分析 MessageQueuenext 方法以及 enqueueMessage 方法,它们分别用于从队列中取出 Message 以及将 Message 存入 MessageQueue 中,首先分析将消息添加进 MessageQueue 的方法 enqueueMessage

enqueueMessage

它的源码如下:

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

第 2 行, if 判断 msgtarget 是否为空,为空的话则抛出异常,从这里我们可以得知调用 enqueueMessage 方法添加消息进 MessageQueue 的时候 target 是不能为空的。

第 22~45 行,这个 if 判断有 3 个条件,分别是队列为空消息执行时间为 0 以及新添加的消息时候比消息队列头的执行时间还早。只要满足这 3 个之中的任意一个,这个消息就会被添加到队列的头部。否则的话就会将该队列添加到队列的中间去。

大致分析完了消息是如何添加进 MessageQueue 之后,接下来我们来分析从队列中取出消息的方法 next

next

next 方法的源码如下所示:

Message next() {
    ......
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
    
        ......

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

它的代码还是比较多的,在这里我们省去非相关代码,只分析核心流程:

第 5 行,进入到了一个无限循环当中,next 方法的主要逻辑就是在这个无限循环中完成的了。

第 13 行,获取队列中的第一个 Message 对象然后将它赋值给 msg,然后在第 14 行中,if 判断 msg 是否不为空以及 msg.target 是否为空,我们在前面提到过 msg 如果是通过 enqueueMessage 方法添加进队列的话,它的 target 一定不为空,而在我们使用 Handler 的过程中无论是调用 post 方法还是 sendMessage 方法最终都一定会调用 enqueueMessage 方法,所以这段我们就直接跳过。

第 21 行的 if 中,如果 msg 不为空的话,首先会在第 20 行的 if 中判断 now 是否大于 msgwhen,也就是 msg 的执行时间,如果小于的话,就会阻塞直到到达该 msg 的执行时间,否则的话,就会对队列中的 Message 进行重新处理过后返回 msg。而如果 msg 为空的话,就说明队列中已经不存在有 Message 了,就会将 nextPollTimeoutMillis 的值设置为 -1,表示队列中已不存在消息了。

第 44 行,在这个 if 判断中,如果 mQuittingtrue,就会执行 dispose 方法清除队列,然后返回 nullmQutting 的值是在 quit 方法中设置的,这个方法要调用的前提是 MessageQueue 必须是允许退出的,也就是 mQuitAllowedtruemQuitAllowed 在我们构造 MessageQueue 对象的时候可以进行设置。

第 52~60 行,这几行的逻辑是获取空闲 Handler 的数量,如果没有空闲的 Handler 的话,就会将 mBlock 设置为 true,表示阻塞以等待空闲的 Handler 来处理消息。紧接着就 continue 进入下一次的循环,依次类推。

在这里我们简单的对 next 方法做一个总结:

  • next 方法就是在一个无限循环中不断地寻找可返回的消息。
  • next 方法如果返回 null 只可能是一种情况:调用了 quit 方法退出了 MessageQueue
  • next 方法阻塞的原因有可能是队列中没有消息了或者当前没有空闲的 Handler 可以处理消息。

到这里我们的 MessageMessageQueue 就大致分析完了,我们接下来看看异步消息处理的完整步骤。

异步消息处理完整步骤

它可大致分为以下 3 步:

Looper.prepare();
Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        // 处理逻辑
    }
};
Looper.loop();
  1. 调用 Looper.prepare()
  2. 创建 Handler 对象。
  3. 调用 Looper.loop()

在这里你可能会有一个疑问,为什么在前面的例子中我们都没有调用过 Looper 的相关方法?

这是因为前面的例子均是在我们的主线程中创建 Handler 对象的。而在我们 APP 进行初始化的时候,就会调用 ActivityThreadmain 方法,我们可以看看这个方法的源代码:

public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();  // 关键点1

        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();  // 关键点2

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

可以看到在第 n 行的关键点 1 处,调用了 LooperprepareMainLooper 方法,而在第 n 行的关键点 2 处,调用了 Looperloop 方法,所以我们在主线程中创建 Handler 对象的时候,才可以省略掉这两个方法的调用,而且我们也不能在主线程中再次调用 Looper 的这两个方法,否则会抛出异常!

而在我们的子线程中,我们就一定要调用 Loopprepare 方法以及 loop 方法,否则的话就会抛出异常。另外一个要注意的点是这 3 个步骤的顺序不能发生更改,否则的话也会抛出异常。

好了,在介绍了这 3 个步骤之后,我们接下来就从 Looperprepare 方法开始来进行分析。

Looper.prepare()

我们直接来看到它的源码:

public static void prepare() {
    prepare(true);
}

可以看到,调用的是另一个带参的 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 方法的参数是一个标志位,表示是否允许退出,我们接下来看到方法体。

第 2 行,调用 sThreadLocalget 方法并判断是否为空,不为空则抛出异常。我们先来看看 sThreadLocal 是什么:

// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

从它的注释我们可以得知,如果我们在当前线程中还未调用 Looperprepare 方法,那么 sThreadLocal.get() 就会返回 null,否则的话就会返回一个 Looper 对象。我们接着回到带参数的 prepare 方法的第 2 行,如果 get 方法返回非空,说明我们已经创建过 Looper 对象了,那么它就会抛出异常。

继续看到第 5 行,如果 get 方法返回 null,说明我们还未创建 Looper 对象,那么就会调用 sThreadLocal.set 方法来创建一个 Looper 对象,我们来看看 Looper 的构造方法里面做了什么:

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

可以看到它做了两件事,第 2 行中它创建了一个 MessageQueue 队列,然后在第 3 行中保存了我们当前线程。

从这段代码中我们可以得知下面 3 点信息:

  1. Looperprepare 方法实际上是在当前的线程创建了一个 Looper 对象,而 Looper 对象实际上就是在内部维护了一个 MessageQueue 对象。
  2. 一个线程只能持有一个 Looper 对象,换句话说 prepare 方法在线程中只能调用一次,重复调用 prepare 方法会抛出异常。
  3. 这也就解释了为什么我们的主线程不需要调用 Looperprepare 方法(不仅不能,而且不准)。

分析完了 prepare 方法,接下来我们看到第 2 步:创建 Handler。

Handler

我们在前面说过,Handler 发送消息可以通过 postsendMessage 等方法发送,我们分别看到它们的源码:

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

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

可以看到,殊途同归,它们最终都是调用的同一个方法:sendMessageDelayed,我们来看看这个方法里面做了什么:

public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

这个方法又是调用的 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);
}

可以看到,这个方法中首先会判断 mQueue 是否为空,为空的话返回 false。否则的话就会继续调用 enqueueMessage 方法,我们继续看到这个方法的实现:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

可以看到我们 msg 的成员 target 就在这里进行了赋值,赋的值就是发送该消息的 Handler 对象。最后它会通过调用 MessageQueueenqueueMessage 方法将消息添加到消息队列中。前面我们讲过 enqueueMessage 方法如果 target 为空,就会抛出异常,而这里我们已经对 target 进行了设置,所以不会有这个问题,而 enqueueMessage 的返回值如果为 false,表示退出了消息队列,否则返回 true

分析到这里,我们可以得知,无论是调用 sendMessage 方法还是 post 方法,都会将 msgtarget 赋值为调用的 Handler 对象,然后将消息添加到消息队列中。

我们接下来重新回到 post 方法:

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

我们注意到 post 方法的参数是 Runnable,它是通过 getPostMessage 处理然后返回一个 Message 对象的,我们来看看这个方法里面做了什么:

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

可以看到它就是通过 obtain 方法创建了一个 Message 对象,然后将它的成员 callback 赋值为 r 就返回了。我们会在接下来的 loop 中看到调用不同的方法(sendMessagepost)产生的消息是如何进行处理分配的。

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

首先看到第 2 行,通过 myLooper 方法获得 Looper 对象赋值给 me,紧接着在第 3 行中,如果 me 的值为 null,那么就会抛出异常。我们来看看 myLooper 方法里面做了什么:

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

可以看到它就是调用了 sThreadLocalget 方法,它的 get 方法我们前面分析过了,如果调用了 Looper.prepare 方法的话,就会返回给我们 Looper 对象。

继续回到 loop 方法,看到第 6 行,这里我们获得了 me 对象的成员 mQueue,它是 MessageQueue 类型的,也就是我们所说的消息队列。

第 14 行,在无限循环里,我们调用了 MessageQueuenext 方法获得 Message。在这里可能会产生阻塞,原因我们在前面分析过了,是队列中没有消息或者没有空闲的 Handler 处理消息。

第 15 行,如果 msgnull,说明退出了 MessageQueue 队列,就会直接 return 跳出循环。退出调用的是MessageQueuequit 方法。

接下来直接看到第 n 行,在这里我们调用的是 msg.target.dispatchMessage() 方法,msg 的成员 target 我们知道就是发送该消息的 Handler,所以它是在 Handler 的方法,我们直接看到这个方法的实现:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

第 2 行,如果 msg.callback 不为空,调用的是 handleCallback 方法,msg.callback 不为空的是我们调用 post 方法的情况,我们看到 handleCallback 的实现:

private static void handleCallback(Message message) {
    message.callback.run();
}

可以看到,我们直接调用的就是 callbackrun 方法。

我们继续回到 dispatchMessage 方法,如果 msg.callback 为空,即调用的是 sendMessage 等其他方法,接下来会判断 mCallback 是否为空,mCallback 是一个 Callback 对象,它回调接口:

public interface Callback {
    /**
     * @param msg A {@link android.os.Message Message} object
     * @return True if no further handling is desired
     */
    public boolean handleMessage(Message msg);
}

一般我们很少使用到这个接口,不会去初始化这个接口,所以我们直接调用的就是 dispatchMessage 方法第 10 行的 handleMessage 方法,我们来看看这个方法的实现:

/**
 * Subclasses must implement this to receive messages.
 */
public void handleMessage(Message msg) {
}

可以看到这是一个空方法,而具体的实现是留待我们的子类去自行重写的,在我们前面的例子中我们重写的就是这个 handleMessage 方法。

分析到这里,我们对 Looper.loop 的主要流程就分析完了,我们对它进行一个简要的总结:

  • loop 首先会检查是否创建了 Looper 对象,为空的话会抛出异常,而 Looper 对象是在调用 prepare 方法的时候创建的。所以如果没有调用 Looper.prepare(),也会抛出异常。
  • loop 会在一个死循环中不断地通过 MessageQueuenext 方法获取消息,调用 next 方法可能会发生阻塞,阻塞原因可能是没有空闲的 Handler 或者队列中没有消息了。
  • loop 在取得消息之后会通过调用 HandlerdispatchMessage 方法对消息处理进行分发,如果 msgcallback 不为空,就调用 handleCallback 方法,否则的话还会检查是否设置了回调,如果设置了回调就调用回调的 handleMessage 方法,没有的话直接调用 handleMessage 方法,一般情况下我们不会设置回调。
  • 从上面第 3 点我们也可以得知,处理消息的优先级是 handleCallback > mCallback.handleMessage > handleMessage。这也解释了为什么我们通过 post 发送消息时不需要重写 handleMessage 方法。

通过这些分析,MessageMessageQueueLooperHandler 之间的关系也就串联起来了。我们来讲它们之间的关系做一个简要的总结。

Message、MessageQueue、Looper 和 Handler 之间的关系

Message 就是我们的消息,我们在它里面保存我们需要发送的信息。

MessageQueue 就是我们的消息队列,我们的消息就是保存在我们的消息队列中进行处理的。在这里它是通过 enqueueMessage 方法将消息添加进队列,通过 next 方法从队列中取出消息。

Looper 我们可以理解为一个无限循环,调用 Looper.prepare 方法是为了创建出一个 Looper 对象,注意到每个线程中只能有一个 Looper 对象。而 Looper 对象中又维护了一个 MessageQueue 用来处理我们的 Message

MessageQueue 是通过 Handler 调用 sendMessagepost 等方法来添加消息的,这些方法最终都是调用到 MessageQueueenqueueMessage 方法。

MessageQueue 是通过 Looper.loop 方法来取出消息进行处理的,在 loop 方法内部我们首先会通过 MessageQueuenext 方法取出队列中的消息,注意到这个方法可能会产生阻塞,然后通过 HandlerdispatchMessage 方法处理消息。

HandlerdispatchMessage 方法会对消息处理进行分发,优先级是 handleCallback > mCallback.handleMessage > handleMessagepost 方法发送的消息通过 handleCallback 处理,sendMessage 方法发送的消息通过 handleMessage 方法进行处理,handleMessage 默认是个空方法,需要我们自己去实现。

我们接下来通过一幅图来描绘它们之间的关系:
在这里插入图片描述
至此,异步消息机制中的这 4 个部分之间的联系,我们就找出来了。接下来我们来解决最后一个问题:Handler 可能造成的内存泄漏问题。

Handler 的内存泄漏分析

我们都知道,Java 是通过GC来回收垃圾的,如果GC发现一个或一组对象为不可达状态,就会将该对象从内存中回收。那么我们就来看我们上面的例子中 Handler 的创建方式:

public class MainActivity extends AppCompatActivity {

	......

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
        	// 具体逻辑
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ......
    }
}

可以看到,HandlerMainActivity 的一个内部类,这段代码会有造成内存泄漏的风险。我们来分析一下原因。

我们可以想象一下,我们在使用到 Handler 的时候,一般情况下都是在子线程中进行耗时的操作,例如下载,然后在操作完成后使用 Handler 通知我们的主线程做相应的操作。此时我们外部类 Handler 持有了我们 MainActivity 对象的一个引用。

如果在我们的下载过程中,用户关闭了 Activity,正常情况下,MainActivity 这个对象会被GC给回收掉,但此时由于下载所在的线程未执行完下载任务,所以我们的线程持有对 Handler 的引用,而我们的 Handler 又持有对 MainActivity 的引用,GC也就无法回收掉我们的 MainActivity 对象了,要一直等待下载任务结束后,GC才会去回收我们的 MainActivity 对象。

假设我们在下载还未完成的时候,不断地打开关闭 MainActivity,那么在内存中就会不停地创建 MainActivity 对象,最终内存占用过高,导致了内存泄漏(OOM)。

而另一种情况就是我们使用 sendMessageDelayedpostDelayed 等延迟发送消息的方法时,Handler 内部在这段延时期间会持有 Message 对象,当在这段时间内关闭 MainActivity 的时候,由于 Handler 持有对 Message 的引用,所以 Handler 无法被GC回收,所以 MainActivity 也会造成无法被GC回收的现象。

那么在知道造成潜在的内存泄漏的原因之后,我们接下来就来看看如何解决这种潜在的内存泄漏。下面有 2 种解决方法:

1.使用静态内部类

静态内部类不持有外部类的引用了,所以在外部类如 MainActivity 关闭的时候,GC就能回收 MainActivity 对象了。但是现在还有一个问题,当我们使用静态内部类的时候,由于 Handler 此时不持有对 MainActivity 的引用了,所以它也就无法直接对 MainActivity 中的对象进行操作了。所以我们在这里还需要用到弱引用来辅助我们产生对 MainActivity 的引用,我们来改写之前的例子看看:

public class MainActivity extends AppCompatActivity {

    private MyHandler handler;
    private TextView mTextView;
    private Button mButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        handler = new MyHandler(this);
        mTextView = (TextView) findViewById(R.id.text_view);
        mButton = (Button) findViewById(R.id.button);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Message msg = new Message();
                handler.sendMessage(msg);
            }
        });
    }

    private static class MyHandler extends Handler {
		// 内部持有对 MainActivity 的弱引用
        private WeakReference<MainActivity> mActivity;

        MyHandler(MainActivity activity){
            this.mActivity = new WeakReference<MainActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            final MainActivity activity = mActivity.get();
            if(activity != null){
                activity.mTextView.setText("下载成功!");
            }
        }
    }
}

可以看到,我们在这里就使用了静态内部类实现了 Handler,并在 Handler 中维护了一个对 MainActivity 的弱引用成员,因为弱引用会在适当的时候被GC回收掉,所以在我们每次通过弱引用获取 MainActivity 的对象时,应当进行判空,防止出现空指针异常。

2.在代码中添加适当的逻辑

在我们 MainActivity 关闭时,我们在 onDestroy 方法添加适当的逻辑让耗时的子线程关闭就可以让 MainActivity 被GC给回收了。

而如果是在调用 sendMessageDelayed 等延时发送的方法造成的内存泄漏时,可以在 onDestroy 中调用 HandlerremoveCallbacks 方法来清除消息队列中的消息即可。

Loop.loop 为何不会阻塞主线程

我们在前面分析 loop 方法的源码的时候,都知道 loop 方法里面就是一个死循环,如果没有调用 quit 方法的话,是永远不会退出来的,我们看下面一个例子:

new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();
        handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
            }
        };
        Log.d(TAG, "before loop()");
        Looper.loop();
        Log.d(TAG, "after loop()");
    }
}).start();

我们在 loop 方法的调用前和调用后分别打印 Log,然后运行发现只打印出如下信息:

before loop()

说明在调用了 loop 方法之后,确实就会阻塞掉这个子线程。那么问题来了,既然我们的主线程中也是有调用 Loop.loop 方法的,那么为何它不会阻塞主线程呢?我们直接来看到源码,再次回到 ActivityThreadmain 方法中:

public static void main(String[] args) {
    ......
    Looper.prepareMainLooper();

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

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();  // 获取 Handler
    }

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

我们在这里重新对 main 方法进行比较仔细的分析:

第 3 行,这里调用的是 Loop.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();
    }
}

它调用了 prepare 方法,传入的值是 false,我们看到 prepare 方法:

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

这个方法我们之前分析过,传入的参数为 false 表示主线程中的 Looper 不允许退出,即不能调用 Looperquit 方法,也就是说,Looper.loop 方法一旦调用,就再也不会从死循环中退出来了。

接下来继续看到第 9 行的代码行,在这个 if 中,如果 sMainThreadHandler 值为空,那么就会调用 getHandler 方法进行赋值,sMainThreadHandlerHandler 类型的,那么我们就来看看 getHandler 方法的实现:

final Handler getHandler() {
    return mH;
}

返回的是 mH,它是 H 类型的成员变量,我们来看看 H 这个类是如何定义的:

private class H extends Handler {
  public static final int LAUNCH_ACTIVITY         = 100;
  public static final int PAUSE_ACTIVITY          = 101;
  public static final int PAUSE_ACTIVITY_FINISHING= 102;
  public static final int STOP_ACTIVITY_SHOW      = 103;
  public static final int STOP_ACTIVITY_HIDE      = 104;
  ......

  String codeToString(int code) {
      if (DEBUG_MESSAGES) {
          switch (code) {
              case LAUNCH_ACTIVITY: return "LAUNCH_ACTIVITY";
              case PAUSE_ACTIVITY: return "PAUSE_ACTIVITY";
              case PAUSE_ACTIVITY_FINISHING: return "PAUSE_ACTIVITY_FINISHING";
              ......
          }
      }
      return Integer.toString(code);
  }
  public void handleMessage(Message msg) {
      if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
      switch (msg.what) {
          case LAUNCH_ACTIVITY: {
              Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
              final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

              r.packageInfo = getPackageInfoNoCheck(
                      r.activityInfo.applicationInfo, r.compatInfo);
              handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
              Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
          } break;
          case RELAUNCH_ACTIVITY: {
              Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
              ActivityClientRecord r = (ActivityClientRecord)msg.obj;
              handleRelaunchActivity(r);
              Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
          } break;
          case PAUSE_ACTIVITY: {
              Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
              SomeArgs args = (SomeArgs) msg.obj;
              handlePauseActivity((IBinder) args.arg1, false,
                      (args.argi1 & USER_LEAVING) != 0, args.argi2,
                      (args.argi1 & DONT_REPORT) != 0, args.argi3);
              Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
          } break;
          case PAUSE_ACTIVITY_FINISHING: {
              Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
              SomeArgs args = (SomeArgs) msg.obj;
              handlePauseActivity((IBinder) args.arg1, true, (args.argi1 & USER_LEAVING) != 0,
                      args.argi2, (args.argi1 & DONT_REPORT) != 0, args.argi3);
              Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
          } break;
          ......
      }
}

可以看到这是一个继承自 Handler 的类,它的内部通过重写 handleMessage 方法,对不同类型的事件进行相应的逻辑处理,Activity 的生命周期都有相对应的 case 条件。

所以在 Activity 中实际的执行过程应该是通过 sMainThreadHandler 对象发送消息,然后发送的消息会交由 Looperloop 方法中进行分发,最后通过 HhandleMessage 方法进行相对应的处理。所以 loop 方法不会阻塞主线程,应该说 Activity 就置于这个无限循环中通过消息处理机制不停地进行处理。

当然这里对于 ActivtyThread 中的 loop 只是进行了一个简单的解释,如果需要更加深入地了解这个问题,就需要引入 Binder 机制了,Binder 机制的文章笔者会另开一篇博客做介绍,我们对这个问题的分析也就先到这里吧!

空格空格空格
我们的文章到这里也就结束了,异步消息处理机制是 Android 非常重要的一块知识点,也是其它知识的基础,希望这篇文章能够带给大家一些收获,有问题的话可以在下方评论区给我留言!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值