Android帧绘制流程深度解析 (一)

Android帧绘制技术有很多基础的知识,比如多buffer、vsync信号作用等基础知识点很多笔记讲的已经很详细了,我也不必再去总结,所以此处不再过多赘述安卓帧绘制技术,基础知识这篇文章总结的很好,一文读懂"系列:Android屏幕刷新机制 - 掘金 (juejin.cn),本文重点记录代码的学习笔记。
代码流程图:在这里插入图片描述

1、 Invalidate()

Android帧绘制的入口就是invalidate()函数,在调用invalidate函数后,当前界面上的内容就会被设置为脏区,需要进行更新。

void invalidate() {
    mDirty.set(0, 0, mWidth, mHeight);
    if (!mWillDrawSoon) {//防止在更新界面时,重复触发更新流程
        scheduleTraversals();
    }
}

2、 scheduleTraversals

void scheduleTraversals() {
    if (!mTraversalScheduled) {//同样是防止重复触发界面更新流行
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();//开启同步屏障,让帧绘//制的消息尽快得到处理
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);//(1)发送帧绘制的消息,以请求vsync信号。
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

(1)处通过choreographer触发请求vsync流程。
此处的调用逻辑如流程图中5-7,最后调用到了postCallbackDelayedInternal方法:

private void postCallbackDelayedInternal(int callbackType,
        Object action, Object token, long delayMillis) {
    if (DEBUG_FRAMES) {
        Log.d(TAG, "PostCallback: type=" + callbackType
                + ", action=" + action + ", token=" + token
                + ", delayMillis=" + delayMillis);
    }

    synchronized (mLock) {
        final long now = SystemClock.uptimeMillis();//获取当前时间
        final long dueTime = now + delayMillis;//上面调用过来的时候,时间延迟为0
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);//将callback放进callbackQueue中,不同的callbackType对应不用的事件,后面再具体说明,此处type是Traversal

        if (dueTime <= now) {
            scheduleFrameLocked(now);//立即触发帧绘制
        } else {
            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
            msg.arg1 = callbackType;
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, dueTime);//时间还没到,所以通过延迟消息触发请求vsync流程
        }
    }
}

3、 scheduleFrameLocked:

因为帧绘制都是立即执行的,所以此处直接查看scheduleFrameLocked,其实如果是走到延迟消息的话,流程也差不多,这里大概的解释下:
首先通过mHandler.sendMessageAtTime(msg, dueTime)发送延时消息,消息的target就是choreographer中的mHandler,而mHandler的类型是FrameHandler。所以找到FrameHandler的定义:

private final class FrameHandler extends Handler {
    public FrameHandler(Looper looper) {
        super(looper);
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_DO_FRAME:
                doFrame(System.nanoTime(), 0, new DisplayEventReceiver.VsyncEventData());
                break;
            case MSG_DO_SCHEDULE_VSYNC:
                doScheduleVsync();
                break;
            case MSG_DO_SCHEDULE_CALLBACK:
                doScheduleCallback(msg.arg1);//此处会走到这里。
                break;
        }
    }
}

可以看到此处会走到case2,然后继续往下找就是这里:

void doScheduleCallback(int callbackType) {
    synchronized (mLock) {
        if (!mFrameScheduled) {
            final long now = SystemClock.uptimeMillis();
            if (mCallbackQueues[callbackType].hasDueCallbacksLocked(now)) {
                scheduleFrameLocked(now);
            }
        }
    }
}

所以最终还是走到了scheduleFrameLocked方法了。再次回到scheduleFrameLocked方法的解读:

private void scheduleFrameLocked(long now) {
    if (!mFrameScheduled) {//同样是为了防止重复触发该流程。
        mFrameScheduled = true;
        if (USE_VSYNC) {//如果是使用Vsync机制,这里好像是android4之后都是默认使用vsync机制的
            if (DEBUG_FRAMES) {
                Log.d(TAG, "Scheduling next frame on vsync.");
            }
            if (isRunningOnLooperThreadLocked()) {//如果当前线程有looper的话
                scheduleVsyncLocked();//请求vsync
            } else {//如果当前线程无Looper的话需要通过消息机制发送消息到Looper线程进行触发
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtFrontOfQueue(msg);
            }
        } else {//这里就是无vsync机制时的绘帧逻辑
            final long nextFrameTime = Math.max(
                    mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
            if (DEBUG_FRAMES) {
                Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
            }
            Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, nextFrameTime);
        }
    }
}

上述代码中有个比较有意思的概念,就是isRunningOnLooperThreadLocked()这个方法,这个方法的解读就是运行在looper线程上。方法定义如下:

private boolean isRunningOnLooperThreadLocked() {
    return Looper.myLooper() == mLooper;
}

这里可以参考之前的说Android消息机制这节内容,这个myLooper方法还会牵扯到ThreadLocal的内容,这里就不再细说,感兴趣的可以看看:Android消息机制-CSDN博客
主要思想就是不是每个线程都会有Looper对象(choreographer的初始化参数中有Looper这个量),但是有Looper的都可以初始化一个choreographer,所以这里就是检查当前线程的Looper是否与choreographer关联的线程相同。

4、 scheduleVsyncLocked:

该方法用来请求Vsync信号:

private void scheduleVsyncLocked() {
    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#scheduleVsyncLocked");
        mDisplayEventReceiver.scheduleVsync();
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

public void scheduleVsync() {
    if (mReceiverPtr == 0) {
        Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
                + "receiver has already been disposed.");
    } else {
        nativeScheduleVsync(mReceiverPtr);
    }
}

可以看到该方法能直接回调到native层的方法以请求Vsync。这里的native层的调用后续会再详细详细讲解,本次暂时还未学习,剩下的流程13-20还需要再仔细看看

相关推荐

  1. Android View绘制流程详解

    2024-06-15 14:22:01       52 阅读
  2. Android中View的绘制流程

    2024-06-15 14:22:01       43 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-06-15 14:22:01       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-06-15 14:22:01       106 阅读
  3. 在Django里面运行非项目文件

    2024-06-15 14:22:01       87 阅读
  4. Python语言-面向对象

    2024-06-15 14:22:01       96 阅读

热门阅读

  1. Gobject tutorial 二

    2024-06-15 14:22:01       25 阅读
  2. 刷题理解JVM内部的数据存储

    2024-06-15 14:22:01       23 阅读
  3. C 语言实例 - 输出数组

    2024-06-15 14:22:01       28 阅读
  4. c语言中的gets()函数记录

    2024-06-15 14:22:01       31 阅读
  5. 八、BGP

    八、BGP

    2024-06-15 14:22:01      22 阅读
  6. TypeScript中的Symbol,确实唯一。。。

    2024-06-15 14:22:01       28 阅读
  7. 认识一些分布-关于极值点分布的一些知识

    2024-06-15 14:22:01       28 阅读
  8. 把本机的bash构建到docker镜像里面

    2024-06-15 14:22:01       22 阅读
  9. AbpVnext中的DDD指南之聚合根

    2024-06-15 14:22:01       35 阅读
  10. grep命令知多少

    2024-06-15 14:22:01       25 阅读
  11. prometheus relabel_configs 标签重写

    2024-06-15 14:22:01       34 阅读