Android 画面显示流程分析 (4)

发表于 2年以前  | 总阅读数:3578 次

7 . 画面更新流程

在我们前面几个章节的讨论中,我们从最底层的显示硬件,SOC 和 DDIC 的接口, linux 和 Userspace 的图形接口以及 APP 与 SurfaceFlinger,HWC service 三者的关系,了解了帧数据流动所经过的关键节点,并重点讨论了帧 buffer 是如何管理的,以及在流动过程中是如何做到同步的。接下来我们将从应用侧角度来从上到下看一下应用所绘制的画面是如何使用到我们上面所设计的流程中的。

7.1. 画布的申请

从前文 5.4 的讨论可知,应用侧对图层的操作是以 Surface 为接口的,其定义如下所示,它包含了一些更新画面相关的核心 api, 比如 dequeueBuffer/queueBuffer/connect/disconnect 等等。

Surface.h (frameworks\native\libs\gui\include\gui)

class Surface
    : public ANativeObjectBase<ANativeWindow, Surface, RefBase>
{
    ......
protected:
    virtual int dequeueBuffer(ANativeWindowBuffer** buffer, int* fenceFd);
    virtual int cancelBuffer(ANativeWindowBuffer* buffer, int fenceFd);
    virtual int queueBuffer(ANativeWindowBuffer* buffer, int fenceFd);
    virtual int perform(int operation, va_list args);
    ......
    virtual int connect(int api);
    ......
public:
    virtual int disconnect(int api,
            IGraphicBufferProducer::DisconnectMode mode =
                    IGraphicBufferProducer::DisconnectMode::Api);
}

那么应用要想画出它的画面,第一个要解决的问题就是应用侧的 Surface 对象是如何创建?它又是如何与 SurfaceFlinger 建立联系的?下面我们将从代码逻辑中找寻到它的建立过程。

在 Android 系统中每个 Activity 都有一个独立的画布(在应用侧称为 Surface, 在 SurfaceFlinger 侧称为 Layer), 无论这个 Activity 安排了多么复杂的 view 结构,它们最终都是被画在了所属 Activity 的这块画布上,当然也有一个例外,SurfaceView 是有自已独立的画布的,但此处我们先只讨论 Activity 画布的建立过程。

首先每个应用都会创建有自已的 Activity, 进而 Android 会为 Activity 创建一个 ViewRootImpl, 并调用到它的 performTraversals 这个函数 (篇幅所限这部分流程请读者自行阅读源码)。

该函数里会调用到 relayoutWindow 函数:

frameworks/base/core/java/android/view/ViewRootImpl.java

private void performTraversals() {
    ......
    relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
    ......
}

relayoutWindow 函数里会调用到 WindowSession 的 relayout 函数,这个函数是一个跨进程调用,mWindowSession 可以看作是 WMS 在应用侧的代表:

frameworks/base/core/java/android/view/ViewRootImpl.java

private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending) throws RemoteException {
     ......
     int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params,
                (int) (mView.getMeasuredWidth() * appScale + 0.5f),
                (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
                insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
                mTmpFrame, mTmpRect, mTmpRect, mTmpRect, mPendingBackDropFrame,
                mPendingDisplayCutout, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
                mTempControls, mSurfaceSize, mBlastSurfaceControl);
     ......
}

随着代码的执行让我们把视角切换到 system_server 进程(WMS 的 relayoutWindow 函数), 这里会调用 createSurfaceControl 去创建一个 SurfaceControl:

frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

public int relayoutWindow(Session session, IWindow client, int seq, LayoutParams attrs,
            int requestedWidth, int requestedHeight, int viewVisibility, int flags,
            long frameNumber, Rect outFrame, Rect outContentInsets,
            Rect outVisibleInsets, Rect outStableInsets, Rect outBackdropFrame,
            DisplayCutout.ParcelableWrapper outCutout, MergedConfiguration mergedConfiguration,
            SurfaceControl outSurfaceControl, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls, Point outSurfaceSize,
            SurfaceControl outBLASTSurfaceControl) {
      ......
      try {
          result = createSurfaceControl(outSurfaceControl, outBLASTSurfaceControl,
          result, win, winAnimator);
      } catch (Exception e) {
      ......
}

SurfaceControl 的创建过程,注意这里创建工作是调用 winAnimator 来完成的,注意下面那句 surfaceController.getSurfaceControl 会把创建出来的 SurfaceControl 通过形参 outSurfaceControl 传出去:

frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

private int createSurfaceControl(SurfaceControl outSurfaceControl,
            SurfaceControl outBLASTSurfaceControl, int result,
            WindowState win, WindowStateAnimator winAnimator) {
    ......
    WindowSurfaceController surfaceController;
    try {
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createSurfaceControl");
        surfaceController = winAnimator.createSurfaceLocked(win.mAttrs.type, win.mOwnerUid);
    } finally {
        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
    }
    if (surfaceController != null) {
        surfaceController.getSurfaceControl(outSurfaceControl);
        ......
    }
    ......
}

我们先看下创建过程,创建了一个 WindowSurfaceController,进而再创建 SurfaceControll:

frameworks/base/services/core/java/com/android/server/wm/WindowStateAnimator.java

WindowSurfaceController createSurfaceLocked(int windowType, int ownerUid) {
    ......
    /*WindowSurfaceController mSurfaceController;*/
    mSurfaceController = new WindowSurfaceController(attrs.getTitle().toString(), width,
                    height, format, flags, this, windowType, ownerUid);
    ......
}
WindowSurfaceController.java (frameworks\base\services\core\java\com\android\server\wm)

 WindowSurfaceController(String name, int w, int h, int format,
            int flags, WindowStateAnimator animator, int windowType, int ownerUid) {
     ......
     final SurfaceControl.Builder b = win.makeSurface()
                .setParent(win.getSurfaceControl())
                .setName(name)
                .setBufferSize(w, h)
                .setFormat(format)
                .setFlags(flags)
                .setMetadata(METADATA_WINDOW_TYPE, windowType)
                .setMetadata(METADATA_OWNER_UID, ownerUid)
                .setCallsite("WindowSurfaceController");
     ......
}

SurfaceControl.java (frameworks\base\core\java\android\view)

public static final @android.annotation.NonNull Creator<SurfaceControl> CREATOR
            = new Creator<SurfaceControl>() {
        public SurfaceControl createFromParcel(Parcel in) {
            return new SurfaceControl(in);
        }

        public SurfaceControl[] newArray(int size) {
            return new SurfaceControl[size];
        }
    };
private SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags,
            SurfaceControl parent, SparseIntArray metadata, WeakReference<View> localOwnerView,
            String callsite){
    ......
    mNativeObject = nativeCreate(session, name, w, h, format, flags,
                    parent != null ? parent.mNativeObject : 0, metaParcel);
    ......
}

到这里我们看到会通过 JNI 去创建 C 层的对象:

android_view_SurfaceControl.cpp (frameworks\base\core\jni)

static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj,
        jstring nameStr, jint w, jint h, jint format, jint flags, jlong parentObject,
        jobject metadataParcel) {
    ScopedUtfChars name(env, nameStr);//Surface名字, 在SurfaceFlinger侧就是Layer的名字
    ......
    sp<SurfaceComposerClient> client;
    ......
    status_t err = client->createSurfaceChecked(
            String8(name.c_str()), w, h, format, &surface, flags, parent, std::move(metadata));
    ......
}

C 层的 Surface 在创建时去调用 SurfaceComposerClient 的 createSurface 去创建,这个 SurfaceComposerClient 可以看作是 SurfaceFlinger 在 Client 端的代表

android_view_SurfaceControl.cpp (frameworks\base\core\jni)

status_t SurfaceComposerClient::createSurfaceChecked(const String8& name, uint32_t w, uint32_t h,
                                                     PixelFormat format,
                                                     sp<SurfaceControl>* outSurface, uint32_t flags,
                                                     SurfaceControl* parent, LayerMetadata metadata,
                                                     uint32_t* outTransformHint) {
      ......
      err = mClient->createSurface(name, w, h, format, flags, parentHandle, std::move(metadata),
                                     &handle, &gbp, &transformHint);
      ......
}

SurfaceComposerClient.cpp (frameworks\native\libs\gui)

sp<SurfaceControl> SurfaceComposerClient::createSurface(const String8& name, uint32_t w, uint32_t h,
                                                        PixelFormat format, uint32_t flags,
                                                        SurfaceControl* parent,
                                                        LayerMetadata metadata,
                                                        uint32_t* outTransformHint) {
    sp<SurfaceControl> s;
    createSurfaceChecked(name, w, h, format, &s, flags, parent, std::move(metadata),
                         outTransformHint);
    return s;
}
status_t SurfaceComposerClient::createSurfaceChecked(const String8& name, uint32_t w, uint32_t h,
                                                     PixelFormat format,
                                                     sp<SurfaceControl>* outSurface, uint32_t flags,
                                                     SurfaceControl* parent, LayerMetadata metadata,
                                                     uint32_t* outTransformHint) {
      .......
      err = mClient->createSurface(name, w, h, format, flags, parentHandle, std::move(metadata),
                                     &handle, &gbp, &transformHint);
      .......
}

跨进程呼叫 SurfaceFlinger:

ISurfaceComposerClient.cpp (frameworks\native\libs\gui)

status_t createSurface(const String8& name, uint32_t width, uint32_t height, PixelFormat format,
                           uint32_t flags, const sp<IBinder>& parent, LayerMetadata metadata,
                           sp<IBinder>* handle, sp<IGraphicBufferProducer>* gbp,
                           uint32_t* outTransformHint) override {
     return callRemote<decltype(&ISurfaceComposerClient::createSurface)>(Tag::CREATE_SURFACE,
                               name, width, height,
                               format, flags, parent,
                               std::move(metadata),
                               handle, gbp,
                               outTransformHint);
}

然后流程就来到了 SurfaceFlinger 进程,由于 SurfaceFlinger 支持很多不同类型的 Layer, 这里我们只以 BufferQueueLayer 为例, 当 SurfaceFlinger 收到这个远程调用后会 new 一个 BufferQueueLayer 出来。

SurfaceFlinger.cpp (frameworks\native\services\surfaceflinger)

status_t SurfaceFlinger::createLayer(const String8& name, const sp<Client>& client, uint32_t w,
                                     uint32_t h, PixelFormat format, uint32_t flags,
                                     LayerMetadata metadata, sp<IBinder>* handle,
                                     sp<IGraphicBufferProducer>* gbp,
                                     const sp<IBinder>& parentHandle, const sp<Layer>& parentLayer,
                                     uint32_t* outTransformHint) {
      ......
      case ISurfaceComposerClient::eFXSurfaceBufferQueue:
            result = createBufferQueueLayer(client, std::move(uniqueName), w, h, flags,
                                            std::move(metadata), format, handle, gbp, &layer);
      ......
}

至此完成了从一个应用的 Activity 创建到 SurfaceFlinger 内部为它创建了一个 Layer 来对应。那么应用侧又是如何拿到这个 Layer 的操作接口呢?

续续看代码:

注意到 WMS 的 createSurfaceControl 方法中是通过 getSurfaceControl 将 SurfaceControll 传出来的:

frameworks/base/services/core/java/com/android/server/wm/WindowSurfaceController.java

void getSurfaceControl(SurfaceControl outSurfaceControl) {
    outSurfaceControl.copyFrom(mSurfaceControl, "WindowSurfaceController.getSurfaceControl");
}

这个对象会通过 WindowSession 跨进程传到应用进程里,进而创建出 Surface:

frameworks/base/core/java/android/view/ViewRootImpl.java

private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending) throws RemoteException {
     ......
     int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params,
                (int) (mView.getMeasuredWidth() * appScale + 0.5f),
                (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
                insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
                mTmpFrame, mTmpRect, mTmpRect, mTmpRect, mPendingBackDropFrame,
                mPendingDisplayCutout, mPendingMergedConfiguration, mSurfaceControl, mTempInsets, //这里的mSurfaceControl是从WMS传来的
                mTempControls, mSurfaceSize, mBlastSurfaceControl);
     ......
     if (mSurfaceControl.isValid()) {
            if (!useBLAST()) {
                mSurface.copyFrom(mSurfaceControl);//通过Surface的copyFrom方法从SurfaceControl来创建这个Surface
            }
     ......
}

那么拿到 Surface 对象后怎么就能拿到帧缓冲区的操作接口呢?我们来看 Surface 的实现:

Surface.java (frameworks\base\core\java\android\view)

public class Surface implements Parcelable {
    ......
    private static native long nativeLockCanvas(long nativeObject, Canvas canvas, Rect dirty)
            throws OutOfResourcesException;
    private static native void nativeUnlockCanvasAndPost(long nativeObject, Canvas canvas);
    ......
}

这里这个 java 层的 Surface 实现有两个方法,一个是 nativeLockCanvas 一个是 nativeUnlockCanvasAndPost, 它其实对应了我们在章节 5.4 中所述的 dequeueBuffer 和 queueBuffer, 我们继续从代码上看下它的实现过程:

android_view_Surface.cpp (frameworks\base\core\jni)

static jlong nativeLockCanvas(JNIEnv* env, jclass clazz,
        jlong nativeObject, jobject canvasObj, jobject dirtyRectObj) {
    ......
     status_t err = surface->lock(&buffer, dirtyRectPtr);
    ......
}
Surface.cpp (frameworks\native\libs\gui)

status_t Surface::lock(
        ANativeWindow_Buffer* outBuffer, ARect* inOutDirtyBounds){
     ......
     status_t err = dequeueBuffer(&out, &fenceFd);
     ......
}
int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) {
    ATRACE_CALL();
    ......
    status_t result = mGraphicBufferProducer->dequeueBuffer(&buf, &fence, reqWidth, reqHeight,
                                                            reqFormat, reqUsage, &mBufferAge,
                                                            enableFrameTimestamps ? &frameTimestamps
                                                                                  : nullptr);
    ......
}

到这里我们看到 java 层 Surface 的 lock 方法最终它有去调用到 GraphicBufferProducer 的 dequeueBuffer 函数,在第 5 章中我们有详细了解过 dequeueBuffer 在获取一个 Slot 后,如果 Slot 没有分配 GraphicBuffer 会在这时给它分配 GraphicBuffer, 然后会返回一个带有 BUFFER_NEEDS_REALLOCATION 标记的 flag, 应用侧看到这个 flag 后会通过 requestBuffer 和 importBuffer 接口把 GraphicBuffer 映射到自已的进程空间。

到此应用拿到了它绘制界面所需的 “画布”。

由于上面的代码过程看起来比较繁琐,我们用一张图来概述一下这个流程:

首先在这个过程中涉及到三个进程,APP,system_server, surfaceflinger, 先从应用调用 performTraversals 中调用 relayoutWindow 这个函数开始,它跨进程调用到了 system_server 进程中的 WMS 模块,这个模块的 relayoutWindow 又经一系列过程创建一个 SurfaceContorl, 在 SurfaceControl 创建过程中会跨进程调用 SurfaceFlinger 让它创建一个 Layer 出来。之后 SurfaceControll 对象会跨进程通过参数回传给应用,应用根据 SurfaceControl 创建出应用侧的 Surface 对象,而 Surface 对象通过一些 api 封装向上层提供拿画布(dequeueBuffer)和提交画布(queueBuffer)的操作接口。这样应用完成了对画布的申请操作。

7.2. 帧数据的绘制过程

上一节我们知道了应用是如何拿到 “画布” 的,接下来我们来看下应用是如何在绘画完一帧后来提交数据的:

上节中应用的主线程在 performTraversals 函数中获取到了操作帧缓冲区的 Surface 对象,这个 Surface 对象会通过 RenderProxy 传递给 RenderThread, 一些关键代码如下:

performTraversals 里初始化 RenderThread 时会把 Surface 对象传过去:

private void performTraversals() {
    ......
    if (mAttachInfo.mThreadedRenderer != null) {
    try {
        hwInitialized = mAttachInfo.mThreadedRenderer.initialize(mSurface);
        ........
    }
    ......
}

在 ThreadedRenderer 的初始化中调用了 setSurface,这个 setSurface 函数会通过 JNI 调到 native 层:

ThreadedRenderer.java (frameworks\base\core\java\android\view)

boolean initialize(Surface surface) throws OutOfResourcesException {
    ......
    setSurface(surface);
    ......
}

android_graphics_HardwareRenderer.cpp (frameworks\base\libs\hwui\jni)

static void android_view_ThreadedRenderer_setSurface(JNIEnv* env, jobject clazz,
        jlong proxyPtr, jobject jsurface, jboolean discardBuffer) {
    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
    ......
    proxy->setSurface(window, enableTimeout);
    ......
}

最终可以看到 setSurface 是通过 RenderProxy 这个对象向 RenderThread 的消息队列中 post 了一个消息,在这个消息的处理中会调用 Context 的 setSurface, 这里的 mContext 是 CanvasContext.

RenderProxy.cpp (frameworks\base\libs\hwui\renderthread)

void RenderProxy::setSurface(ANativeWindow* window, bool enableTimeout) {
    ANativeWindow_acquire(window);
    mRenderThread.queue().post([this, win = window, enableTimeout]() mutable {
        mContext->setSurface(win, enableTimeout);
        ANativeWindow_release(win);
    });
}

CanvasContext 的 setSurface 会进一步调用到 Surface 对象的 connect 方法(5.4 节)和 SurfaceFlinger 侧协商同步一些参数。

上述过程如下图所示

这其中一些关键过程可以在 systrace 中看到,如下图所示:

接下来应用主线程收到 vsync 信号后会开始绘图流程,应用主线程会遍历 ViewTree 对所有的 View 完成 measure, layout, draw 的工作,我们知道 Android 的应用界面是由很多 View 按树状结构组织起来的,如下图以微信主界面为例,但无论界面多么复杂它都有一个根 View 叫 DecorView, 而纷繁复杂的界面最终都是由一个个 View 通过树形结构组织起来的。

限于篇幅我们这里不讨论 UI 线程的 measure 和 layout 部分,这里只来看下 draw 部分:

首先 App 每次开始绘画都是收到一个 vsync 信号才会开始绘图(这里暂不讨论 SurfaceView 自主上帧的情况),应用是通过 Choreographer 来感知 vsync 信号,在 ViewRootImpl 里向 Choreographer 注册一个 callback, 每当有 vsync 信号来时会执行 mTraversalRunnable:

ViewRootImpl.java (frameworks\base\core\java\android\view)

 void scheduleTraversals() {
     if (!mTraversalScheduled) {
         ......
         mChoreographer.postCallback(
         Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);//注册vsync的回调
         ......
     }
 }
 final class TraversalRunnable implements Runnable {
     @Override
     public void run() {
        doTraversal();//每次vsync到时调用该函数
     }
 }

而 doTraversal () 主要是调用 performTraversals () 这个函数,performTraversals 里会调用到 draw () 函数:

private boolean draw(boolean fullRedrawNeeded) {
     ......
      if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
          ......
          mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);//这里传下去的mView就是DecorView
          ......
      }
     ......
 }

上面的 draw () 函数进一步调用了 ThreadedRenderer 的 draw:

ThreadedRenderer.java (frameworks\base\core\java\android\view)

void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
    ......
    updateRootDisplayList(view, callbacks);
    ......
}
private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Record View#draw()");//这里有个trace,我们可以在systrace中观察到它
    ......
    updateViewTreeDisplayList(view);
    ......
}
private void updateViewTreeDisplayList(View view) {
    ......
    view.updateDisplayListIfDirty();//这里开始调用DecorView的updateDisplayListIfDirty
    ......
}

接下来代码调用到 DecorView 的基类 View.java:

View.java (frameworks\base\core\java\android\view)

public RenderNode updateDisplayListIfDirty() {
    ......
     final RecordingCanvas canvas = renderNode.beginRecording(width, height);//这里开始displaylist的record
     ......
     draw(canvas);
     ......

}

上面的 RecordingCanvas 就是扮演一个绘图指令的记录员角色,它会将这个 View 通过 draw 函数绘制的指令以 displaylist 形式记录下来,那么上面的 renderNode 又个什么东西呢?

熟悉 Web 的同学一定会对 DOM Tree 和 Render Tree 不陌生,Android 里的 View 和 RenderNode 是类似的概念,View 代表的是实体在空间结构上的存在,而 RenderNode 代表它在界面呈现上的存在。这样的设计可以让存在和呈现进行分离,便于实现同一存在不同状态下呈现也不同。

在 Android 的设计里 View 会对应一个 RenderNode, RenderNode 里的一个重要数据结构是 DisplayList, 每个 DisplayList 都会包含一系列 DisplayListData. 这些 DisplayList 也会同样以树形结构组织在一起。

当 UI 线程完成它的绘制工作后,它工作的产物是一堆 DisplayListData, 我们可以将其理解为是一堆绘图指令的集合,每一个 DisplayListData 都是在描绘这个 View 长什么样子,所以一个 View 树也可能理解为它的样子由对应的 DisplayListData 构成的树来描述:

我们再来看下 DisplayListData 是长什么样子,它定义在下面这个文件中:

RecordingCanvas.h (frameworks\base\libs\hwui)

class DisplayListData final {
    ......
    void drawPath(const SkPath&, const SkPaint&);
    void drawRect(const SkRect&, const SkPaint&);
    void drawRegion(const SkRegion&, const SkPaint&);
    void drawOval(const SkRect&, const SkPaint&);
    void drawArc(const SkRect&, SkScalar, SkScalar, bool, const SkPaint&);
    ......
    template <typename T, typename... Args>
    void* push(size_t, Args&&...);
    ......
    SkAutoTMalloc<uint8_t> fBytes;
    ......
}

它的组成大体可以看成三个部分,第一部分是一堆以 draw 打头的函数,它们是最基本的绘图指令,比如画一条线, 画一个矩形,画一段圆弧等等,上面我们摘取了其中几个,后面我们将以 drawRect 为例来看它是如何工作的;第二部分是一个 push 模版函数,后面我们会看到它的作用;第三个是一块存储区 fBytes,它会根据需要放大存储区的大小。

我们来看下 drawRect 的实现:

RecordingCanvas.cpp (frameworks\base\libs\hwui)

void DisplayListData::drawRect(const SkRect& rect, const SkPaint& paint) {
    this->push<DrawRect>(0, rect, paint);
}

我们发现它只是 push 了画 一个 Rect 相关的参数,那么这个 DrawRect 又是什么呢?

struct Op {
    uint32_t type : 8;
    uint32_t skip : 24;
};
struct DrawRect final : Op {
    static const auto kType = Type::DrawRect;
    DrawRect(const SkRect& rect, const SkPaint& paint) : rect(rect), paint(paint) {}
    SkRect rect;
    SkPaint paint;
    void draw(SkCanvas* c, const SkMatrix&) const { c->drawRect(rect, paint); }
};

通过上面代码我们不难发现,DrawRect 代表的是一段内存布局,这段内存第一个字节存储了它是哪种类型,后面的部分存储有画这个 Rect 所需要的参数信息,再来看 push 方法的实现:

template <typename T, typename... Args>
void* DisplayListData::push(size_t pod, Args&&... args) {
    ......
    auto op = (T*)(fBytes.get() + fUsed);
    fUsed += skip;
    new (op) T{std::forward<Args>(args)...};
    op->type = (uint32_t)T::kType;//注意这里将DrawRect的类型编码Type::DrawRect存进了第一个字节
    op->skip = skip;
    return op + 1;
}

这里 push 方法就是在 fBytes 后面放入这个 DrawRect 的内存布局,也就是执行 DisplayListData::drawRect 方法时就是把画这个 Rect 的方法和参数存入了 fBytes 这块内存中, 那么最后 fBytes 这段内存空间就放置了一条条的绘制指令。

通过上面的了解,我们知道了 UI 线程并没有将应用设计的 View 转换成像素点数据,而是将每个 View 的绘图指令存入了内存中,我们通常称这些绘图指令为 DisplayList, 下面让我们跳出这些细节,再次回到宏观一些的角度。

当所有的 View 的 displaylist 建立完成后,代码会来到:

RenderProxy.cpp (frameworks\base\libs\hwui\renderthread)

int RenderProxy::syncAndDrawFrame() {
    return mDrawFrameTask.drawFrame();
}
DrawFrameTask.cpp (frameworks\base\libs\hwui\renderthread)

void DrawFrameTask::postAndWait() {
    AutoMutex _lock(mLock);
    mRenderThread->queue().post([this]() { run(); });//丢任务到RenderThread线程
    mSignal.wait(mLock);
}

这边可以看到 UI 线程的工作到此结束,它丢了一个叫 DrawFrameTask 的任务到 RenderThread 线程中去,之后画面绘制的工作转移到 RenderThread 中来:

DrawFrameTask.cpp (frameworks\base\libs\hwui\renderthread)

void DrawFrameTask::run() {
    .....
    context->draw();
    .....
}
CanvasContext.cpp (frameworks\base\libs\hwui\renderthread)

void CanvasContext::draw() {
    ......
    Frame frame = mRenderPipeline->getFrame();//这句会调用到Surface的dequeueBuffer
    ......
     bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue,
                                      mContentDrawBounds, mOpaque, mLightInfo, mRenderNodes,
                                      &(profiler()));
    ......
    waitOnFences();
    ......
    bool didSwap =
            mRenderPipeline->swapBuffers(frame, drew, windowDirty, mCurrentFrameInfo, &requireSwap);//这句会调用到Surface的queueBuffer
    ......
}

在这个函数中完成了三个重要的动作,一个是通过 getFrame 调到了 Surface 的 dequeueBuffer 向 SurfaceFlinger 申请了画布, 第二是通过 mRenderPipeline->draw 将画面画到申请到的画布上, 第三是通过调 mRenderPipeline->swapBuffers 把画布提交到 SurfaceFlinger 去显示。

那么在 mRenderPipeline->draw 里是如何将 displaylist 翻译成画布上的像素点颜色的呢?

SkiaOpenGLPipeline.cpp (frameworks\base\libs\hwui\pipeline\skia)

bool SkiaOpenGLPipeline::draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
                              const LightGeometry& lightGeometry,
                              LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds,
                              bool opaque, const LightInfo& lightInfo,
                              const std::vector<sp<RenderNode>>& renderNodes,
                              FrameInfoVisualizer* profiler) {
      ......
      renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface, SkMatrix::I());
      ......
 }
SkiaPipeline.cpp (frameworks\base\libs\hwui\pipeline\skia)

void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
                               const std::vector<sp<RenderNode>>& nodes, bool opaque,
                               const Rect& contentDrawBounds, sk_sp<SkSurface> surface,
                               const SkMatrix& preTransform) {
    ......
    SkCanvas* canvas = tryCapture(surface.get(), nodes[0].get(), layers);
    ......
    renderFrameImpl(clip, nodes, opaque, contentDrawBounds, canvas, preTransform);
    endCapture(surface.get());
    ......
    ATRACE_NAME("flush commands");
    surface->getCanvas()->flush();
    ......
}

在上面的 renderFrameImpl 中会把在 UI 线程中记录的 displaylist 重新 “绘制” 到 skSurface 中,然后通过 SkCanvas 将其转化为 gl 指令, surface->getCanvas ()->flush (); 这句是将指令发送给 GPU 执行,这其中是如何 “翻译” 的细节笔者暂时尚未研究,这里先不做讨论。

总结一下应用通过 android 的 View 系统画出第一帧的总的流程,如下图所示:

首先是 UI 线程进行 measure, layout 然后开始 draw, 在 draw 的过程中会建立 displaylist 树,将每个 view 应该怎么画记录下来,然后通过 RenderProxy 把后续任务下达给 RenderThread, RenderThread 主要完成三个动作,先通过 Surface 接口向 Surfaceflinger 申请 buffer, 然后通过 SkiaOpenGLPipline 的 draw 方法把 displaylist 翻译成 GPU 指令, 指挥 GPU 把指令变成像素点数据, 最后通过 swapBuffer 把数据提交给 SurfaceFlinger, 完成一帧数据的绘制和提交。

这个过程我们可以在 systrace 上观察到,如下图所示:

7.3. 帧数据的提交过程

那么应用提交 buffer 以后 SurfaceFlinger 会如何处理呢?又是如何提交到 HWC Service 去合成的呢?

首先响应应用 queueBuffer 的是一条 binder 线程, 处理逻辑会走进:

BufferQueueProducer.cpp (frameworks\native\libs\gui)

status_t BufferQueueProducer::queueBuffer(int slot,
        const QueueBufferInput &input, QueueBufferOutput *output) {
    ATRACE_CALL();
    ATRACE_BUFFER_INDEX(slot);
    ......
     if (frameAvailableListener != nullptr) {
        frameAvailableListener->onFrameAvailable(item);
     }
    ......
}

上面的 frameAvailableListener 是 BufferQueueLayer:

BufferQueueLayer.cpp (frameworks\native\services\surfaceflinger)

void BufferQueueLayer::onFrameAvailable(const BufferItem& item) {
    ......
     mFlinger->signalLayerUpdate();//这里申请一下个vsync-sf信号
    ......
}

由上面代码可知,只要有 layer 上帧,那么就会申请下一次的 vsync-sf 信号, 当 vsync-sf 信号来时会调用 onMessageReceived 函数来处理帧数据:

SurfaceFlinger.cpp (frameworks\native\services\surfaceflinger)

void SurfaceFlinger::onMessageInvalidate(nsecs_t expectedVSyncTime) {
    ATRACE_CALL();
    ......
        refreshNeeded |= handleMessageInvalidate();
    ......
    signalRefresh();//再次向消息队列发送一个消息,消息到达时会调用onMessageRefresh
    ......
}
bool SurfaceFlinger::handleMessageInvalidate() {
    ATRACE_CALL();
    bool refreshNeeded = handlePageFlip();
    ......
}

在 handleMessageInvalidate 里一个比较重要的函数是 handlePageFlip ():

bool SurfaceFlinger::handlePageFlip()
{
    ATRACE_CALL();
    ......
    mDrawingState.traverse([&](Layer* layer) {
        if (layer->hasReadyFrame()) {
            frameQueued = true;
            if (layer->shouldPresentNow(expectedPresentTime)) {
                mLayersWithQueuedFrames.push_back(layer);
            }
            .......
        }
        ......
    });
    ......
        for (auto& layer : mLayersWithQueuedFrames) {
            if (layer->latchBuffer(visibleRegions, latchTime, expectedPresentTime)) {
                mLayersPendingRefresh.push_back(layer);
            }
            .......
        }
        ......
}

这里可以看出来,handlePageFlip 里一个重要的工作是检查所有的 Layer 是否有新 buffer 提交,如果有则调用其 latchBuffer 来处理:

BufferLayer.cpp (frameworks\native\services\surfaceflinger)

bool BufferLayer::latchBuffer(bool& recomputeVisibleRegions, nsecs_t latchTime,
                              nsecs_t expectedPresentTime) {
    ATRACE_CALL();
    ......
    status_t err = updateTexImage(recomputeVisibleRegions, latchTime, expectedPresentTime);
    ......
}
BufferQueueLayer.cpp (frameworks\native\services\surfaceflinger)

status_t BufferQueueLayer::updateTexImage(bool& recomputeVisibleRegions, nsecs_t latchTime,
                                          nsecs_t expectedPresentTime) {
     ......
      status_t updateResult = mConsumer->updateTexImage(&r, expectedPresentTime, &mAutoRefresh,
                                                      &queuedBuffer, maxFrameNumberToAcquire);
     ......
}
BufferLayerConsumer.cpp (frameworks\native\services\surfaceflinger)

status_t BufferLayerConsumer::updateTexImage(BufferRejecter* rejecter, nsecs_t expectedPresentTime,
                                             bool* autoRefresh, bool* queuedBuffer,
                                             uint64_t maxFrameNumber) {
    ATRACE_CALL();
    ......
    status_t err = acquireBufferLocked(&item, expectedPresentTime, maxFrameNumber);
    ......
}

这里调用到了 BufferLayerConsumer 的基类 ConsumerBase 里:

status_t ConsumerBase::acquireBufferLocked(BufferItem *item,
        nsecs_t presentWhen, uint64_t maxFrameNumber) {
    ......
    status_t err = mConsumer->acquireBuffer(item, presentWhen, maxFrameNumber);
    ......
}

到这里 onMessageReceived 中的主要工作结束,在这个函数的处理中,SurfaceFlinger 主要是检查每个 Layer 是否有新提交的 buffer, 如果有则调用 latchBuffer 将每个 BufferQueue 中的 Slot 通过 acquireBuffer 拿走。之后拿走的 buffer (Slot 对应的状态是 ACQUIRED 状态) 会被交由 HWC Service 处理,这部分是在 onMessageRefresh 里处理的:

void SurfaceFlinger::onMessageRefresh() {
    ATRACE_CALL();
    ......
    mCompositionEngine->present(refreshArgs);
    ......
}

CompositionEngine.cpp (frameworks\native\services\surfaceflinger\compositionengine\src)

void CompositionEngine::present(CompositionRefreshArgs& args) {
    ATRACE_CALL();
    ......
    for (const auto& output : args.outputs) {
        output->present(args);
    }
    ......
}

Output.cpp (frameworks\native\services\surfaceflinger\compositionengine\src)

void Output::present(const compositionengine::CompositionRefreshArgs& refreshArgs) {
    ATRACE_CALL();
    ......
    updateAndWriteCompositionState(refreshArgs);//告知HWC service有哪些layer要参与合成
    ......
    beginFrame();
    prepareFrame();
    ......
    finishFrame(refreshArgs);
    postFramebuffer();//这里会调用到HWC service的接口去present display合成画面
}
void Output::postFramebuffer() {
    ......
    auto frame = presentAndGetFrameFences();
    ......
}

HWComposer.cpp (frameworks\native\services\surfaceflinger\displayhardware)

status_t HWComposer::presentAndGetReleaseFences(DisplayId displayId) {
    ATRACE_CALL();
    ......
    auto error = hwcDisplay->present(&displayData.lastPresentFence);//送去HWC service合成
    ......
    std::unordered_map<HWC2::Layer*, sp<Fence>> releaseFences;
    error = hwcDisplay->getReleaseFences(&releaseFences);
    RETURN_IF_HWC_ERROR_FOR("getReleaseFences", error, displayId, UNKNOWN_ERROR);

    displayData.releaseFences = std::move(releaseFences);//获取releaseFence, 以便通知到各个Slot, buffer被release后会通过dequeueBuffer给到应用,应用在绘图前会等待releaseFence
    ......
}

Hwc2::Composer 是 HWC service 提供给 Surfaceflinger 侧的一些接口,挑些有代表的如下

class Composer final : public Hwc2::Composer {
    ......
    Error createLayer(Display display, Layer* outLayer) override;//通知HWC要新加一个layer
    Error destroyLayer(Display display, Layer layer) override;//通知hwc 有个layer被destory掉了
    ......
    Error presentDisplay(Display display, int* outPresentFence) override;//合成画面
    ......
    Error setActiveConfig(Display display, Config config) override;//设置一些参数,如屏幕刷新率
    ......
    CommandWriter mWriter;
    CommandReader mReader;//mWriter用于SurfaceFlinger向HWC service发送指令, mReader用于从HWC service侧获取信息
}
Error Composer::presentDisplay(Display display, int* outPresentFence)
{
    ......
    mWriter.presentDisplay();
    ......
}

到这里 SurfaceFlinger 侧的处理主流程走完了,我们先来总结一下当应用的 buffer 提交到 SurfaceFlinger 后 SurfaceFlinger 所经历的主要流程, 如下图所示,首先 binder 线程会通过 BufferQueue 机制把应用上帧的 Slot 状态改为 QUEUED, 然后把这个 Slot 放入 mQueue 队列(请回忆 5.3 节知识), 然后通过 onFrameAvailable 回调通知到 BufferQueueLayer, 在处理函数里会请求下一次的 vsync-sf 信号,在 vsync-sf 信号到来后,SurfaceFlinger 主线程要执行两次 onMessageReceived, 第一次要检查所有的 layer 看是否有上帧, 如果有 Layer 上帧就调用它的 latchBuffer 把它的 buffer acquireBuffer 走。并发送一个消息到主消息队列,让 UI 线程再次走进 onMessageReceived, 第二次走进来时,主要执行 present 方法,在这些方法里会和 HWC service 沟通,调用它的跨进程接口通知它去做图层的合成。

之后就进入 HWC Service 的处理流程,这部分的处理流程和芯片厂商 HAL 层实现紧密相关,限于某些因素不便于介绍。本章应用更新画面的代码流程就介绍到这里。

7.4. 本章小结

本章我们沿着代码逻辑学习了应用是如何申请到画布、使用 android 的 View 系统如何绘图、绘图完成后如何提交 buffer 以及 buffer 提交以及 Surfaceflinger 如何处理。但本章所述的逻辑均是指通过 android 的 View 系统绘图的过程,也可以称其为 hwui 绘图流程,从上面代码流程可以知道,hwui 的绘图流程是被 vsync 信号触发的,开始于 vsync 信号到达 UI 线程调用 performTraversals 函数, hwui 的画面更新是被 vsync 信号驱动的。

在 android 系统中也有提供不依赖 vsync 信号的自主上帧接口,比如 app 可以使用 SurfaceView 这个特殊的 View 来申请一个独立于 Activity 之外的画布。接下来我们通过一些 helloworld 示例来看下这些接口如何使用。

参考资料

[1]Android 画面显示流程分析 (1): https://www.jianshu.com/p/df46e4b39428

[2]Android 画面显示流程分析 (2): https://www.jianshu.com/p/f96ab6646ae3

[3]Android 画面显示流程分析 (3): https://www.jianshu.com/p/3c61375cc15b

[4]Android 画面显示流程分析 (4): https://www.jianshu.com/p/7a18666a43ce

[5]Android 画面显示流程分析 (5): https://www.jianshu.com/p/dcaf1eeddeb1

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/wUG1KBoPrtNFVRWfu1etnQ

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:6月以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:6月以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:6月以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:6月以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:6月以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:6月以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:7月以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:6月以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:6月以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:6月以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:6月以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:6月以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:6月以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:6月以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:6月以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:7月以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:7月以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:7月以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:6月以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:6月以前  |  398次阅读  |  详细内容 »
 相关文章
简化Android的UI开发 4年以前  |  520651次阅读
Android 深色模式适配原理分析 3年以前  |  28546次阅读
Android阴影实现的几种方案 1年以前  |  10655次阅读
Android 样式系统 | 主题背景覆盖 3年以前  |  9521次阅读
 目录