Android Handler消息机制02-Looper源码学习

Posted 双木青橙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Handler消息机制02-Looper源码学习相关的知识,希望对你有一定的参考价值。

0. 系列文章汇总

1.源码

本文主要是对Looper类的源码进行解析学习,用于更深入的理解Handler消息机制
Looper的源码路径为:android.os.Looper

1.2 典型案例

如下展示了一个源码中提供的典型实例

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

如上所示的是在一个子线程构建一个Handler,但是在实际开发中不建议这么做。一般来说Handler机制是用于子线程往主线程传递消息用于UI更新操作。如果实在要在子线程创建,请使用HandlerThread进行创建

/**
 * Looper类被用于运行一个线程的消息循环,消息默认没有与之关联的消息循环,为了创建一个,在运行循环的线程中调用Looper.prepare,
 * 然后调用loop来处理消息直到循环停止。
 *
 * <p>与消息循环交互的大多数交互是通过Handler类。
 */
public final class Looper 
    /*
     * API Implementation Note:
     *
     * 该类基于MessageQueue去设置和管理事件循环,影响队列状态的API应在MessageQueue或者Handler上定义,而不是在Looper本身上定义,
     * 例如默认Handlers和同步屏障在队列中定义,而prepare、loop和quit定义在Looper中
     */

    private static final String TAG = "Looper";

    // sThreadLocal 会返回null 直到你调用prepare,表示当前线程的Looper对象
    @UnsupportedAppUsage
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    @UnsupportedAppUsage
    private static Looper sMainLooper;  // 静态关联主线程的Looper
    private static Observer sObserver;

    @UnsupportedAppUsage
    final MessageQueue mQueue;
    final Thread mThread;
    //
    private boolean mInLoop;

    @UnsupportedAppUsage
    private Printer mLogging;
    private long mTraceTag;

    /**
     * If set, the looper will show a warning log if a message dispatch takes longer than this.
     * 如果赋值,Looper会打印一个告警日志如果消息处理耗时比这个值大。
     */
    private long mSlowDispatchThresholdMs;

    /**
     * If set, the looper will show a warning log if a message delivery (actual delivery time -
     * post time) takes longer than this.
     * 如果被赋值,且消息分发(实际 delivery time -post time)耗时过长且会打印一个告警日志
     */
    private long mSlowDeliveryThresholdMs;

    private Looper(boolean quitAllowed) 
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread(); //mThread置为当前线程
    

    /**
     * 初始化当前线程为一个Looper
     * 这提供了在实际开始loop之前,创建关联当前Looperr的Handler的时机,
     * 确保在调用此方法后再调用#loop(),然后通过#quit()来结束他
     *
     */
    public static void prepare() 
        prepare(true);
    

    /**
     * 准备此Looper
     * @param quitAllowed 是否允许此Hnadler停止
     */
    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,以此作为当前应用的主looper,是在ActivityThread调用。
     *
     * @deprecated 进程的主Looper是通过Android系统创建的,所以永远不需要自己调用prepareMainLooper方法。
     */
    @Deprecated
    public static void prepareMainLooper() 
        prepare(false);
        synchronized (Looper.class) 
            if (sMainLooper != null) 
                throw new IllegalStateException("The main Looper has already been prepared.");
            
            sMainLooper = myLooper();
        
    

    /**
     * 返回当前的主Looper,关联着当前应用的主线程。
     */
    public static Looper getMainLooper() 
        synchronized (Looper.class) 
            return sMainLooper;
        
    

    /**
     * 设置当前进程的所有Looper设置观察者
     *
     * @hide
     */
    public static void setObserver(@Nullable Observer observer) 
        sObserver = observer;
    

    /**
     * 运行当前线程的消息队列,确保call #quit() 在循环结束后
     */
    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();

        // 允许使用系统参数覆盖阈值,供系统使用
        // 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(); // 可能会阻塞
            if (msg == null) 
                // 没有消息则此消息队列会退出处理流程
                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 Observer observer = sObserver;

            final long traceTag = me.mTraceTag;
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs; // 消息处理耗时阈值
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs; // 消息分发耗时阈值
            if (thresholdOverride > 0)  // 如果从系统变量里面读取的值不等于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); // 可选字段,指示导致该消息进入队列的uid,仅用于系统服务
            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);
                
            
            // 关于分发的耗时操作日志处理,删除

            // 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);
            
            // 回收Message对象,注意dispatchMessage与recycleUnchecked 在同一线程串行执行,所以要注意Message对象
            // 不要传递到子线程去处理,否则会导致多线程安全问题
            msg.recycleUnchecked();
        
    

    private static boolean showSlowLog(long threshold, long measureStart, long measureEnd,
                                       String what, Message msg) 
        final long actualTime = measureEnd - measureStart;
        if (actualTime < threshold) 
            return false;
        
        // For slow delivery, the current message isn't really important, but log it anyway.
        Slog.w(TAG, "Slow " + what + " took " + actualTime + "ms "
                + Thread.currentThread().getName() + " h="
                + msg.target.getClass().getName() + " c=" + msg.callback + " m=" + msg.what);
        return true;
    

    /**
     * 返回当前线程关联的Looper对象,如果调用线程与Looper无关则返回null
     */
    public static @Nullable
    Looper myLooper() 
        return sThreadLocal.get();
    

    /**
     * 此方法用于返回关联当前线程的@link MessageQueue 对象,此方法必须被运行Looper的线程调用,否则会抛出空指针异常
     */
    public static @NonNull
    MessageQueue myQueue() 
        return myLooper().mQueue;
    

    /**
     * 如果当前线程是此Looper关联的线程,则返回true
     */
    public boolean isCurrentThread() 
        return Thread.currentThread() == mThread;
    

    /**
     * Control logging of messages as they are processed by this Looper.  If
     * enabled, a log message will be written to <var>printer</var>
     * at the beginning and ending of each message dispatch, identifying the
     * target Handler and message contents.
     *
     * @param printer A Printer object that will receive log messages, or
     *                null to disable message logging.
     */
    public void setMessageLogging(@Nullable Printer printer) 
        mLogging = printer;
    

    /**
     * 设置处理/分发耗时日志的阈值
     * @hide
     */
    public void setSlowLogThresholdMs(long slowDispatchThresholdMs, long slowDeliveryThresholdMs) 
        mSlowDispatchThresholdMs = slowDispatchThresholdMs;
        mSlowDeliveryThresholdMs = slowDeliveryThresholdMs;
    

    /**
     * 停止此Looper
     * <p>
     * 直接停止消息队列的消息处理且不再处理消息队列里面的任何消息
     * 此方法可能会不安全,因为部分在队列里面可能没有处理Loop就停止了,建议采用quitSafely()
     * </p>
     *
     * @see #quitSafely
     */
    public void quit() 
        mQueue.quit(false);
    

    /**
     * 安全停止此Looper,建设采用此方法
     * 等所有队列中已存在的消息都分发处理后才会停止loop(),
     * Causes the @link #loop method to terminate as soon as all remaining messages
     * in the message queue that are already due to be delivered have been handled.
     * 但是未到时间的delayed消息后面也不会分发给Handler处理了。
     * </p>
     *  任何尝试在调用quitSafely()方法后去post消息到队列中的尝试都会失败。比如@link Handler#sendMessage(Message) 会返回flase
     */
    public void quitSafely() 
        mQueue.quit(true);
    

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

    /**
     * 获取此Looper的消息队列
     *
     * @return 消息队列
     */
    public @NonNull
    MessageQueue getQueue() 
        return mQueue;
    

    @Override
    public String toString() 
        return "Looper (" + mThread.getName() + ", tid " + mThread.getId()
                + ") " + Integer.toHexString(System.identityHashCode(this)) + "";
    

    /**
     * 观察者接口,在需要的时候进行
     * @hide
     */
    public interface Observer 
        /**
         * 在发送消息之前调用
         *
         * <p> 设计者并没有指定token类型以允许开发者指定自己的类型.
         *
         * @return 处理单个消息时用于收集telemetry的token,token必须精确地传递给
         * @link Observer#messageDispatched or @link Observer#dispatchingThrewException,而且不允许重复使用
         */
        Object messageDispatchStarting();

        /**
         * 当消息被Handler处理时调用
         * Called when a message was processed by a Handler.
         *
         * @param token 通过先前通过@link Observer#messageDispatchStarting在同一个观察者实例上获得
         * @param msg   被处理的消息
         */
        void messageDispatched(Object token, Message msg);

        /**
         * 但处理消息时抛出异常时调用.
         *
         * @param token token
         * @param msg      被分发且导致出现异常的Message消息对象
         * @param exception 出现的异常
         */
        void dispatchingThrewException(Object token, Message msg, Exception exception);
    

2. Handler如何避免出现内存泄漏

  1. 在Activity的生命周期结束后,在onDestory()调用mHandler.removeCallbacksAndMessages(null);将所有的消息和回调都进行清理。(此方法仅用于清理仅在当前Context下创建的私有Handler,而不是主线程,否则会将其他场景产生的消息进行清除,导致功能异常)
  2. 使用弱引用方法,此方法还未实践,待真正实践时再来记录。

3. Tips

Q: 如何知道当前线程是否是主线程
有关于UI的操作都要求在主线程执行,如下提供了通过Looper一种获取当前线程是否是主线程的方法:

    public static boolean isMainThread()
        //获取主线程的Looper对象
        Looper mainLooper = Looper.getMainLooper();
        //获取主线线程的Looper对象
        Looper currentLooper = Looper.myLooper();
        return mainLooper== currentLooper;
    

以上是关于Android Handler消息机制02-Looper源码学习的主要内容,如果未能解决你的问题,请参考以下文章

Android的消息机制Handler

深入分析Android-Handler消息机制

Android Handler消息机制源码解析

深入解析Android中Handler消息机制

Android的消息机制Handler详解

Android中的Handler机制