当前位置: 首页 > news >正文

​一帧图像的Android之旅 :应用的首个绘制请求

#BEGIN#

Android 框架提供了各种用 2D 和 3D 图形渲染的 API 与制造商的图形驱动程序实现方法交互,在Android平台上应用开发者可通过三种方式将图像绘制到屏幕上:Canvas、OpenGLES、Vulkan 无论使用什么方式进行内容的生产,这个离用户最近的图形系统都扮演者一个非常重要的角色,在此系统一系列关键组件的协同帮助下,最终按照我们的预期将画面展示给用户。

无论是从系统工程师角度或者应用开发者角度来看,这个离用户最近的系统都是非常值得我们去一探其工作原理的,通过了解Android图形系统的工作流程,可以帮助我们在实际生产设计应用或者分析图形性能问题时做出高效明智的选择。

正所谓窥一斑而知全豹,由于整个图形子系统过于庞大,直接啃代码不知从何下手,所以我希望通过观察Demo中一个具体图像帧的整个生命周期可以一窥整个Android系统的图形系统的工作流程以及工作模式。

Demo

在跟踪代码流程之前先看一个简单的动图直观的了解一下各个关键器件之间如何进行协同工作有个基本的概念:

显示器在显示动态画面时,每一帧图像的显示实际上是由上到下逐行扫描的,当扫描完最后一行时,需要将扫描点挪到左上角继续下一次扫描,而这个重置扫描点的动作称为vblank,在vblank之前将会产生一个信号称为vsync,对于Android系统而言,此信号将会驱动图形生产的逻辑代码在CPU上运行,而工作过程中CPU偏向于收集应用的绘制意图,收集完成后将指令一通刷到GPU中,GPU则是将这些指令再次展开执行,将一帧新的图像渲染到后缓冲区中,这个过程相对CPU是异步的.

而作为首篇文章,要跟踪的流程自然是应用如何发出首帧绘制请求的.毕竟有了vsync,我们应用的图形生产代码才会工作起来.

根据跟踪代码绘制的时序图:

Activity

在Android系统中,当用户通过桌面点击应用的图标后,桌面程序是通过系统API启动一个我们事先在应用清单文件里注册的Activity,所以对于用户来说,看到的第一个关键组件就是Activity,所以一切的一切,我打算从这个响应界面的Activity的生命周期作为起点进行跟踪.

attch

函数位于:Activity.java

    final void attach(...) {
        ...
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);  
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        ...
      }

补充一些个人理解:

  • Activity中持有一个Window(PhoneWindow),Activity的主要功能是为了控制生命周期.Window里的逻辑用于提供参与图像生成的能力并装饰.

  • WindowManager是个继承了ViewManager的接口,在当前版本中,其唯一实现类为WindowManagerImpl,其作用是提供对低级系统服务WindowManagerGlobal(下称WMG)与WindowManagerServie(下称WMS)的访问接口,并不提供具体逻辑实现.

  • 当首个Activity attch时候,会创建一个与当前Activity相关联的WindowManagerImpl,并触发惰性单例的WMG初始化.

setContentView

该函数的作用就是指定内容区域参与图像生成的View,实际上是通过PhoneWindow提供了逻辑支持.具体的这里不展开,网上有很多大佬文章讲的非常细.

handleResumeActivity

函数位于:ActivityThread.java

@Override
    public void handleResumeActivity(...) {
            ...
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                } else {
                   ...
                }
            }
            ...

关键信息:

  • 这部分与Dialog或者Popup其实是一致的,因为这些都是一些高级别组件,相对于更低级的系统组件来说是没有dialog/activity这种概念的,都是对低级别组件的封装.

  • 关键函数即wm.addView,这个wm就是我们在attach创建的那个WindowManager.所以我们直接看到WMG里的具体逻辑中:

addView

该函数位于:WindowManagerImpl.java

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
       ...
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

关键信息:

  • ViewRootImpl是个非常重要的类,将会在这里创建,即调用几次addView就有几个ViewRootImpl.

  • 由于WMG是单例的,所以该应用中所创建的所有DecorView、ViewRootImpl都会保存在这里.以便有公共事件发生时,系统可以统一调度.

  • 最后setView完成,WMG这段流程就交给ViewRootImpl继续进行处理.

ViewRootImpl

关于ViewRootImpl,我们先看一下该类的构造函数中也有一些关键信息:

public ViewRootImpl(Context context, Display display) {
        mContext = context;
        mWindowSession = WindowManagerGlobal.getWindowSession();
        ...
        mChoreographer = Choreographer.getInstance();

关键信息:

  • WindowSession在这里被创建. 该对象用于应用和SystemServer进程中的WMS进行通信.

  • 当前线程的Choreographer在这里被初始化. 该对象在Android上层View系统中扮演着非常重要的角色,后续流程中马上就会用到.

另外,Surface以及SurfaceControl也是在声明的地方进行了初始化,但仅仅只是java对象的初始化,真正的nativeObj并未在这里执行构造,所以这里直接略过.至于这两个对象的具体作用这里先不需要了解,后续进行剖析.

setView

此函数代码较长,以下只将我认为与图像生成相关的关键代码进行了保留:

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
       ...
       enableHardwareAcceleration(attrs);
       ...
       int res; /* = WindowManagerImpl.ADD_OKAY; */
       // Schedule the first layout -before- adding to the window
       // manager, to make sure we do the relayout before receiving
       // any other events from the system.
       requestLayout();
       ...
       try {
           mOrigWindowType = mWindowAttributes.type;
           mAttachInfo.mRecomputeGlobalAttributes = true;
           collectViewAttributes();
           res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                   getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                   mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                   mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                   mTempInsets);
           setFrame(mTmpFrame);
       } catch (RemoteException e) {
           mAdded = false;
           mView = null;
           mAttachInfo.mRootView = null;
           mInputChannel = null;
           mFallbackEventHandler.setView(null);
           unscheduleTraversals();
           setAccessibilityFocus(null, null);
           throw new RuntimeException("Adding window failed", e);
       } finally {
           if (restore) {
               attrs.restore();
           }
       }
       
      
       mPendingOverscanInsets.set(0, 0, 0, 0);
       mPendingContentInsets.set(mAttachInfo.mContentInsets);
       mPendingStableInsets.set(mAttachInfo.mStableInsets);
       mPendingDisplayCutout.set(mAttachInfo.mDisplayCutout);
       mPendingVisibleInsets.set(0, 0, 0, 0);
       mAttachInfo.mAlwaysConsumeSystemBars =
               (res & WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS) != 0;
       mPendingAlwaysConsumeSystemBars = mAttachInfo.mAlwaysConsumeSystemBars;
       mInsetsController.onStateChanged(mTempInsets);
       
       mAppVisible = (res & WindowManagerGlobal.ADD_FLAG_APP_VISIBLE) != 0;
                ...
            }
        }
    }

关键信息:

  • 硬件加速相关的主要对象在此被创建,(//TODO 由于涉及到的流程会比较长,所以这里先不展开分析.)

  • 在该函数中,在将View真正添加到系统之前,调用了requestLayout函数,该函数的主要作用是请求一次垂直同步信号(VSYNC).

  • 接下来使用之前的单例WindowSession与WMS进行通信,将ViewRootImpl与系统的WMS建立连接.

  • 这里需要注意addToDisplay中的第一个参数,其类型为ViewRootImpl的一个内部类“W”,作用是供WMS进行回调.

requestLayout

此函数是非常关键的一步,因为从这里开始,我们已经进入了第一帧的触发流程:

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    ...
        void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
    

关键信息:

  • requestLayout会将LayoutRequested设置为true.用来标记应用请求了layout.

  • 接下来scheduleTraversals里调用了设置了一个Handle屏障,来确保没有其他非异步任务影响接下来的任务.

  • 调用mChoreographer.postCallback放进去了一个callBack.注意其传入的参数"TRAVERSAL",以及一个callBack.这是非常关键的一步.

  • 通知渲染器准备:"我即将要请求一次渲染了".

Choreographer

这个类是线程单例的,在ViewRootImpl构造阶段首次触发主线程的单例创建,我们还是看一下这个类的构造函数,这里也有一些不可忽略的关键组件的创建:

private Choreographer(Looper looper, int vsyncSource) {
        mLooper = looper;
        mHandler = new FrameHandler(looper);
        mDisplayEventReceiver = USE_VSYNC
                ? new FrameDisplayEventReceiver(looper, vsyncSource)
                : null;
        mLastFrameTimeNanos = Long.MIN_VALUE;

        mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());

        mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
        for (int i = 0; i <= CALLBACK_LAST; i++) {
            mCallbackQueues[i] = new CallbackQueue();
        }
        // b/68769804: For low FPS experiments.
        setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR, 1));
    }

关键信息:

  • 触发创建的线程必须有looper(即调用wm.addView的如果是子线程,那么在调用之前需要准备好looper)

  • FrameDisplayEventReceiver在这里被创建(也是非常重要的一个类,其作用是接收跨进程的垂直同步信号)

  • CallbackQueue数组的创建,大小为CALLBACK_LAST,即为每一种Callback类型创建一个CallbackQueue.

postCallback

继续跟着requestLayout的流程往下走到postCallback函数中,其最终执行函数如下:

private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
...
        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

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

这里虽然有两个分支,但最后执行的函数都是一样的,只是根据dalayMillis来确定一个合适的实际执行时间:

  • 1.dalayMillis为0,即立即执行scheduleFrameLocked.

  • 2.dalayMillis不为0,表明是一个延迟请求,将此次请求放入Handler,在合适的时间再执行scheduleFrameLocked

scheduleFrameLocked

private void scheduleFrameLocked(long now) {
        if (!mFrameScheduled) {
            mFrameScheduled = true;
            if (USE_VSYNC) {
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "Scheduling next frame on vsync.");
                }

                // If running on the Looper thread, then schedule the vsync immediately,
                // otherwise post a message to schedule the vsync from the UI thread
                // as soon as possible.
                if (isRunningOnLooperThreadLocked()) {
                    scheduleVsyncLocked();
                } else {
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtFrontOfQueue(msg);
                }
            } else {
                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);
            }
        }
    }
    
    private void scheduleVsyncLocked() {
        mDisplayEventReceiver.scheduleVsync();
    }
    

关键信息:

  • 这里主要判断是否启用了VSYNC,若未启用VSYNC,则直接执行doFrame(现在市面上绝大多数的Android设备都是以VSYNC进行驱动绘制的).

  • 若启用了VSYNC,则调用FrameDisplayEventReceiver请求一次VSYNC信号.

DisplayEventReceiver

该类是FrameDisplayEventReceiver的父类,java层只有访问接口,所有逻辑均通过JNI调用到CPP,对应的类为NativeDisplayEventReceiver,位于该文件中: frameworks/base/core/jni/android_view_DisplayEventReceiver.cpp 所以我们后面直接分析NativeDisplayEventReceiver这个类.

newInstance

这个类的构造函数很简单,JNI调用到CPP会为其构造一个NativeDisplayEventReceiver对象,并返回给java层:

public DisplayEventReceiver(Looper looper, int vsyncSource, int configChanged) {
...
     mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,
                vsyncSource, configChanged);
    }

static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
    jobject messageQueueObj, jint vsyncSource, jint configChanged) {
        ...
    sp<NativeDisplayEventReceiver> receiver = new NativeDisplayEventReceiver(env,
            receiverWeak, messageQueue, vsyncSource, configChanged);
    status_t status = receiver->initialize();
}

NativeDisplayEventReceiver的构造函数中并没有什么重要的内容,而其父类则不同,其在创建时,会将mReceiver(DisplayEventReceiver(native))变量一同初始化

DisplayEventDispatcher::DisplayEventDispatcher(const sp<Looper>& looper,
        ISurfaceComposer::VsyncSource vsyncSource,
        ISurfaceComposer::ConfigChanged configChanged) :
        mLooper(looper), mReceiver(vsyncSource, configChanged), mWaitingForVsync(false) {
    ALOGV("dispatcher %p ~ Initializing display event dispatcher.", this);
}

而这个DisplayEventReceiver便是真正负责搭建起SF与APP之间通信管道的一个类,因为在它的构造函数中将会创建BitTube并传递给SF进程,用于SF与APP进程通信:

DisplayEventReceiver::DisplayEventReceiver(ISurfaceComposer::VsyncSource vsyncSource,
                                           ISurfaceComposer::ConfigChanged configChanged) {
    sp<ISurfaceComposer> sf(ComposerService::getComposerService());
    if (sf != nullptr) {
        mEventConnection = sf->createDisplayEventConnection(vsyncSource, configChanged);
        if (mEventConnection != nullptr) {
            mDataChannel = std::make_unique<gui::BitTube>();
            mEventConnection->stealReceiveChannel(mDataChannel.get());
        }
    }
}

initialize

那APP进程内如何接收这些数据呢?这需要我们再回头看到NativeDisplayEventReceiver构造后调用的initialize函数中:

status_t DisplayEventDispatcher::initialize() {
    status_t result = mReceiver.initCheck();
    if (result) {
        ALOGW("Failed to initialize display event receiver, status=%d", result);
        return result;
    }

    int rc = mLooper->addFd(mReceiver.getFd(), 0, Looper::EVENT_INPUT,
            this, NULL);
    if (rc < 0) {
        return UNKNOWN_ERROR;
    }
    return OK;
}

关键信息:

  • 这里的方案是,使用Looper监听FD.

  • 通过将BitTube的FD添加到Looper中,这步执行完后,当对端有事件写入(例如Vsync事件)BitTube时,DisplayEventDispatcher的handleEvent函数就会被回调.

  • 通信管道建立好了,事件监听的工作也安排妥当了,如此一来我们就可以在事件到来时及时处理了,这样FrameDisplayEventReceiver的初始化流程就结束了。

scheduleVsync

在了解了DisplayEventReceiver初始化的工作后,就可以继续跟踪请求Vsync的流程了:

status_t DisplayEventDispatcher::scheduleVsync() {
    if (!mWaitingForVsync) {
        ALOGV("dispatcher %p ~ Scheduling vsync.", this);

        // Drain all pending events.
        nsecs_t vsyncTimestamp;
        PhysicalDisplayId vsyncDisplayId;
        uint32_t vsyncCount;
        if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount)) {
            ALOGE("dispatcher %p ~ last event processed while scheduling was for %" PRId64 "",
                    this, ns2ms(static_cast<nsecs_t>(vsyncTimestamp)));
        }

        status_t status = mReceiver.requestNextVsync();
        if (status) {
            ALOGW("Failed to request next vsync, status=%d", status);
            return status;
        }

        mWaitingForVsync = true;
    }
    return OK;
}

bool DisplayEventDispatcher::processPendingEvents(
        nsecs_t* outTimestamp, PhysicalDisplayId* outDisplayId, uint32_t* outCount) {
    bool gotVsync = false;
    DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
    while ((n = mReceiver.getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {
        ALOGV("dispatcher %p ~ Read %d events.", this, int(n));
        for (ssize_t i = 0; i < n; i++) {
            const DisplayEventReceiver::Event& ev = buf[i];
            switch (ev.header.type) {
            case DisplayEventReceiver::DISPLAY_EVENT_VSYNC:
                gotVsync = true;
                *outTimestamp = ev.header.timestamp;
                *outDisplayId = ev.header.displayId;
                *outCount = ev.vsync.count;
                break;
                ...
        }
    }
    if (n < 0) {
        ALOGW("Failed to get events from display event dispatcher, status=%d", status_t(n));
    }
    return gotVsync;
}

关键信息:

  • 首先判断在当前周期中是否已经请求过VSYNC了,如果已经请求过,那么这个函数什么都不会做.

  • processPendingEvents函数在这里的作用是再次触发事件处理确保排空管道.(对于Vsync信号,他只关注管道中的最后一个Vsync事件,并且这里不做处理,而是由调用者去处理).

  • requestNextVsync是真正触发与远端进程(SF)通信:“我需要一个Vsync信号”(//TODO 具体SF是按照什么规则发送的信号后面再看).

  • 将mWaitingForVsync设置为true,保证在等待Vsync信号的这段时间里不会再次发出请求.

handleEvent

当SF将事件写入BitTube后,handleEvent将会被回调:

int DisplayEventDispatcher::handleEvent(int, int events, void*) {
    if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
        return 0; // remove the callback
    }

    if (!(events & Looper::EVENT_INPUT)) {
        return 1; // keep the callback
    }
    ...
    if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount)) {
        ALOGV("dispatcher %p ~ Vsync pulse: timestamp=%" PRId64 ", displayId=%"
                ANDROID_PHYSICAL_DISPLAY_ID_FORMAT ", count=%d",
                this, ns2ms(vsyncTimestamp), vsyncDisplayId, vsyncCount);
        mWaitingForVsync = false;
        dispatchVsync(vsyncTimestamp, vsyncDisplayId, vsyncCount);
    }
    return 1; // keep the callback
}

java:
private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
   //又转一手调用到onVsync中
   onVsync(timestampNanos, physicalDisplayId, frame);
}

关键信息:

  • looper event是和epoll event是相对应的,所以当出现一些错误事件时,我们需要将此callback移除.

  • 当出现input以外的事件时,直接跳过即可,因为我们只关注input.

  • 出现input事件时,调用processPendingEvents 从BitTube读取一个object.并且将mWaitingForVsync设置为false,表明可以进行下次Vsync请求了.

  • 读到Vsync事件后,即调用dispatchVsync通过JNI调用到JAVA层DisplayEventReceiver的同名函数中,又转一手到onVsync中.

onVsync

经过一圈的调用,响应信号终于还是回到java了,到现在为止已经完成了VSYNC的请求与接收,那么接下来事情就看下当初这个VSYNC的请求者,他会拿这个VSYNC做些什么.

public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
    long now = System.nanoTime();
    if (timestampNanos > now) {
        timestampNanos = now;
    }
    if (mHavePendingVsync) {
        Log.w(TAG, "Already have a pending vsync event.  There should only be "
                + "one at a time.");
    } else {
        mHavePendingVsync = true;
    }
    mTimestampNanos = timestampNanos;
    mFrame = frame;
    Message msg = Message.obtain(mHandler, this);
    msg.setAsynchronous(true);
    mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}

public void run() {
    mHavePendingVsync = false;
    doFrame(mTimestampNanos, mFrame);
}

关键信息:

  • timestampNanos是HAL生成Vsync事件的时间点,这里要保证这个值的真实可靠性(事件不可能发生在未来)

  • FrameDisplayEventReceiver实现了Runnable接口,这里将自身添加到Handler中,所以当早于timestampNanos的异步消息执行完成后(别忘了我们之前设置同步屏障的步骤),doFrame将得到调用.

doFrame

看到了吗,Vsync开始驱动Java层的一些组件工作了,而doFrame即是被Vsync实际驱动的第一个函数:

void doFrame(long frameTimeNanos, int frame) {
    final long startNanos;
    synchronized (mLock) {
        ...
        long intendedFrameTimeNanos = frameTimeNanos;
        startNanos = System.nanoTime();
        final long jitterNanos = startNanos - frameTimeNanos;
        if (jitterNanos >= mFrameIntervalNanos) {
            final long skippedFrames = jitterNanos / mFrameIntervalNanos;
            if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
                        + "The application may be doing too much work on its main thread.");
            }
            final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
            if (DEBUG_JANK) {
                Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
                        + "which is more than the frame interval of "
                        + (mFrameIntervalNanos * 0.000001f) + " ms!  "
                        + "Skipping " + skippedFrames + " frames and setting frame "
                        + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
            }
            frameTimeNanos = startNanos - lastFrameOffset;
        }

        if (frameTimeNanos < mLastFrameTimeNanos) {
            if (DEBUG_JANK) {
                Log.d(TAG, "Frame time appears to be going backwards.  May be due to a "
                        + "previously skipped frame.  Waiting for next vsync.");
            }
            scheduleVsyncLocked();
            return;
        }

        mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
        mFrameScheduled = false;
        mLastFrameTimeNanos = frameTimeNanos;
    }

    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
        AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

        mFrameInfo.markInputHandlingStart();
        doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);

        mFrameInfo.markAnimationsStart();
        doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
        doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);

        mFrameInfo.markPerformTraversalsStart();
        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

        doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
    } finally {
        AnimationUtils.unlockAnimationClock();
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
...
}
  • 注意,frameTimeNanos就是HAL生成Vsync的时间,而该函数中记录的startNanos即是真正开始处理Vsync的时间,这两个时间的差值称之为抖动时间.

  • 当抖动时间超过一个屏幕刷新周期(例如60HZ的屏幕,它的屏幕刷新周期为 1000ms/60hz),那么意味着可能发生了跳帧.

  • 当跳帧数量达到debug.choreographer.skipwarning配置的阈值后,将会打印 Skipped xxx frames! The application may be doing too much work on its main thread.

  • 抖动发生后,这里还会修正frameTimeNanos的时间为离当前时间最近的一个已错过的Vsync时间.因为后面frameinfo需要记录实际处理vsync的时间是在哪个刷新周期里,所以这里的值必须向左消除偏差.

  • 当前这次Vsync时间已经小于之前处理过的最后一个Vsync时间,则这次Vsync将被丢弃,再请求一个新的Vsync.

  • FrameInfo记录Vsync时间,以及Vsync真正开始处理的时间.

  • 接下来主要处理这三种类型的Callback:CALLBACK_INPUT,CALLBACK_ANIMATION,CALLBACK_TRAVERSAL,开始处理之前都会打一个时间戳到FrameInfo里.

  • 由于我们之前流程中只添加了CALLBACK_TRAVERSAL类型的callback,doCallbacks所做的事情也很简单,就是拿出对应的Callback然后执行调用,所以我们这里再回到ViewRootImpl那里.

TraversalRunnable

这个回调的作用就是将工作转一手给doTraversal

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}

doTraversal

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }

        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

这个函数的作用也很简单,执行到了就说明之前请求到的Vsync已经过来了,我们需要准备处理View调用了,所以在一开始的时候,先将之前设置的同步屏障移除掉.performTraversals正式开始遍历View树.

到这里,应用进程中关于请求绘制信号的流程就跟踪结束了.但是这并不是整个完整的流程,涉及到与SF的信号是怎么来的? 与SF交互的详细逻辑并没有去跟踪,所以下一篇会跟进看一下SF里面响应交互时的逻辑.遍历View树的逻辑跟踪放到SF之后继续跟.


技术交流,欢迎加我微信:ezglumes ,拉你入技术交流群。

推荐阅读:

音视频面试基础题

OpenGL ES 学习资源分享

一文读懂 YUV 的采样与格式

OpenGL 之 GPUImage 源码分析

推荐几个堪称教科书级别的 Android 音视频入门项目

觉得不错,点个在看呗~

相关文章:

  • 【Android 音视频开发打怪升级:FFmpeg 音视频编解码篇】三、Android FFmpeg视频解码播放...
  • 声网 RTE 秋季挑战赛正式启动!6 万奖金等你赢,还有机会进入招聘绿色通道
  • 程序员的“三十而已”,你都30岁了,不会只有xxx吧?
  • 【每周一记】域名备案终于过啦~~
  • Android 10还没适配完,Android 11又要出了?
  • 高仿剪映视频多轨剪辑页实现
  • 移动直播技术知多少:基础原理解析 腾讯云直播接入
  • 【Android 音视频开发打怪升级:FFmpeg音视频编解码】四、Android FFmpeg+OpenSL ES音频解码播放...
  • 一个简简单单的Plugin入门
  • 给大家分享一下阿里三面的面试真题
  • WebRTC Android 开发学习环境搭建~
  • Android 11 最终 Beta 版发布,正式版即将到来!
  • NDK中使用 MediaCodec 编解码视频
  • 【资源分享】免费学 清华大学 · 游戏程序设计公开课啦!!!
  • 谈一谈Android上的SurfaceTexture
  • [原]深入对比数据科学工具箱:Python和R 非结构化数据的结构化
  • 《用数据讲故事》作者Cole N. Knaflic:消除一切无效的图表
  • iOS 系统授权开发
  • Java知识点总结(JavaIO-打印流)
  • k8s如何管理Pod
  • MD5加密原理解析及OC版原理实现
  • swift基础之_对象 实例方法 对象方法。
  • Yii源码解读-服务定位器(Service Locator)
  • 欢迎参加第二届中国游戏开发者大会
  • 微服务核心架构梳理
  • 我是如何设计 Upload 上传组件的
  • 学习HTTP相关知识笔记
  • 学习笔记TF060:图像语音结合,看图说话
  • 积累各种好的链接
  • 支付宝花15年解决的这个问题,顶得上做出十个支付宝 ...
  • ​RecSys 2022 | 面向人岗匹配的双向选择偏好建模
  • $.ajax中的eval及dataType
  • (二)WCF的Binding模型
  • (附源码)spring boot公选课在线选课系统 毕业设计 142011
  • (附源码)计算机毕业设计大学生兼职系统
  • (解决办法)ASP.NET导出Excel,打开时提示“您尝试打开文件'XXX.xls'的格式与文件扩展名指定文件不一致
  • (亲测)设​置​m​y​e​c​l​i​p​s​e​打​开​默​认​工​作​空​间...
  • (顺序)容器的好伴侣 --- 容器适配器
  • (一)Linux+Windows下安装ffmpeg
  • (转)GCC在C语言中内嵌汇编 asm __volatile__
  • (转)清华学霸演讲稿:永远不要说你已经尽力了
  • (总结)Linux下的暴力密码在线破解工具Hydra详解
  • .babyk勒索病毒解析:恶意更新如何威胁您的数据安全
  • .NET Remoting学习笔记(三)信道
  • .net Stream篇(六)
  • .NET/C# 反射的的性能数据,以及高性能开发建议(反射获取 Attribute 和反射调用方法)
  • .NET/C# 项目如何优雅地设置条件编译符号?
  • .NET/C# 在代码中测量代码执行耗时的建议(比较系统性能计数器和系统时间)
  • .NET学习全景图
  • [145] 二叉树的后序遍历 js
  • [BUG] Hadoop-3.3.4集群yarn管理页面子队列不显示任务
  • [C++提高编程](三):STL初识
  • [CISCN 2023 初赛]go_session
  • [CISCN2021 Quals]upload(PNG-IDAT块嵌入马)
  • [CTF]2022美团CTF WEB WP