Android P 图像显示系统(三)Android HWUI 绘制流程

本文详细探讨了Android P中的硬件加速机制,解释了如何通过HWUI将2D绘图转化为3D绘图并利用GPU进行渲染。文章分析了从Java层到native层的绘制流程,包括Draw操纵的录制、RenderNode的创建、HwuiContext与HwuiRenderer的角色,以及RenderThread的工作原理。此外,还介绍了CanvasContext、DisplayListCanvas和DisplayList在其中的作用,以及如何通过OpenGL进行最终的绘制操作。文章通过一个硬件绘制实例展示了这一过程,帮助读者深入理解Android的硬件加速机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


Android中,绘图的API很多,比如2D的绘图skia;3D的绘图OpenGLES,Vulkan等。Android 开始,的View系统中,多数都是采用2D的模式的View Widget,比如绘制一张Bitmap图片,显示一个按钮等。随着Android系统的更新,和用户对视觉效果的追求,以前的这套2D View系统,不仅不能满足要求,而且渲染非常的慢。所以Android一方面完善对3D的API的支持,另一方面修改原来View Widget的渲染机制。

渲染机制的更新,Android提出了硬件加速的机制,其作用就是将2D的绘图操纵,转换为对应的3D的绘图操纵,这个转换的过程,我们把它叫做录制。需要显示的时候,再用OpenGLES通过GPU去渲染。界面创建时,第一次全部录制,后续的过程中,界面如果只有部分区域的widget更新,只需要重新录制更新的widget。录制好的绘图操纵,保存在一个显示列表DisplayList中,需要真正显示到界面的时候,直接显示DisplayList中的绘图 操纵。这样,一方面利用GPU去渲染,比Skia要快;另一方面,采用DisplayList,值重新录制,有更新区域,最大程度利用上一帧的数据,效率自然就快很多。这就是硬件加速的来源。
roundRectClipState
语言苍白,实践为先,我们结合测试示例,来看看硬件加速是怎么回事~

应用使用硬件(GPU)绘制实例

这个是Android原生的测试硬件绘制的应用:

* frameworks/base/tests/HwAccelerationTest/src/com/android/test/hwui/HardwareCanvasSurfaceViewActivity.java

    private static class RenderingThread extends Thread {
        private final SurfaceHolder mSurface;
        private volatile boolean mRunning = true;
        private int mWidth, mHeight;

        public RenderingThread(SurfaceHolder surface) {
            mSurface = surface;
        }

        void setSize(int width, int height) {
            mWidth = width;
            mHeight = height;
        }

        @Override
        public void run() {
            float x = 0.0f;
            float y = 0.0f;
            float speedX = 5.0f;
            float speedY = 3.0f;

            Paint paint = new Paint();
            paint.setColor(0xff00ff00);

            while (mRunning && !Thread.interrupted()) {
                final Canvas canvas = mSurface.lockHardwareCanvas();
                try {
                    canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
                    canvas.drawRect(x, y, x + 20.0f, y + 20.0f, paint);
                } finally {
                    mSurface.unlockCanvasAndPost(canvas);
                }

                ... ...

                try {
                    Thread.sleep(15);
                } catch (InterruptedException e) {
                    // Interrupted
                }
            }
        }

        void stopRendering() {
            interrupt();
            mRunning = false;
        }
    }

应用这里拿到一个Surface,然后lock一个HardwareCanvas,用lock的HardwareCanvas进行绘制,我们绘制的就可以使用硬件GPU进行绘制。这里每隔15秒循环一次,绘制一个小方块,在屏幕上不停的运动。而背景,被绘制成0x00000000,黑色。

硬件绘制Java层相关流程

通过前面的代码,关键的是在lockHardwareCanvas。

lockHardwareCanvas的代码如下:

* frameworks/base/core/java/android/view/SurfaceView.java

        @Override
        public Canvas lockHardwareCanvas() {
            return internalLockCanvas(null, true);
        }

        private Canvas internalLockCanvas(Rect dirty, boolean hardware) {
            mSurfaceLock.lock();

            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Locking canvas... stopped="
                    + mDrawingStopped + ", surfaceControl=" + mSurfaceControl);

            Canvas c = null;
            if (!mDrawingStopped && mSurfaceControl != null) {
                try {
                    if (hardware) {
                        c = mSurface.lockHardwareCanvas();
                    } else {
                        c = mSurface.lockCanvas(dirty);
                    }
                } catch (Exception e) {
                    Log.e(LOG_TAG, "Exception locking surface", e);
                }
            }

            if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Returned canvas: " + c);
            if (c != null) {
                mLastLockTime = SystemClock.uptimeMillis();
                return c;
            }

            ... ...

            return null;
        }

这里Canvas是通过mSurface来申请的。

* frameworks/base/core/java/android/view/Surface.java

    public Canvas lockHardwareCanvas() {
        synchronized (mLock) {
            checkNotReleasedLocked();
            if (mHwuiContext == null) {
                mHwuiContext = new HwuiContext();
            }
            return mHwuiContext.lockCanvas(
                    nativeGetWidth(mNativeObject),
                    nativeGetHeight(mNativeObject));
        }
    }

Surface中封装了一个 HwuiContext ,其构造函数如下:

        HwuiContext() {
            mRenderNode = RenderNode.create("HwuiCanvas", null);
            mRenderNode.setClipToBounds(false);
            mHwuiRenderer = nHwuiCreate(mRenderNode.mNativeRenderNode, mNativeObject);
        }

在HwuiContext的构造函数中,创建了一个RenderNode,创建了一个HwuiRenderer。nHwuiCreate创建一个native的HwuiRender。

这里的HwuiContext,就是和HWUI打交道了。

HwuiContext的lockCanvas实现如下:

        Canvas lockCanvas(int width, int height) {
            if (mCanvas != null) {
                throw new IllegalStateException("Surface was already locked!");
            }
            mCanvas = mRenderNode.start(width, height);
            return mCanvas;
        }

RenderNode的start函数:

    public DisplayListCanvas start(int width, int height) {
        return DisplayListCanvas.obtain(this, width, height);
    }

    static DisplayListCanvas obtain(@NonNull RenderNode node, int width, int height) {
        if (node == null) throw new IllegalArgumentException("node cannot be null");
        DisplayListCanvas canvas = sPool.acquire();
        if (canvas == null) {
            canvas = new DisplayListCanvas(node, width, height);
        } else {
            nResetDisplayListCanvas(canvas.mNativeCanvasWrapper, node.mNativeRenderNode,
                    width, height);
        }
        canvas.mNode = node;
        canvas.mWidth = width;
        canvas.mHeight = height;
        return canvas;
    }

RenderNode,start时,将创建一个DisplayListCanvas。DisplayListCanvas是显示列表的Canvas。DisplayListCanvas 构建时,将通过nCreateDisplayListCanvas创建一个native的DisplayListCanvas。

    private DisplayListCanvas(@NonNull RenderNode node, int width, int height) {
        super(nCreateDisplayListCanvas(node.mNativeRenderNode, width, height));
        mDensity = 0; // disable bitmap density scaling
    }

DisplayListCanvas和RecordingCanvas的构造函数都比较简单,但是留意一下Canvas的构造函数:

    public Canvas(long nativeCanvas) {
        if (nativeCanvas == 0) {
            throw new IllegalStateException();
        }
        mNativeCanvasWrapper = nativeCanvas;
        mFinalizer = NoImagePreloadHolder.sRegistry.registerNativeAllocation(
                this, mNativeCanvasWrapper);
        mDensity = Bitmap.getDefaultDensity();
    }

这里的mNativeCanvasWrapper,就是nCreateDisplayListCanvas时,创建的native对应的Canvas。后续,JNI中都是通过mNativeCanvasWrapper去找到对应的nativ的Canvas的。

我们先来看这些相关的类之间的关系~
Hwui Context相关类图

其中,RenderNode,DisplayListCanvas,HwuiRenderer构成了硬件绘制的重要元素。

再回到我们的测试代码,我们这里有两个绘制操纵:

  • drawColor
  • drawRect

drawColor是在DisplayListCanvas的父类RecordingCanvas中实现的:

    public final void drawColor(@ColorInt int color, @NonNull PorterDuff.Mode mode) {
        nDrawColor(mNativeCanvasWrapper, color, mode.nativeInt);
    }

这里调用native的nDrawColor方法。

drawRect也是在DisplayListCanvas的父类RecordingCanvas中实现的:

    @Override
    public final void drawRect(float left, float top, float right, float bottom,
            @NonNull Paint paint) {
        nDrawRect(mNativeCanvasWrapper, left, top, right, bottom, paint.getNativeInstance());
    }

调用native的nDrawRect方法。

native处理流程

###native的Canvas创建

DisplayListCanvas的JNI实现如下:

* frameworks/base/core/jni/android_view_DisplayListCanvas.cpp

const char* const kClassPathName = "android/view/DisplayListCanvas";

static JNINativeMethod gMethods[] = {

    // ------------ @FastNative ------------------

    { "nCallDrawGLFunction", "(JJLjava/lang/Runnable;)V",
            (void*) android_view_DisplayListCanvas_callDrawGLFunction },

    // ------------ @CriticalNative --------------
    { "nCreateDisplayListCanvas", "(JII)J",     (void*) android_view_DisplayListCanvas_createDisplayListCanvas },
    { "nResetDisplayListCanvas",  "(JJII)V",    (void*) android_view_DisplayListCanvas_resetDisplayListCanvas },
    { "nGetMaximumTextureWidth",  "()I",        (void*) android_view_DisplayListCanvas_getMaxTextureWidth },
    { "nGetMaximumTextureHeight", "()I",        (void*) android_view_DisplayListCanvas_getMaxTextureHeight },
    { "nInsertReorderBarrier",    "(JZ)V",      (void*) android_view_DisplayListCanvas_insertReorderBarrier },
    { "nFinishRecording",         "(J)J",       (void*) android_view_DisplayListCanvas_finishRecording },
    { "nDrawRenderNode",          "(JJ)V",      (void*) android_view_DisplayListCanvas_drawRenderNode },
    { "nDrawLayer",               "(JJ)V",      (void*) android_view_DisplayListCanvas_drawLayer },
    { "nDrawCircle",              "(JJJJJ)V",   (void*) android_view_DisplayListCanvas_drawCircleProps },
    { "nDrawRoundRect",           "(JJJJJJJJ)V",(void*) android_view_DisplayListCanvas_drawRoundRectProps },
};

nCreateDisplayListCanvas对应的实现为android_view_DisplayListCanvas_createDisplayListCanvas。

static jlong android_view_DisplayListCanvas_createDisplayListCanvas(jlong renderNodePtr,
        jint width, jint height) {
    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
    return reinterpret_cast<jlong>(Canvas::create_recording_canvas(width, height, renderNode));
}

注意我们这里的renderNodePtr。这个是RenderNode在native层的对象(地址)。

Canvas的create_recording_canvas函数如下:

Canvas* Canvas::create_recording_canvas(int width, int height, uirenderer::RenderNode* renderNode) {
    if (uirenderer::Properties::isSkiaEnabled()) {
        return new uirenderer::skiapipeline::SkiaRecordingCanvas(renderNode, width, height);
    }
    return new uirenderer::RecordingCanvas(width, height);
}

isSkiaEnabled没有被enable的,所以创建的是native的RecordingCanvas。Android 8.0开始,对HWUI进行了重构,增加了RenderPipeline的概念。目前有三种类型的pipeline,分别对应不同的渲染。

enum class RenderPipelineType {
	OpenGL = 0,
	SkiaGL,
	SkiaVulkan,
	NotInitialized = 128
};

默认还是OpenGL类型。

native的RecordingCanvas如下:

* frameworks/base/libs/hwui/RecordingCanvas.cpp

RecordingCanvas::RecordingCanvas(size_t width, size_t height)
        : mState(*this), mResourceCache(ResourceCache::getInstance()) {
    resetRecording(width, height);
}

RecordingCanvas创建时,创建了对应的CanvasState,和ResourceCache。CanvasState是Canvas的状态,管理Snapshot的栈,实现matrix,save/restore,clipping等Renderer的接口。ResourceCache主要是做资源cache,cache为点九类型。

在resetRecording函数中,又做了很多初始化。

void RecordingCanvas::resetRecording(int width, int height, RenderNode* node) {
    LOG_ALWAYS_FATAL_IF(mDisplayList, "prepareDirty called a second time during a recording!");
    mDisplayList = new DisplayList();

    mState.initializeRecordingSaveStack(width, height);

    mDeferredBarrierType = DeferredBarrierType::InOrder;
}
  • 创建了显示列表mDisplayList,这个很重要,稍后我们再介绍。它主要用来保存显示列表的绘制命令。
  • 初始化CanvasState

到此,native的Canvas创建完成。

Draw操纵的录制

测试代码中,一共两个绘制操纵,我们以这两个绘制操纵为例,来说明绘制的操纵的录制。
nDrawColor nDrawRect

* frameworks/base/core/jni/android_graphics_Canvas.cpp

static const JNINativeMethod gDrawMethods[] = {
    {"nDrawColor","(JII)V", (void*) CanvasJNI::drawColor},
    {"nDrawPaint","(JJ)V", (void*) CanvasJNI::drawPaint},
    {"nDrawPoint", "(JFFJ)V", (void*) CanvasJNI::drawPoint},
    {"nDrawPoints", "(J[FIIJ)V", (void*) CanvasJNI::drawPoints},
    {"nDrawLine", "(JFFFFJ)V", (void*) CanvasJNI::drawLine},
    {"nDrawLines", "(J[FIIJ)V", (void*) CanvasJNI::drawLines},
    {"nDrawRect","(JFFFFJ)V", (void*) CanvasJNI::drawRect},

drawColor函数

static void drawColor(JNIEnv* env,
### 使用 ADB 命令强制 HWC 合成 为了确保硬件合成器 (HWC) 被用于缓冲区组合操作,可以利用 `adb shell` 提供的一系列命令来调整系统的图形处理行为。具体来说,通过设置特定属性能够影响系统是否优先采用硬件路径完成图像合成工作[^1]。 #### 设置 SurfaceFlinger 属性 可以通过修改 `debug.hwui.force_gpu_rendering` 和 `debug.sf.enable_hwc` 这两个属性来控制渲染方式: ```bash # 强制关闭 GPU 渲染,让系统更多依赖于 HWC adb shell settings put global debug.hwui.force_gpu_rendering 0 # 开启 HWC 加速 adb shell settings put global debug.sf.enable_hwc 1 ``` 上述命令会使得设备更倾向于使用硬件加速来进行界面绘制与合成过程。 另外一种方法是直接作用于开发者选项中的配置项,这同样能达到类似的效果: ```bash # 打开开发者模式下的硬件叠加层功能 adb shell settings put global window_animation_scale 1 adb shell settings put global transition_animation_scale 1 adb shell settings put global animator_duration_scale 1 ``` 这些参数虽然主要针对动画效果的速度调节,但在某些情况下也会影响到底层是如何选择合适的合成策略。 需要注意的是,不同版本的 Android 可能存在细微差异,因此建议先确认当前使用的操作系统版本再做相应调整。 #### 验证更改生效 执行完以上指令之后,可通过如下命令查看当前状态并验证设置是否成功应用: ```bash # 查看所有已设定的相关属性值 adb shell getprop | grep hwui adb shell getprop | grep sf ``` 如果一切正常,则应该可以看到预期的变化已经反映到了实际运行环境中。 ```python import os def check_adb_command(command): result = os.popen(f'adb shell {command}').read() return result.strip() print(check_adb_command('getprop | grep hwui')) print(check_adb_command('getprop | grep sf')) ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夕月风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值