一份详细的Handler原理解析

本文详细探讨了Android中Handler的使用和原理,包括其基础、消息处理流程、Looper与MessageQueue的创建,以及常见问题。Handler是Android系统中重要的事件分发机制,涉及线程间通信、延迟加载等功能。文章深入剖析了Handler、Message、MessageQueue和Looper的工作原理,解释了线程安全和内存泄漏的相关问题。

        Handler已经是个老生常谈的问题了,对于Android开发者来说Handler是一个无论面试还是日常开发都无法避开的话题,因为它是Android系统中最重要的事件分发和处理机制。本篇文章就Handler从使用到源码解析再到使用上可能产生相应的问题做一个总结。

        

一、Handler基础

      1、初识Handler

        相信许多小伙伴刚了解Handler的时候是在线程间通信上认识的。比较常用的当一个功能在子线程执行出结果需要在控件上展示的时候,由于在一般情况下子线程不允许更新View,所以需要将子线程的结果提交给主线程,由主线程完成数据的绘制,在这两个线程进行数据传递就需要Handler的帮助。

        再后来根据Handler提供的方法特性,我们可以用来做延迟加载或者开发一个自定义定时器等功能。

      2、基础使用

        Handler的使用比较简单:

        1、定义Handler,接收消息的主体

private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
             //此方法用于接受消息,并进一步处理
            super.handleMessage(msg);
            //根据传入的what类型处理不同消息
            switch (msg.what){
//                case msgWhat1:
//                    break;
//                case msgWhat2:
//                    break;
//                    ...
            }
        }
    };

        2、发送消息

        Handler提供了多种发送消息的方法,根据不同需求执行不同方法

//通过复用的方式创建消息体
Message msg = mHandler.obtainMessage();

//发送消息
mHandler.sendMessage(msg)
//发送空消息
mHandler.sendEmptyMessage()
//延迟发送空消息
mHandler.sendEmptyMessageDelayed(what,time)
//延迟发送消息
mHandler.sendMessageDelayed(msg,time)
//发送一个特定时间执行的消息
mHandler.sendMessageAtTime(msg,time)
//发送一个特定时间执行的空消息
mHandler.sendEmptyMessageAtTime(what,time)
//发送消息并插入到队列前,立刻执行
mHandler.sendMessageAtFrontOfQueue(msg)

//除了send系列还有post系列的消息发送,区别在于post发送需要传入
//一个Runnable对象,执行完后不会执行到handleMessage方法中,而是
//直接回调到Runnable的run方法中,post提供的方法与send类似,不
//多做赘述
mHandler.post(new Runnable() {
    @Override
    public void run() {
        //执行代码
    }
});

二、Handler原理

      1、Handler成员

        Handler:消息的接收和发送主体,通过send/post发送消息,在handleMessage或者 Runnable中处理消息

        MessageQueue:消息队列的管理者,虽然名字带着Queue,但是MessageQueue本身并不是队列结构体,真正的结构体是Message,它只是消息队列的管理者,同事内部持有当前线程的Message队列,对消息进行获取、插入、移除等操作

        Message:消息主体,类内部通过Message类型的next变量将Message组成一个单向链表结构

public final class Message implements Parcelable {
    //......
    @UnsupportedAppUsage
    /*package*/ Handler target;

    @UnsupportedAppUsage
    /*package*/ Runnable callback;

    // sometimes we store linked lists of these things
    //指向下一条Message,形成链表
    @UnsupportedAppUsage
    /*package*/ Message next;
    //......
}

        Looper:轮询者,内部是一个无限循环,通过不断检测和轮询MessageQueue中是否有消息来分发到Handler的回调方法中。

        

      2、执行流程

        2.1 发送消息

        以sendMessage为例:

//Handler.java

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

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

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    //MessageQueue直接取的成员变量
    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);
}

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    //Message持有当前Handler对象
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    //发送消息最终都会调用到MessageQueue的enqueueMessage方法
    return queue.enqueueMessage(msg, uptimeMillis);
}

         发送消息主要做了三件事

        1、检测MessageQueue是否创建

        2、通过msg.target = this 让Message持有当前Handler对象,方便后面调用

        3、最终调用了MessageQueue的enqueueMessage方法

         那MessageQueue.enqueueMessage()又做了什么呢?

//MessageQueue.class

/**
* 当前的消息队列
*/
@UnsupportedAppUsage
Message mMessages;

/**
* 将发送的消息进行排序,插入等处理
*/
boolean enqueueMessage(Message msg, long when) {
    //是否持有了Handler
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    //同步锁防止,同一个Hander在不同线程发送消息造成的同步插入问题
    synchronized (this) {
        //正在使用中的抛出异常 与markInUse配合
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }
        //当前消息系统退出了 直接回收Message
        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;
        }
        //改变使用标识位,与isInUse配合
        msg.markInUse();
        //更新Message时间,用于排列
        msg.when = when;
        //获取队列的头指针
        Message p = mMessages;
        boolean needWake;
        //如果当队列没有消息 || 
        //如果当前消息时间为0,为立即执行的消息放在队首 || 
        //当前消息执行时间还小于队列中最近需要执行的消息
        if (p == null || when == 0 || when < p.when) {
            //将当前消息作为队列的头指针
            msg.next = p;
            mMessages = msg;
            //如果阻塞则需要唤起,否则等待loop的轮询执行
            needWake = mBlocked;
        } else {
            // 插入到队列中间。通常我们不需要唤醒事件队列,除非在队列
            //的头部有一个障碍,并且消息是队列中最早的异步消息。
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            //遍历当前队列插入到队列中间,队列插入算法
            for (;;) {
                prev = p;
                p = p.next;
                //查找第一个时间大于当前消息的Message
                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;
}

        enqueueMessage中主要做了这几件事:

        1、检测线程内是否有消息队列或者当前传入消息的时间是否插入到队列头部,是的话插入队首,否则遍历队列插入对应时间结点中

        2、检测当前轮询是否需要唤醒,在头指针时判断如果当前已经阻塞则需要唤醒队列;如果插入在队里中除了特殊情况之外是不需要唤醒的只需要等待Looper根据时间轮询调用即可

        3、需要唤醒是调用nativeWake(ptr),这是一个native方法底层C实现,用来唤醒通过nativePollOnce阻塞的方法。

        在这里我们需要注意的是插入队列加了一个同步锁synchronized,其实也好理解,发消息用到Handler,然而同一个Handler是可以在不同线程发送消息的,那插入的时候必然会有同步问题,所以需要同步处理。

        消息的发送到插入就这么多,在这个过程中我们可以看出用到MessageQueue,并且它是直接通过成员变量赋值,那么我们还需要了解一下MessageQueue是如何创建的

        2.2 Looper的启动和MessageQueue的创建

        2.2.1 MessageQueue赋值

public class Handler {
    //......
    @UnsupportedAppUsage
    final Looper mLooper;
    final MessageQueue mQueue;
    //......
    public Handler(@Nullable Callback callback, boolean async) {
        //......
        //Looper通过Looper.myLooper()获取
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
        }
        //MessageQueue通过Looper获取
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
}

        可以看出Handler中MessageQueue是通过Looper获取的,而Looper又是通过本身Looper.myLooper创建,所以我们还需要探究Looper的创建。

        2.2.2 Looper创建

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

        Looper.myLooper()方法只是一个获取操作,还需要进一步查看Looper的创建

private Looper(boolean quitAllowed) {
    //创建MessageQueue
    mQueue = new MessageQueue(quitAllowed);
    //获取当前线程
    mThread = Thread.currentThread();
}

        Looper构造函数私有对外是没有办法通过new方法创建,需要查看内部提供的方法,并且在构造函数中我们看到了MessageQueue的创建

        通过构造方法我们知道了创建Looper是在Looper的prepare()方法中执行的:

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

private static void prepare(boolean quitAllowed) {
    //如果创建过,抛出异常,保证线程内Looper的唯一
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    //创建Looper存入sThreadLocal池中
    sThreadLocal.set(new Looper(quitAllowed));
}

        prepare()中保证了Looper在线程的唯一性,并且创建Looper存入sThreadLocal中。那ThreadLocal又是什么,如何存入的

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

        ThreadLocal的 java.lang 包下面的一个类,我们现在可以暂时简单把它理解为一个以当前线程为key的Map

public class ThreadLocal<T> {
    //......
    public ThreadLocal() {
    }
    /**
     * 获取当前线程存入的value
     * @return the initial value
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    //......

    /**
     * 获取当前线程作为key,存入数据
     */
    public void set(T value) {
        //获取当前线程
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        //以线程作为key,存入数据
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
    /**
     * 获取当前线程内的ThreadLocal
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    /**
     * 创建字典
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    //......
    
    /**
    * 内部类通过K,V方式存入数据,类似Map
    */
    static class ThreadLocalMap {
        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        //......
    }
}

        ThreadLocal.set()方法是将创建的Looper存入以当前线程为key的字典中,在存入之前通过get()方法先获取当前线程是否有过Looper,创建过抛出异常从而保证了唯一性

        上述代码我们可以了解到:

        一个线程内只能存在一个Looper,一个Looper对应一个MessageQueue,所以一个线程内也只有一个MessageQueue

        2.2.3 Looper的启动

        我们介绍过Looper是一个无限循环,类似传送带一样不断轮询MessageQueue中的消息,那创建完Looper如何启动这个传送带呢?

        这里我们需要用到Looper.loop()方法

//Looper.java

public static void loop() {
    //获取当前线程的Looper
    final Looper me = myLooper();
    if (me == null) {
        //子线程使用Handler没有启动Looper的经典错误
        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;
    //赋值当前Looper中的MessageQueue
    final MessageQueue queue = me.mQueue;

    //......
    
    //开启无限循环
    for (;;) {
        //获取下一个消息
        Message msg = queue.next(); // might block
        //返回null,退出当前循环
        //需要注意 如果当前系统内没有消息是会阻塞线程 并不会返回null
        //返回null,代表了出现了异常或者主动退出
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        //......
        try {
            //msg.target是发送消息的Handler对象,所以调用到了
            //发送消息的Handler的dispatchMessage方法
            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);
            }
        }
        //......
        //消息回收,进行复用
        msg.recycleUnchecked();
    }
}

        loop()方法中直接定义了一个for无限循环,通过不断的调用MessageQueue.next(),在获取到Message后再调用target(发送时持有Handler)的dispatchMessage方法进一步处理。

        这里我们需要知道正常情况下MessageQueue.next()要么返回Message,要么没有消息就挂起方法等待新Message插入后唤醒线程再返回,如果返回null则代表整个消息机制异常或者被关闭了,所哟判断MessageQueue.next()==null,就可以直接退出轮询。

        2.3 MessageQueue获取Message

        MessageQueue.next()方法

//MessageQueue.java

@UnsupportedAppUsage
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.
    // ptr是系统分配的一个code,在消息机制退出的时候会赋值0,结束循环
    // 在MessageQueue构造方法中通过nativeInit函数创建
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }
    //空闲时需要执行的任务数
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    //阻塞等待时间 0:立即执行; -1:一直等待; 其他:根据数值挂起等待
    int nextPollTimeoutMillis = 0;
    for (;;) {
        //nextPollTimeoutMillis不为0则说明程序可能要挂起
        if (nextPollTimeoutMillis != 0) {
            //接下来可能会挂起,此方法用于释放挂起对象的引用
            Binder.flushPendingCommands();
        }
        //挂起函数,根据nextPollTimeoutMillis判断时间
        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;
            //找到第一个可执行的Message
            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) {//如果存在需要执行的Message
                //判断Message时间与当前时间,是否需要等待
                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.
                    //不需要等待则需要取出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();
                    //返回Message给Looper继续处理
                    return msg;
                }
            } else {
                // No more messages.
                //如果队列中没有可执行的Message,赋值-1为一直等待状态
                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.
            //如果当前没有可执行的Message,程序处于空闲状态,那需要判断
            //有没有需要执行的空闲任务
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                //一般情况下没有其他空闲任务直接进入下一次循环
                //根据nextPollTimeoutMillis=-1,挂起队列
                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.
        //执行完空闲任务后,为了确保是否有新Message,需要重新循环检测
        nextPollTimeoutMillis = 0;
    }
}

        由上总结next()方法的主要工作:

        1、开启无限循环,根据nextPollTimeoutMillis判断是否需要挂起等待。

        2、根据当前时间戳查找需要执行的Message,如果大于当前时间需要计算等待时间,否则返回对应Message给Looper,进一步处理。

        3、如果没有可执行的Message,就判断当前是否有存在空闲任务,如果有则执行空闲任务,否则nextPollTimeoutMillis = -1,在进入下一次循环的时候挂起等待。

        这里有几个问题需要解释一下:

        1、ptr是MessageQueue构造创建通过nativeInit函数创建的code,主要用于当前挂起和唤醒程序的唯一标示

        2、nativePollOnce函数是MessageQueue定义用C实现的底层函数,通过Linux的epoll机制实现挂起,根据nextPollTimeoutMillis的值用于判断当前程序的挂起状态,其中

        nextPollTimeoutMillis = 0;立即执行,无需等待

        nextPollTimeoutMillis = -1;进入挂起状态,等待唤醒

        nextPollTimeoutMillis = 其他数值;挂起等待的时间

        当调用nativePollOnce挂起之后,需要使用nativeWake函数唤醒,也就是我们调用MessageQueue.enqueueMessage()方法插入Message时用到的函数

        当然其中还涉及的Linux底层,感兴趣的小伙伴可以进一步了解

        3、next()获取Message也用到了synchronized锁,这里因为我们在插入的时候可能是其他线程,为了防止边插入边获取带来的同步问题

        4、查找Message比较简单,根据当前时间戳,如果最近的消息时间大于当前时间需要计算等待时间,否则按顺序获取Message返回给Looper

        5、关于IdleHandler空闲任务,我们可以理解为,当前系统如果没有Message执行那代表系统处于空闲时间,我们可以利用这段空闲时间来完成一些并不紧急的任务。等一旦有Message执行,则优先处理Message。

        比如 系统的GC机制,在系统繁忙的时候GC一般是不会进行垃圾回收,当程序空闲时GC就开始进行检测处理系统垃圾。

        执行完空闲任务后,可能会有新的Message进来,所以需要将nextPollTimeoutMillis赋为0重新检测一遍。

        以上就是MessageQueue.next()的主要功能,Looper在获取Message后有提交给了Handler处理,我们继续梳理。

        2.5 Handler处理Message

        根据2.4的介绍Looper获取到Message之后,执行了Message.target.dispatchMessage方法,也就是Handler的dispatchMessage方法:

//Handler.java

public void dispatchMessage(@NonNull Message msg) {
    //优先判断callback是否实现 post方式传入的消息
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        //创建Handler时,构造方法提供了传入Callback的重载,如果
        //传入则优先回调Callback
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        //定义Handler所实现的回调方法。
        handleMessage(msg);
    }
}

        dispatchMessage比较简单:

        1、先判断了msg.callback,开始我们介绍了Handler发送消息的两种方式(send/post),这里的callback就是通过post方式传入的Runnable

//Handler.java

/**
* post方式
*/
public final boolean post(@NonNull Runnable r) {
   //最终调用了与send相同方法
   return  sendMessageDelayed(getPostMessage(r), 0);
}

/**
* 构建Message
*/
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    //赋值callback
    m.callback = r;
    return m;
}

        2、Handler构造提供了多种重载,除了无参默认的方式,也可以传入Runnable作为回调方法,如果采用默认方式那最终调用到我们所熟悉的handleMessage方法。

        2.6 Message的缓存复用策略

        我们平时发送消息的时候通常使用的是Handler提供Message,而不是直接new 创建。

//使用Hander提供的方法
//Handler从Message的缓存池中找到空闲的Message
Message msg = mHandler.obtainMessage();

        Handler发送消息可能是一个频繁多次的过程,所以频繁的创建Message,用完后如果没有及时回收会造成大量垃圾,造成性能问题,所以我们需要了解Message是如何缓存如何复用。

//Handler.java
Message msg = mHandler.obtainMessage();

@NonNull
public final Message obtainMessage(){
    return Message.obtain(this);
}

//Message.java
public static Message obtain(Handler h) {
    Message m = obtain();
    m.target = h;
    return m;
}

//锁
public static final Object sPoolSync = new Object();
//缓存池
private static Message sPool;
public static Message obtain() {
    //同步锁,防止Handler在不同线程提供消息时的同步问题
    synchronized (sPoolSync) {
        //缓存池
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    //缓存池为null 提供新Message
    return new Message();
}

        我们可以看出:

        1、缓存池的本质跟MessageQueue中的队列一样,通过Message.next组成的单向列表结构

        2、获取Message时也加了同步锁是因为sPool是一个static的全局静态变量意味着整个进程只有一个缓存池,同时同一个Handler可能会在不同线程调用obtainMessage()获取Message会有同步问题,所以需要加同步锁。

        3、获取比较简单,当sPool有值时取出队首Message,将sPool指向next下一条Message同时更新size;如果当前sPool为null,即没有可用的缓存直接创建新Message并返回。

        获取就这些,那Message是什么时候存入缓存池中的呢?

        在我们探究Handler流程时,会发现在MessageQueue插入Messgae和Looper用完后回调用Message.recycle()或者Message.recycleUnchecked();

/**
* 不能回收正在使用的消息
*/
public void recycle() {
    if (isInUse()) {
        if (gCheckRecycle) {
            throw new IllegalStateException("This message cannot be recycled because it "
                    + "is still in use.");
        }
        return;
    }
    recycleUnchecked();
}

/**
 * Recycles a Message that may be in-use.
 * Used internally by the MessageQueue and Looper when disposing of queued Messages.
 * 回收Message而且这个Message可能被正在使用,用于Message和Looper内部
 */
@UnsupportedAppUsage
void recycleUnchecked() {
    // 清理
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = UID_NONE;
    workSourceUid = UID_NONE;
    when = 0;
    target = null;
    callback = null;
    data = null;
    //与obtain 同一个锁sPoolSync,防止边回收边获取的同步问题
    synchronized (sPoolSync) {
        //判断最大size
        if (sPoolSize < MAX_POOL_SIZE) {
            //将当前回收的Message放到缓存头部节点
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

        回收代码中将使用过的Message内部的各种状态值,参数值初始化。加入缓存池的实质操作是将当前Message作为缓存池的头节点插入队列中,同时防止与obtain获取Message发生同步问题,需要加入与获取Message一样的同步锁

      3、总结

        1、在使用Handler之前需要先调用Looper.prepare()和Looper.loop()

        Looper.prepare()负责创建Looper和MessageQueue,同时将Looper对象存入ThreadLocal中,防止一个线程多次创建

        Looper.loop()负责开启无限轮询,不断调用MessageQueue.next获取消息

        2、Handler通过send或者post方式发送消息,让Message持有当前Handler并最终调用到MessageQueue.enqueueMessage()方法

        3、MessageQueue.enqueueMessage()负责判断将当前Message插入到Message队列中,根据Message.when进行排序,并根据当前执行状态可能需要通过nativeWake()唤醒挂起的队列

        4、Looper.loop()中调用MessageQueue.next()获取Message,在next()中需要判断队列中的Message与当前时间是否需要挂起,如果没有Message或者需要挂起则通过nativePollOnce挂起程序,否则返回Message给Looper

        5、loop()在获取到Message后调用到Handler的dispatchMessage方法,dispatchMessage方法根据发送的方式(send/post)不同,回调到callback或者handleMessage方法中。

三、Handler相关问题     

        整理比较常见的Handler相关问题

        1、子线程如何使用Handler,主线程为什么能直接使用

        根据Handler的原理,使用Handler之前需要初始化MessageQueue和Looper,同时还需要开启轮询,所以子线程中用到Handler之前需要先调用Looper.prepare()和Looper.loop()两个方法

        主线程同样也需要初始化这两个,我们之所以能直接使用是因为在App启动的时候就已经初始化了Looper,在ActivityThread.main()中

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

    //......
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    //......
    //启动轮询
    Looper.loop();

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

        main函数我们都知道是Java程序的入口程序,同样也是Android App的入口程序,在main方法中我们可以看到Looper使用的是prepareMainLooper()方法,方法内部依旧调用了prepare(),只是针对特殊的主线程做了其他处理。

@Deprecated
public static void prepareMainLooper() {
    //创建Looper,并且不可退出
    prepare(false);
    synchronized (Looper.class) {
        //记录主线程Looper
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

        2、使用Handler中存在的内存泄漏几种情况以及解决方法。

        第一种:Activity中匿名声明Handler,在退出是没有清理正在处理的消息

匿名声明的对象会持有外部类引用,也就是Handler会持有Activity引用。而且在发送消息时,Message会持有Handler的引用:msg.target = this //Handler

        Message间接持有了Activity的引用,如果在Activity退出的时候MessageQueue中依旧有未处理的Message的,那Activity也会一直被持有,故而Activity无法被释放回收造成内存泄漏问题。

        解决方法:

        Handler移除所有已知的Message,并且Handler=null来断开Handler与Activity的关联

@Override
  protected void onDestroy() {
    super.onDestroy();
    if(mHandler != null){
        //清理消息,移除外部类引用
        mHandler.removeCallbacksAndMessages(null);
        mHandler = null   
    }    
  }

        第二种:子线程中没有正常退出Handler机制

        子线程使用Handler会通过Looper.prepare()、Looper.loop()开启无限轮询,这就意味着子线程即便代码逻辑已经结束,但Handler机制却一直运行,即便没有Message也会在MessageQueue.next()阻塞从而无法被释放,如果此线程也是通过匿名内部类方式创建那还会持有外部类引用,造成内存泄漏,单单移除Message是没用的。

        解决方法:

        需要手动关闭Looper

MessageQueue.quit()

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

        退出时主要工作:

                1、判断当前任务是否可以退出

                2、修改mQuitting的值

                3、移除所有MEssage

                4、唤醒任务进行判断

        同时配合MessageQueue.next()中对mQuitting的判断返回的Message为null,Looper获取到null值后会return退出轮询从而退出整个消息机制。

        3、既然Looper.loop是无限循环,那主线程为什么没有ANR

        首先我们需要明白ANR是指主线程中被某种原因(CPU过载,内存不足,睡眠...)等原因被阻塞,无法响应和处理当前事件;或者当前正在处理的事件过于耗时没有及时完成,所触发的一种系统保护机制。

        App运行实质是以Handler消息机制为驱动执行的,包括四大组件间的启动运行,事件输入输出等都是在主线程的Looper中运行,包括ANR的检测也是由Handler驱动。所以ANR是Looper执行主线程的Message在没有在指定的时间内响应所触发的机制,与轮询本身没有任何关系。

        也可以简单理解为:ANR是系统阻塞的检测和保护机制,触发的本质也是一种Message发送到主线程的Handler,运行在Looper.loop()中。

        4、Handler是如何进行线程切换的

        线程A创建的Handler,在线程B发送消息,最终在线程A中执行

        通过原理分析我们可以知道发送消息只是插入到队列中,真正获取和处理消息是在Looper中进行的,所以无论在哪个线程发送消息,最终都需要在Looper所在线程进行处理,而Looper在创建的时候就已经和当前线程通过的方式绑定在ThreadLocal,包括MessageQueue。

        所以线程切换的本质是:线程B发送消息插入到线程A的队列中,再由线程A的Looper分发给Hanlder进行处理

        5、Handler、Looper、MessageQueue在同一个线程数量以及对应关系,同时如何保证线程安全问题。

        一个线程内可以创建多个Handler,并且可以在不同线程中使用;

        根据Looper.prepare代码分析,一个线程内只能有一个Looper存放在ThreadLocal中;

        Looper创建时在构造方法内同时创建MessageQueue所以一个线程内只有一个MessageQueue。

        Handler可以在不同线程发送消息插入到MessageQueue,所以在MessageQueue.enqueueMessage中加入了synchronized同步锁防止多线程插入的同步问题,对应的插入在其他线程中那获取时即Looper.next()也需要加入synchronized同步锁,防止边存边取带来的同步问题。这两个锁对象都是MessageQueue,由于一个线程只有一个MessageQueue,所以同步锁是有效的。

        结语:以上就是对Handler原理解析的全部内容,后续也会持续更新和改正其中的不足。

评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值