阅读 1328

Handler你真的懂了吗

1、简介

你在网上看了很多handler的原理,是不是有迷茫的地方,为什么是那个样子的?

总是听大家侃侃而谈,谈的内容还是千篇一律,流程原理说的也是那么个回事,但是再往深问一点,又啥啥不会。。。

如果你觉得有必要深深的理解它,记住它,请给我一起看看它是如何实现的

handler是android提供的一个可以在线程间传递消息的机制,主要涉及类有:Looper,MessageQueue,Message; 我们先从消息对象来看源码吧

2、消息Message

不仅是一个数据类,还实现了Parcelable接口,而且也包含了一个单链表的数据结构,还通过对象池,实现了复用

2.1 数据类

public int what;
public int arg1;
public int arg2;
public Object obj;
public Messenger replyTo;
public int sendingUid = UID_NONE;
public int workSourceUid = UID_NONE;
int flags;
public long when;
Bundle data;
Handler target;
Runnable callback;
Message next;
复制代码

主要包含一下数据内容

  • 消息内容:
  1. 消息标志what
  2. 整数型数据arg1,arg2
  3. 其它类型数据obj
  4. 进程间使用携带相关数据,data、sendingUid、workSourceUid,Messenger
  5. 消息发送对象target
  6. 消息执行时间戳when
  • 实现单链表结构下个节点 next
  • 消息到期时执行任务 callback
  • flags标志:0有效状态,低位1使用过可重复利用状态,次低位异步状态

对于跨进程使用,则主要涉及轻量级跨进程通信Messenger机制,有兴趣的可以自行了解,这里暂时不介绍了

2.2 池技术使用

池是啥?简单理解:池是使用过对象进行缓存,需要使用对象时,从缓存拿实例,进行变量重新设置即可;好处就是减少对象的创建,减少内存剧烈波动

这里是池技术相关变量

    Message next;
    public static final Object sPoolSync = new Object();
    private static Message sPool;
    private static int sPoolSize = 0;
    private static final int MAX_POOL_SIZE = 50;
    private static boolean gCheckRecycle = true;
复制代码

gCheckRecycle变量这里不可直接操作可以通过反射来设置;其值改变方法被hide了而且这个方法只有在L版本一下,才会改变;另外池的复用主要是这个变量,为true表示已经使用过的对象

    /** @hide */
    public static void updateCheckRecycle(int targetSdkVersion) {
        if (targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
            gCheckRecycle = false;
        }
    }
复制代码

池数据使用了单链表结构,先入后出的操作逻辑吗,sPool链表头;池中最大数据为50个,每次回收对象时,则重置对象的所有相关数据

   public void recycle() {
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }

    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;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }
复制代码

从链表的表头拿出数据

public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; 
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
复制代码

3、消息队列MessageQueue

主要使用了队列进行消息的排队,也是单链表结构,按照时间递增排序;还是用了原生方法进行等待,唤醒操作

3.1 队列逻辑

是一个符合消费者模式的队列 相关变量

Message mMessages; // 消息队列对头
private boolean mQuitting; // 是否结束
private boolean mBlocked; // 是否被阻塞
复制代码

入队操作

入队前,先检查状态

  • 消息处理对象handler是否不为空
  • 消息是否是有有效状态
  • 是否已经退出,退出,则直接回收message对象
    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) {
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                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;
            }

            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
复制代码

入队过程

  1. 对消息处理,标记已经使用,并对触发时间赋值
  2. 加入队列头部
  • 队列为空
  • 新加入的消息时间戳为0
  • 新加入的时间戳小于队列头部消息的时间戳
  1. 如果2不步骤中条件不满足,则从队列中找到最后一个时间戳小于when的节点,并插入其后面
  2. 如果队列为空,加入成功后,则进行唤醒

出队操作

出队操作是一个阻塞操作

    Message next() {
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1;
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                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 {
                        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 {
                    nextPollTimeoutMillis = -1;
                }
                if (mQuitting) {
                    dispose();
                    return null;
                }
                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);
            }
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; 

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            pendingIdleHandlerCount = 0;

            nextPollTimeoutMillis = 0;
        }
    }
复制代码

大致流程如下

  1. 判断原生地址,有效,则进行原生阻塞方法调用
  2. 如果是同步,则查找第一个有效的消息对象;这里有效的消息对象满足下面两点
  • 消息本身不为空
  • 消息的处理handler不为空
  1. 如果消息不为空且当前时间戳大于消息时间戳,则返回当前消息
  2. 如果不满足3,首先如果已经退出,则销毁原生对象,返回空消息
  • 如果消息对象为空,则原生等待,直到等待被唤醒
  • 如果消息不为空,唤醒时长为int最大正整数和消息时间戳减去当前时间戳的最小值

3.2 原生方法

原生对象映射java对象中,其实是把地址java成员变量中,java方法通过把指针来操作原生对象

这里mPtr为原生对象地址

nativeWake方法

nativePollOnce方法

4、发起接收者Handler

消息的发送,是有Handler发起的,消息的接收也是由handler来处理;其过程通过代理来实现的

主要相关变量

final Looper mLooper;
final MessageQueue mQueue;
final Callback mCallback;
复制代码

4.1 内部代理

  • 消息发送通过mQueue代理,实现消息查找、发送、删除操作;其时间戳计算根据操作不同而不同
  1. 延时发送,时间戳为:当前时间戳+延时时间
  2. 立刻发送,时间戳为:当前时间戳
  3. 某个时间发送:为自定义时间
  • 静态生成Message对象,由Message代理

4.2 重要方法方法

dispatchMessage方法在消息被处理时在Looper类中调用

    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
复制代码

其处理方法的优先级: 消息对象的回调(callback) > 回调(mCallback成员变量) > handleMessage方法(空方法)

Handler静态创建方法

    public static Handler createAsync(@NonNull Looper looper, @NonNull Callback callback) {
        if (looper == null) throw new NullPointerException("looper must not be null");
        if (callback == null) throw new NullPointerException("callback must not be null");
        return new Handler(looper, callback, true);
    }

    public static Handler getMain() {
        if (MAIN_THREAD_HANDLER == null) {
            MAIN_THREAD_HANDLER = new Handler(Looper.getMainLooper());
        }
        return MAIN_THREAD_HANDLER;
    }

    public static Handler mainIfNull(@Nullable Handler handler) {
        return handler == null ? getMain() : handler;
    }
复制代码

给定looper的handler,主线程Handler;返回给定handler的非空版本

5、消息传送带 Looper

主要是取出队列中消息,并分发给handler处理消息

5.1 主要变量

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;
    private static Observer sObserver;
    final MessageQueue mQueue;
    final Thread mThread;
复制代码

sThreadLocal静态实例,保存了线程的looper对象

sMainLooper:主线程looper对象

sObserver:监听消息执行状态:开始,结束,异常

mQueue:消息排队队列

mThread:looper对象相关的线程对象

5.2 构造器

   private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    
    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }
    
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
复制代码

构造器是私有方法,提供了静态方法,来获取主线成looper对象,当前线程looper对象

5.3 启动消息分发处理

启动要调用静态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;
        
        ..........(省略一些代码)

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

            ......(省略一些代码) 
            if (observer != null) {
                token = observer.messageDispatchStarting();
            }

            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);
                }
            }
            
            ......(省略一些代码) 

            msg.recycleUnchecked();
        }
    }
复制代码

流程大致如下:

  • 首先取出当前线程的looper,如果为空则抛出异常;所以需要需要设置当前线程looper对象到sThreadLocal中,这就需要prepare系列方法
  • 开始handler处理前监听
  • 调用handler的dispatchMessage方法,如果出现异常,则调用监听器异常处理,无异常调用监听器正常结束状态处理
  • 消息对象进行池回收

6、原理总结

这里主要分析了线程间通信的机制;进程间通信机制依赖Messenger通信机制;下面对线程间通信进行一些总结

  • handler对象是消息发送者,和消息的处理者;
  • 消息能够运行,需要先调用Looper的prepare方法,再调用loop方法,启动循环
  • Message携带消息信息,而且使用了池技术;对象构造时,应从池中获取
  • 消息的处理方法并不是固定,具有优先级关系:Message对象中callback > handler对象中mCallback > handler中handleMessage方法
  • 消息队列退出后,looper循环也会跟着一起退出
  • 消息队列采用消费者模式,并根据时间戳进行等待;其等待唤醒采取native方法(暂时在androidRef和自己下载的framework源码中未跟踪到实现,暂时未能进行分析给与歉意)
  • 队列阻塞为何用原生方法,而不直接用线程挂起唤醒呢?很简单,主线程是UI线程,如果是主线程的消息,还要不要刷新界面了,这里的阻塞机制,类似与协程的实现;主线程的消息机制可以直接用,因为在ActivityThread类中已经调用Looper的预加载,启动方法了

技术变化都很快,但基础技术、理论知识永远都是那些;作者希望在余后的生活中,对常用技术点进行基础知识分享;如果你觉得文章写的不错,请给与关注和点赞;如果文章存在错误,也请多多指教!

文章分类
Android
文章标签