Android view硬件加速 源码学习笔记 (上), 基于AndroidQ

1,184 阅读5分钟

最近一年多时间里,在某k12在线教育培训机构工作,期间参与了集团超级板书的开发, 其实核心就是 传输获取不同的点,绘制到canvas上. 中间也碰到了一些困惑和优化上的难题,听了组内一些在这方面比较有经验的大佬的分享. 但所了解都非常零碎,不成体系. 所以也暗下决心, 计划花一定时间去整理一下这些,其中首当其冲的就是Android view层硬件加速的全流程,利用周末记录一下流程(本人水平有限,如有错误之处,请不吝指正):

硬件加速绘制和软件绘制的区别

首先,需要先了解一下Android 硬件加速绘制和软件绘制的区别:

硬件加速绘制: 是先使用cpu去主线程去遍历RenderNode树, 通过canvas.drawRenderNode(). 把将要绘制的信息写入c++层的SkdisplayList里的DisplayListData中 , 然后去请求RenderThread去执行将所有RenderNode绘制的流程.

软件绘制: 是直接在surface上lock一个canvas,然后去遍历view树的脏区域,使用canvas绘制内容,整个流程是在MainThread中通过cpu实现的.

硬件加速涉及到的核心类的结构

因为流程较长,涉及到核心类比较多,所以前面先简单罗列一下各个类,混个脸熟:

java层:

RecordingCanvas.java 伪代码

RecordingCanvas extends DisplayListCanvas extends BaseRecordingCanvas extends BaseRecordingCanvas extends Canvas {
    .....
}

RenderNode.java 伪代码

RenderNode {

    RecordingCanvas mCurrentRecordingCanvas;
    ....
}

可以看出: RecordingCanvas 和 RenderNode在java层都没有很多属性,大部分属性都定义在native层

native层:

SkiaRecordingCanvas.cpp

SkiaRecordingCanvas.cpp : SkiaCanvas : Canvas {

    //native层,保存renderNode信息的核心类
    RecordingCanvas mRecorder;
    //renderNode的绘制信息,及renderNode树保存数据结构的指针
    std::unique_ptr<SkiaDisplayList> mDisplayList;
    //记录view层级的屏障
    StartReorderBarrierDrawable* mCurrentBarrier;
   
    ....
}

RecordingCanvas.cpp

RecordingCanvas.cpp : SkCanvasVirtualEnforcer<SkNoDrawCanvas> {
    DisplayListData* fDL;
    .....
}

SkiaDisplayList.cpp, RenderNodeDrawable.cpp

SkiaDisplayList {
    
     DisplayListData mDisplayList
     //是一个双向队列,用来记录,子节点renderNode
     std::deque<RenderNodeDrawable> mChildNodes;
     .....
}
//记录renderNode的子节点
RenderNodeDrawable  {
    //每个节点的指针
    sp<RenderNode> mRenderNode;
    ...
}

ReorderBarrierDrawable.cpp

每一层ViewGroup,开始时中插入的屏障对象
StartReorderBarrierDrawable: SkDrawable {

    int mEndChildIndex;

    int mBeginChildIndex;

    FatVector<RenderNodeDrawable*, 16> mChildren;

    SkiaDisplayList* mDisplayList;
    ...
 }
每一层ViewGroup,结束时中插入的屏障对象
EndReorderBarrierDrawable : SkDrawable {
    ...
}

RenderNode.cpp, DisplayList.h, android_view_ThreadedRenderer.cpp

SkiaDisplayList {
     //是指RenderNode的宽高等信息的存储对象
     RenderProperties mProperties
     //就是SkiaDisplayList
     DisplayList* mDisplayList
    ….
}
DisplayList.h  {

    DisplayList = SkiaDisplayList
    ....
}

/**
RootRenderNode 只能通过ThreadedRender.java 构造方法中的
mRootNode = RenderNode.*adopt*(*nCreateRootRenderNode*());创建出来,
类似View中根节点decorView的概念.但是需要注意 decorView并不是一个RootRenderNode,
而是一个普通的RenderNode
**/
RootRenderNode : RenderNode, ErrorHandler

}

代码执行流程

说完了各个类的概念,我们要开始看一下RenderNode是如何生成DisplayListData中的数据的过程了. 首先, viewRootImpl里面的触发绘制的逻辑,我们就不捋了, 直接开始从硬件绘制流程的起点开始:ThreadedRenderer中的draw()

/**
 * Draws the specified view.
 *
 * @param view The view to draw.
 * @param attachInfo AttachInfo tied to the specified view.
 */
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
    final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;
    //给ChoreGrapher设置一个开始绘制的标记
    choreographer.mFrameInfo.markDrawStart();
    
    //调用了mDecorView的 updateDisplayListIfDirty(),
    //如果view树中存在dirty区域,则创建新的renderNode,并保存,此过程在主线程中执行
    updateRootDisplayList(view, callbacks);

    //处理正在执行动画的RenderNode
    // register animating rendernodes which started animating prior to renderer
    // creation, which is typical for animators started prior to first draw
    if (attachInfo.mPendingAnimatingRenderNodes != null) {
        final int count = attachInfo.mPendingAnimatingRenderNodes.size();
        for (int i = 0; i < count; i++) {
            registerAnimatingRenderNode(
                    attachInfo.mPendingAnimatingRenderNodes.get(i));
        }
        attachInfo.mPendingAnimatingRenderNodes.clear();
        // We don't need this anymore as subsequent calls to
        // ViewRootImpl#attachRenderNodeAnimator will go directly to us.
        attachInfo.mPendingAnimatingRenderNodes = null;
    }
    //请求绘制renderNode,此过程在RenderThread中执行
    int syncResult = syncAndDrawFrame(choreographer.mFrameInfo);
    //如果绘制结果返回SYNC_LOST_SURFACE_REWARD_IF_FOUND标志位,说明可能Surface无效或者出错,则关闭硬件渲染,再次请求使用cpu方式绘制。
    if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
        setEnabled(false);
        attachInfo.mViewRootImpl.mSurface.release();
        // Invalidate since we failed to draw. This should fetch a Surface
        // if it is still needed or do nothing if we are no longer drawing
        attachInfo.mViewRootImpl.invalidate();
    }
  //  如果SYNC_INVALIDATE_REQUIRED返回了,说明还需要再刷新一次
    if ((syncResult & SYNC_REDRAW_REQUESTED) != 0) {
        attachInfo.mViewRootImpl.invalidate();
    }
}

可以看出比较核心的方法有两个:

1,updateRootDisplayList(view, callbacks);

2,syncAndDrawFrame(choreographer.mFrameInfo);

构建RenderNode树

我们来看一下updateRootDisplayList(view, callbacks)方法 `

private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Record View#draw()");
    //从decorView开始请求构建RenderNode结构
    updateViewTreeDisplayList(view);

    // Consume and set the frame callback after we dispatch draw to the view above, but before
    // onPostDraw below which may reset the callback for the next frame.  This ensures that
    // updates to the frame callback during scroll handling will also apply in this frame.
    if (mNextRtFrameCallbacks != null) {
        final ArrayList<FrameDrawingCallback> frameCallbacks = mNextRtFrameCallbacks;
        mNextRtFrameCallbacks = null;
        setFrameCallback(frame -> {
            for (int i = 0; i < frameCallbacks.size(); ++i) {
                frameCallbacks.get(i).onFrameDraw(frame);
            }
        });
    }

    if (mRootNodeNeedsUpdate || !mRootNode.hasDisplayList()) {
        //通过根节点,RootNode来生成一个RecordingCanvas,来进行renderNode中每次绘制的记录
        RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight);
        try {
            final int saveCount = canvas.save();
            canvas.translate(mInsetLeft, mInsetTop);
            callbacks.onPreDraw(canvas);
             
            canvas.enableZ();
            canvas.drawRenderNode(view.updateDisplayListIfDirty());
            canvas.disableZ();

            callbacks.onPostDraw(canvas);
            canvas.restoreToCount(saveCount);
            mRootNodeNeedsUpdate = false;
        } finally {
            mRootNode.endRecording();
        }
    }
    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}

附一个生成ReenderNode数据全流程图

22C453C4-026D-403F-98A0-515B684B1ED9.png

可以看出,每个View都有自己的RenderNode, 当自己被标记为dirty需要绘制时,会根据自己的RenderNode,创建一个RecordingCanvas, 然后通过这个canvas执行onDraw()操作,最后保存在SKiaRecordCanvas的SKiaDisplayList

1, 具体onDraw()里面的每次操作是怎么保存起来对应到native层的呢?

63117772-FF69-47FA-8EDB-015A0BE5C1A4.png 1,RenderNode在beginRecording()时,会从一个长度为25的pool里先尝试取一个RecordingCanvas,去到就reset()一下,取不到就创建,创建流程如上.所以每次onDraw里的操作其实都是保存在了这一级RenderNode对应创建的DisplayListData里.

2,创建java层的RecordingCanvas, 会在native层创建一个SkiaRecordingCanvas. SkiaRecordingCanvas中有一个成员变量 RecordingCanvas.cpp对象,这个对象又继承了SkNoDrawCanvas.

3,SkiaRecordingCanvas在构造方法会调用 initDisplayList(),然后会将RecordingCanvas:SkNoDrawCanvas传递过去.这样硬件加速下每次小的操作比较DrawText()等于是在SkNoDrawCanvas上执行的.流程如下

0A5F445A-0756-4FC0-90CC-D3784083468F.png

4,根据3可知, View层onDraw中自定义的draw操作其实每次都会执行到RecordingCanvas.cpp中,并调用onDrawXXX()系列方法.这些最终都会调用DisPlayListData中的drawXXX(),并调用push方法通过 fBytes保存起来.

2,View树上的RenderNode是怎么建立关系的呢?

1,view层draw方法 在调用完 onDraw()后,会使用DecorView的Canvas执行drawRenderNode操作.执行完成之后,子RenderNode就和DecorView建立起来联系了

D62492D7-AAAF-4C29-B05F-E87CB8E748C9.png

`

void SkiaRecordingCanvas::drawRenderNode(uirenderer::RenderNode* renderNode) {
    // Record the child node. Drawable dtor will be invoked when mChildNodes deque is cleared.
    //将子View的RenderNode放入双向队列,队列需要传递的是一个RenderNodeDrawable,这个是怎么生成相应对象的?
    mDisplayList->mChildNodes.emplace_back(renderNode, asSkCanvas(), true, mCurrentBarrier);
    //拿到刚才加入的RenderNodeDrawable
    auto& renderNodeDrawable = mDisplayList->mChildNodes.back();
    if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
        // Put Vulkan WebViews with non-rectangular clips in a HW layer
        renderNode->mutateStagingProperties().setClipMayBeComplex(mRecorder.isClipMayBeComplex());
    }
    //RenderNodeDrawable是继承自SkDrawable的,所以可以直接使用SkCanvas去添加,最终就保存在了当前SkiaRecordingCanvas中的SkiaDisplayList中
    drawDrawable(&renderNodeDrawable);

    // use staging property, since recording on UI thread
    if (renderNode->stagingProperties().isProjectionReceiver()) {
        mDisplayList->mProjectionReceiver = &renderNodeDrawable;
    }
}

3,RenderNode既然是保存在SkiaDisplayList中的,哪它是怎么保存我们view的层级结构的呢?

1,RecordingCanvas,java 中有一个方法nInsertReorderBarrier(),每次ViewGroup,draw()开始都会调用这个方法insertReorderBarrier(true), 在SkiaRecordingCanvas.finishRecording()中,也会调一下这个方法的结束insertReorderBarrier(false).

void SkiaRecordingCanvas::insertReorderBarrier(bool enableReorder) {
    if (mCurrentBarrier && enableReorder) {
        // Already in a re-order section, nothing to do
        return;
    }

    if (nullptr != mCurrentBarrier) {
        // finish off the existing chunk
         //需要关闭,就会向SkiaDisplayList中加入一个继承自SkDrawable的EndReorderBarrierDrawable
        SkDrawable* drawable =
                mDisplayList->allocateDrawable<EndReorderBarrierDrawable>(mCurrentBarrier);
        mCurrentBarrier = nullptr;
        drawDrawable(drawable);
    }
    //需要开启,就会向SkiaDisplayList中加入一个继承自SkDrawable的StartReorderBarrierDrawable
    if (enableReorder) {
        mCurrentBarrier =
                mDisplayList->allocateDrawable<StartReorderBarrierDrawable>(mDisplayList.get());
        drawDrawable(mCurrentBarrier);
    }
}

绘制流程

1,接下来,终于到了绘制流程了

1, syncAndDrawFrame(choreographer.mFrameInfo)方法执行;首先借用大佬们一张图.

9880421-c784cdb55418392b.webp

,里面又涉及到了一些新的类:

RenderThread 硬件渲染线程,所有的渲染任务都会在该线程中使用硬件渲染线程的Looper进行。 RenderThread里面的threadloop,这位大佬重点分析了RenderThread,基于Android9的, Android10在这块目前没发现有太大的改动. 有兴趣可以去看一下 www.jianshu.com/p/c84bfa909… `

bool RenderThread::threadLoop() {
    setpriority(PRIO_PROCESS, 0, PRIORITY_DISPLAY);
    Looper::setForThread(mLooper);
    if (gOnStartHook) {
        gOnStartHook("RenderThread");
    }
    initThreadLocals();

    while (true) {
        waitForWork();
        processQueue();

        if (mPendingRegistrationFrameCallbacks.size() && !mFrameCallbackTaskPending) {
            drainDisplayEventQueue();
            mFrameCallbacks.insert(mPendingRegistrationFrameCallbacks.begin(),
                                   mPendingRegistrationFrameCallbacks.end());
            mPendingRegistrationFrameCallbacks.clear();
            requestVsync();
        }

        if (!mFrameCallbackTaskPending && !mVsyncRequested && mFrameCallbacks.size()) {
            // TODO: Clean this up. This is working around an issue where a combination
            // of bad timing and slow drawing can result in dropping a stale vsync
            // on the floor (correct!) but fails to schedule to listen for the
            // next vsync (oops), so none of the callbacks are run.
            requestVsync();
        }
    }

    return false;
}

` 2,绘制执行流程

先上一张流程图

8BE70E55-CF1F-4CB8-9754-EA4A290DEA20.png

看一下DrawFrameTask的run()方法

void DrawFrameTask::run() {
    ATRACE_NAME("DrawFrame");

    bool canUnblockUiThread;
    bool canDrawThisFrame;
    {
        TreeInfo info(TreeInfo::MODE_FULL, *mContext);
        canUnblockUiThread = syncFrameState(info);
        canDrawThisFrame = info.out.canDrawThisFrame;

        if (mFrameCompleteCallback) {
            mContext->addFrameCompleteListener(std::move(mFrameCompleteCallback));
            mFrameCompleteCallback = nullptr;
        }
    }

    // Grab a copy of everything we need
    CanvasContext* context = mContext;
    std::function<void(int64_t)> callback = std::move(mFrameCallback);

    mFrameCallback = nullptr;

    // From this point on anything in "this" is *UNSAFE TO ACCESS*
    if (canUnblockUiThread) {
        unblockUiThread();
    }

    // Even if we aren't drawing this vsync pulse the next frame number will still be accurate
    if (CC_UNLIKELY(callback)) {
        context->enqueueFrameWork(
                [callback, frameNr = context->getFrameNumber()]() { callback(frameNr); });
    }

    if (CC_LIKELY(canDrawThisFrame)) {
        context->draw();
    } else {
        // wait on fences so tasks don't overlap next frame
        context->waitOnFences();
    }

    if (!canUnblockUiThread) {
        unblockUiThread();
    }
}