eaaomk

eaaomk eaaomk
主页
博客
  • 常用命令
  • 情景再现
  • AMS
  • Zygote
  • 审计
  • 财务会计
  • 进程与线程
  • 消息传递
  • 处理机调度与死锁
  • 设计思想与代码质量优化
  • 设计模式
  • 数据结构
  • 算法
  • VSCODE
标签
时间轴
关于
author-avatar

eaaomk

37

文章

10

标签

主页
博客
  • 常用命令
  • 情景再现
  • AMS
  • Zygote
  • 审计
  • 财务会计
  • 进程与线程
  • 消息传递
  • 处理机调度与死锁
  • 设计思想与代码质量优化
  • 设计模式
  • 数据结构
  • 算法
  • VSCODE
标签
时间轴
关于

Handler&Message

Handler&Message

eaaomk 2022-01-07 23:00:00 JavaAndroid

# Handler

Handler主要用于异步消息的处理:当发出一个消息之后,首先进入一个消息队列,发送消息的函数即刻返回,而另外一个部分在消息队列中逐一将消息取出,然后对消息进行处理,也就是发送消息和接收消息不是同步的处理。 这种机制通常用来处理相对耗时比较长的操作。

# Handler 相关介绍

  • 先看粗糙版本的类图 avatar
  • Handler 在初始化的时候也会初始化mLooper 以及 mQueue,mQueue就是存储在mLooper中的消息队列。我们可以不传入looper参数去new 一个Handler ,因为它会在构造函数中调用 Looper.myLooper() 自动去获取当前线程的looper,目前高版本的SDK中这种不传入looper的方法已经Deprecated了,所以推荐在使用的时候手动传入一个looper。
//frameworks/base/core/java/android/os/Looper.java
public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
}
  • 这里可以清晰的看见,我们的looper 是存储在Thread线程中的
//libcore/ojluni/src/main/java/java/lang/ThreadLocal.java
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();
}
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
  • 在MessageQueue 中,包含了Message属性,它是以一个链表的形式存在的,这个链表里面存储的是按照时间顺序排列好的一堆Message 消息,在next 字段中存储了下一个Message对象
//frameworks/base/core/java/android/os/Handler.java
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
}
  • 首先我们需要注意的是,Handler 必须拥有一个looper,这个looper 并不归属于Handler ,它是由线程Thread 管理的。

  • looper 是放在了当前线程的threadLocals 属性中的,所以如果想在一个线程中使用Handler ,首先要保证这个线程初始化了这个looper 对象.

  • 需要知道的是,一个线程中只会存在一个looper 对象,不能被多次初始化。

  • 一个looper 对象可以被多个Handler 对象使用,也就是一个线程中可以有多个handler 对象

// frameworks/base/core/java/android/os/Looper.java
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));
}

avatar

  • handler 所做的事情就是将消息按照时间顺序放在当前线程的looper中的消息队列中,随后发送消息的函数立即返回,当前线程的职责就此结束,不需要做其他耗时处理。在主线程中,会有一个循环的代码,一直轮询这个消息队列,当有消息来时,就处理这个消息,这样我们就实现了线程的切换。
  • 值得注意的是主线程中的looper 会一直循环下去,并不会退出,一旦退出,那么我们的app 就退出了。所以在looper 循环读取消息的过程中,有一个mQuitAllowed变量,当这个变量为false 的时候,即使线程中的消息队列没有消息,它也不会退出循环,而是一直等待在next() 方法中.
public static void prepareMainLooper() {
    // 主线程不能退出,故需要传入false
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

# ActivityThread 中的Handler

  • 这里将会涉及到Activity 的启动相关流程,如对这部分感兴趣的读者请移步阅读Activity 的启动流程相关文章
  • 在ActivityThread 的main 方法中,可以看见,调用了prepareMainLooper函数对当前线程(这里我们称为主线程)的looper的初始化,中间进行了一系列的其他初始化操作,随后调用loop 函数进行消息队列的循环,如果主线程结束了这个循环,那么程序就会抛出异常,因为我们主线程的looper 不应该在这里退出。
// frameworks/base/core/java/android/app/ActivityThread.java
public static void main(String[] args) {
    ....
    //modify for prefork blank process end
    Looper.prepareMainLooper();
    ....
    ActivityThread thread = new ActivityThread();
    ....
    /// 开启消息循环
    Looper.loop();
    /// 如果退出循环,我们的程序就结束了,抛出异常
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

  • looper 循环开启
//frameworks/base/core/java/android/os/Looper.java
public static void loop() {
    /// 获取当前线程的looper 对象
    final Looper me = myLooper();
    if (me == null) {
        // 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;
    // 获取消息队列的链表头部
    final MessageQueue queue = me.mQueue;

    ....

    boolean slowDeliveryDetected = false;

    for (;;) {
        // 这里将有可能会产生阻塞
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        ...

        try {
            /// 开始分发消息,将调用对应的target目标处理这个消息
            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();
    }
}
  • 分析一下next()函数,取出下一条消息
Message next() {
    //当queue退出的时候,ptr被置为0,这时返回null,此时looper也会退出循环
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }


    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    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;
            //判断是不是屏障消息,如果则遍历链表找到异步消息,优先执行
            //1.屏障消息的target为空,并且是直接插入到消息队列头部,目的是为了让绘制任务尽快被执行
            //2.当系统有屏幕绘制请求时,会发送一个屏障消息到消息队列
            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 {
                    // 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 正式被使用
                    msg.markInUse();
                    // 返回给loop 循环函数,让其执行消息处理
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            // 如果正在退出,需要dispose ,并返回一个null ,结束主线程的looper 循环
            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.
            //初始化闲时任务mPendingIdleHandlers,即当消息队列没有需要处理的消息时,
            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);
        }

        // 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;
            //如果queueIdle返回true,代表保留该任务,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.
        nextPollTimeoutMillis = 0;
    }
}
  • 这个闲时任务有些鸡肋,当主线程繁忙的时候,它可能一直得不到执行,而当主线程闲的时候,如果我们没有将它移除的话,它可能很快地被执行多次。由于它执行时机的不确定性,暂时想不到很好的应用场景,以至于不看源码几乎不知道还有这么个机制。

  • 分析一下发送消息的函数

  • 我们在发送消息的时候最终都会调用sendMessageAtTime函数,最终都会走到enqueueMessage函数中

  • Handler中的enqueueMessage 会调用queue中的enqueueMessage函数

// frameworks/base/core/java/android/os/Handler.java

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

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        // 将msg 的target 指定为当前的handler 
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
}
  • 重点查看一下MessageQueue函数
// frameworks/base/core/java/android/os/MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
    // 判断target 是否为空,因为最终处理消息的还是这个target对象在处理
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }

    synchronized (this) {
        // 是否已经在被使用了
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        // 如果正在退出,直接回收掉这个msg消息,并返回false 
        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;
        //是否需要唤醒事件队列,其实也就是唤醒queue.next()
        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;
}

# Message 详细解析

尽管Message本身的构造方式是公共的,但实现Message对象的最好方法确实是通过Message.obtain()函数返回,或者通过Handler.obtainMessage()方法,这个函数最终还是调用了obtain函数。

  • 享元模式的应用
  • Message 消息池其实是一个链表,它自带的一个属性 next 字段指向下一个Message
  • 对同步锁不清楚的读者请移步同步锁相关系列的文章
  • Message 实现了 Parcelable 接口
// frameworks/base/core/java/android/os/Message.java
    // 一般应用层开发者用来指定消息类型,可以自定义
    public int what;

    @UnsupportedAppUsage
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    //消息发送的时间戳
    public long when;
    // 数据
    /*package*/ Bundle data;
    // 目标handler 
    @UnsupportedAppUsage
    /*package*/ Handler target;

    // 回调函数
    @UnsupportedAppUsage
    /*package*/ Runnable callback;

    // sometimes we store linked lists of these things
    @UnsupportedAppUsage
    /*package*/ Message next;


    /** @hide */
    public static final Object sPoolSync = new Object();
    private static Message sPool;
    private static int sPoolSize = 0;///当前消息池有多少MSG

    private static final int MAX_POOL_SIZE = 50; /// 消息池大小

    private static boolean gCheckRecycle = true;


    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                // 取出链表第一个msg 并清空
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

  • 先是一个对象的同步锁,如果sPools不为空的话,把sPools赋值给Message,并把Message的next赋值给sPool,当前Message的next为空null,flags标记清零,sPoolSize减1,返回我们需要的Message. 如果sPools为空的话直接new一个Message.
  • 虽然sPool看上去像一个消息池,但其实是一个Message对象,每一个Message 对象通过next指向下一个Message(最后一个Message的next为null)形成一个链表,Message对象就成了一个可用的Message池。
  • 或许读者会产生疑问,Message对象是什么时候放入链表中的呢?从obtain函数并没有看见存储Message的操作,其实它是在Handler讲解里面提到的调用了recycle 回收的时候放入池子中的。
// frameworks/base/core/java/android/os/Message.java
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() {
    // 清空Message
    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) {
        // 如果池子里已有的Message对象数量小于定义的池子最大值,放入链表的开头

        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}