Android Handler消息机制详解(附带源码)

本文详细解读Handler、Looper、MessageQueue和Message在Android中的协作,通过源码剖析其工作原理,包括如何创建、使用和避免内存泄露,以及Activity的runOnUiThread和View的post方法的应用。
摘要由CSDN通过智能技术生成

Handler消息机制主要涉及到四个类:Handler、Looper、MessageQueue以及Message。官方给出的使用例子如下:

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

下面将介绍基本概念,并通过源码来讲解其原理(源码版本:Android 11.0(R,30))。

1.基本概念:

1.1 Looper

Looper:Looper是安卓消息循环的核心,它主要有以下职责:

  1. 创建并维护消息队列(MessageQueue mQueue);
  2. 关联调用线程(执行Looper.prepare方法的线程);
  3. 在关联的线程中启动消息循环(message loop),不断地将消息从消息队列中取出然后进行派发(dispatchMessage);

Looper类中具有如下常用的静态方法和实例方法:

(1)prepare:给当前的调用线程初始化并关联一个Looper实例,调用该方法后我们就可以实例化一个或多个handler实例来引用该Looper实例。关键源码节选如下:

    /** 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()}.
      */
    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));    // 将Looper实例保存到sThreadLocal中
    }

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);    // 初始化消息队列
        mThread = Thread.currentThread();          // 关联当前线程
    }

(2)loop:为当前线程启动消息循环。

源码中实际上就是在一个死循环中通过调用mQueue的next方法不断地从消息队列中获取消息,然后根据msg.target来获取到消息的源头(产生消息的Handler实例),最后通过该实例的dispatchMessage方法来处理该消息。

部分关键源码节选如下:

public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        // 调用loop方法前一定要调用prepare初始化Looper实例,否则会抛出如下异常
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;
    
    // 源码省略...

    for (;;) {
        Message msg = queue.next(); // might block
    
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        // 源码省略...
        
        try {
            msg.target.dispatchMessage(msg);    // 转到消息来源的Handler来处理该消息
            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);
             }
        }

        // 源码省略...

        msg.recycleUnchecked();
    }
}

从上面的代码我们可以看出,Looper.loop()方法后的代码是不会执行的,因为该方法内部是一个死循环(阻塞式查询消息队列中是否有新的消息,并进行处理),除非我们调用下面的quit和quitSafely方法,loop方法才会终止。

(3)quit / quitSafely:终止当前的消息循环。源码如下:

// Looper中源码:
public void quit() {
    mQueue.quit(false);
}

public void quitSafely() {
     mQueue.quit(true);
}

// MessageQueue中源码:
void quit(boolean safe) {
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }

    synchronized (this) {
        if (mQuitting) {
            return;
        }
        mQuitting = true;

        if (safe) {
            removeAllFutureMessagesLocked();
        } else {
            removeAllMessagesLocked();
        }

        // We can assume mPtr != 0 because mQuitting was previously false.
        nativeWake(mPtr);
    }
}

quit和quitSafely方法都可以通过mQuitting这个标记位来终止Looper.loop中的消息循环(死循环退出),从而不再接收和处理任何从handler传递过来的消息。它们的区别在于终止时对于消息队列中剩余消息的处理方式上:

a. quit:执行了MessageQueue#removeAllMessagesLocked方法,这意味着会清空消息队列中的所有消息,包括延时消息和即时消息(延时消息就是通过postDelayed,sendMessageDelayed等方法发送的消息)。

b. quitSafely:执行了MessageQueue#removeAllFutureMessagesLocked方法,这意味着只会清空消息队列中的延时消息,而即时消息会在消息循环终止前,被分发给对应的handler进行处理。

(4)myLooper:获取当前线程关联的Looper实例。源码如下:

    /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

(5)getThread:获取当前Looper对象关联的线程。源码如下:

    /**
     * Gets the Thread associated with this Looper.
     *
     * @return The looper's thread.
     */
    public @NonNull Thread getThread() {
        return mThread;
    }

(6)getMainLooper:在任意的其他线程中获取主线程的Looper实例。

    /**
     * Returns the application's main looper, which lives in the main thread of the application.
     */
    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

(7)isCurrentThread:当前调用此方法的looper实例所关联的线程是否为当前线程。

    /**
     * Returns true if the current thread is this looper's thread.
     */
    public boolean isCurrentThread() {
        return Thread.currentThread() == mThread;
    }

通过(4)(5)(6)(7)等方法,我们可以判断当前线程是否为主线程,例如:

Looper.myLooper() = Looper.getMainLooper();
Looper.getMainLooper().getThread() == Thread.currentThread();
Looper.getMainLooper().isCurrentThread();

Looper中还有一些其他不太常用的方法,可以自行查阅官方文档或者是源码。

 

1.2 Handler

Handler类主要有如下职责:

  1. 对消息进行处理,定义相关回调;
  2. 发送延时或即时消息;

Handler具有如下公开可用的构造函数(除了用@UnsupportedAppUsage注解标记的):

public Handler() {
    this(null, false);
}

public Handler(@Nullable Callback callback) {
    this(callback, false);
}

public Handler(@NonNull Looper looper) {
    this(looper, null, false);
}

public Handler(@NonNull Looper looper, @Nullable Callback callback) {
    this(looper, callback, false);
}

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 " + Thread.currentThread()
                + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

@UnsupportedAppUsage
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

Handler具有如下常用的方法:

(1)handleMessage(Message msg):这是用于消息处理的方法,需要我们去实现。

    /**
     * Callback interface you can use when instantiating a Handler to avoid
     * having to implement your own subclass of Handler.
     */
    public interface Callback {
        /**
         * @param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
        boolean handleMessage(@NonNull Message msg);
    }
    
    /**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(@NonNull Message msg) {
    }

(2)post(Runnable r):Runnable对象会被包装为Message对象,然后立即添加到消息队列中。

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

// 将Runnable对象封装到Message对象的callback属性上
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

(3)postAtTime(Runnable r, long uptimeMillis):与上面的方法类似,只不过会在指定时刻延时发送该消息。

public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis) {
    return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}

(4)postDelayed(Runnable r, long delayMillis):与上面的方法类似,只不过会延时指定的时间发送该消息。

public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}

(5)sendEmptyMessage(int what):发送一个空消息。

(6)sendMessage(Message msg):立即发送构造好的消息。

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

(7)sendMessageAtTime(Message msg, long uptimeMillis):在指定时刻延时发送该消息。

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

(8)sendMessageDelayed(Message msg, long delayMillis):在延迟指定时间后发送该消息。

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

通过上面的api源码,我们不难发现,所有api最后的落脚点都是sendMessageAtTime方法。而在该方法中所有的消息都是通过enqueueMessage方法提交到消息队列中的,并且最终调用的是MessageQueue#enqueueMessage方法。

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        // 这一句很重要,looper实例就是通过msg的target属性找到当前提交该消息的handler实例,进而交给handler中的handleMessage或callback进行处理
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

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

通过上面的源码,我们基本能够大致地梳理清楚Handler内部提交消息的具体流程。下面总结一下Handler是如何与Looper及MessageQueue关联起来的。

(1)在Handler的构造函数中,会通过Looper.myLooper()方法来获取当前线程关联的looper实例(除非我们指定looper),这就要求我们在子线程中实例化handler前,必须先调用Looper.prepare方法。

(2)在步骤(1)中,handler实例持有了对应的looper实例,自然就能够获取到对应的消息队列实例mQueue。

(3)调用mQueue中提供的入队方法,将封装好的Message对象添加到消息队列中。

接下来你可能会好奇,looper实例是如何消息循环中将msg交回给对应的handler实例进行处理的。其中的关键点就在于Handler#enqueueMessage方法中的:

msg.target = this;

也就是说,所有提交到消息队列的消息中都有一个target字段指向提交该msg的handler实例。回看Looper.loop方法中的:

msg.target.dispatchMessage(msg);

接下来就看看dispatchMessage中的逻辑:

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


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

消息处理的逻辑如下:

(1)如果msg.callback属性不为null,则直接调用callback的run方法,也就是直接执行通过post相关api提交的Runnable对象的run方法。

(2)如果不是情况(1),则判断mCallback是否为null,是的话则执行mCallback的handleMessage方法,并且根据该方法的返回值,决定是否继续调用handler实例的handleMessage(这个mCallback是在实例化Handler对象时传入的,可以回看一下上面Handler的构造函数源码,这个mCallback主要作用是可以让我们根据需求来拦截Handler的消息)。

 

1.3 Message

Message对象是消息的载体,主要包括如下属性:

(1)arg1:存放整型数据;

(2)arg2:存放整型数据;

(3)obj:存放任意对象;

(4)what:存放用户自定义的消息码(整型);

如果想要在Message对象中存放其他对象,可以通过setData(Bundle bundle)的方式。

Message对象使用时需要注意如下几点:
1、不要用new Message()实例化,应该用Message.obtain()或handler实例的obtainMessage方法从消息池中获得空消息对象,可以节省(内存)资源。
2、尽可能使用what字段标记不同的消息。

 

2. 使用

上面通过部分源码简要的介绍了Handler消息循环的运行机制,Android源码中的部分其他常用api也用到了handler,下面就一起来看看:

2.1 Activity#runOnUiThread

runOnUiThread是一个十分常用的api,这个方法在Activity中可以直接使用,经常被我们用于在子线程中执行UI的相关操作。它的源码如下:

// handler在主线程中实例化,其中通过Looper.myLooper获取到的looper实例就是关联到主线程的
@UnsupportedAppUsage
final Handler mHandler = new Handler();    

public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}

从源码中可以看出,如果当前调用该方法的线程不是UI线程(主线程),那么就通过mHandler.post方法将Runnable.run方法中的代码提交到主线程中执行。否则直接执行Runnable.run方法。

2.2 View#post、View#postDelayed

在View实例上,我们往往也可以调用其post方法来将部分任务提交到UI线程来执行,它们的源码如下:

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

 

3. 注意事项

在了解了handler的基本原理和使用方式后,有一些事项需要我们在使用中注意:

3.1 handler使用不当引起的内存泄露

考虑如下场景:在某个HandlerDemoActivity中实例化了一个mHandler,并发送了一个延时消息:

mHandler.postAtTime(() -> {}, t1);

在前面的源码分析中,我们能够知道,handler在提交消息到消息队列中时,消息队列会引用消息对象msg,而msg.target属性又引用了mHandler实例。而在Java中,内部类会持有外部类。mHandler实例会持有HandlerDemoActivity实例。

如果在t1时刻延时消息处理完成前,HandlerDemoActivity已经不处在前台,正常情况下,在内存资源不足的情况下,HandlerDemoActivity是可以被回收的。但是此时由于mHandler实例还处于被引用的状态,这导致HandlerDemoActivity无法正常被回收,也就出现了内存泄露的情况。

如果想要避免这种问题,我们就需要将Handler定义为静态的内部类,在内部通过弱引用的方式持有当前的Activity,并及时移除所有消息。例如:

private final SafeHandler mSafeHandler = new SafeHandler(this);

private static class SafeHandler extends Handler {

    private WeakReference<HandlerDemoActivity> mWeakRef;

    public SafeHandler(HandlerDemoActivity activity) {
        mWeakRef = new WeakReference(activity);
    }

    @Override
    public void handleMessage(final Message msg) {
        HandlerDemoActivity activity = mWeakRef.get();
        if (activity != null) {
            activity.handleMessage(msg);
        }
    }
}

然后在onDestroy方法中移除所有的消息:

@Override
protected void onDestroy() {
    mSafeHandler.removeCallbacksAndMessages(null);
    super.onDestroy();
}

3.2 子线程中正确的弹出Toast

因为Toast的实现依赖于Handler,所以如果想要在子线程中弹出Toast,我们需要启用事件循环:

new Thread(() -> {
            Looper.prepare();
            Toast.makeText(mContext, "xxx", Toast.LENGTH_LONG).show();
            Looper.loop();
        }).start();

不过一般还是建议不要在子线程中弹出Toast,我们可以使用runOnUiThread将其放到UI线程中执行。

4. 参考文章

Android异步消息处理机制完全解析,带你从源码的角度彻底理解

Handler 都没搞懂,拿什么去跳槽啊?

你真的懂Handler吗?Handler问答

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值