You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android如何通过JNI获取Java Canvas底层SkCanvas指针实现Skia C++绘制

Android JNI层从Java Canvas获取SkCanvas指针实现方案

背景

在Android平台开展基于Java Canvas API的原生绘制实验时,通常会将View OnDraw()回调中获取的Canvas对象通过JNI传入Native层,调用C++侧Skia API完成绘制。核心待解决问题为:Java层Canvas对象传入Native后,如何正确获取对应的底层SkCanvas指针。

已知源码链路与卡点

  • Java层Canvas的私有成员mNativeCanvasWrapper定义在BaseCanvas类中,可通过反射调用被@UnsupportedAppUsage标记的隐藏方法getNativeCanvasWrapper()获取该long类型的Native句柄,对应方法实现:
/** @hide */
@UnsupportedAppUsage
public long getNativeCanvasWrapper() {
    return mNativeCanvasWrapper;
}
  • 上述long型句柄会作为参数传入所有Canvas相关的Native绘制方法,跟踪getWidth()调用链可在JNI实现中看到对应注册逻辑:
{"nGetWidth","(J)I", (void*) CanvasJNI::getWidth},
  • JNI层内置的get_canvas工具方法会直接将传入的long型句柄强转为Canvas*基类指针,对应实现:
static jint getWidth(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle) {
    return static_cast<jint>(get_canvas(canvasHandle)->width());
}

static Canvas* get_canvas(jlong canvasHandle) {
    return reinterpret_cast<Canvas*>(canvasHandle);
}
  • 该Canvas*指针实际指向继承自Canvas基类的SkiaCanvas实例,其内部持有目标SkCanvas指针,但该成员为私有属性,唯一的getter方法为protected权限,无法直接从外部访问。

可落地的获取方案

方案1:继承访问Hack(利用C++访问控制规则)

由于SkiaCanvas获取内部SkCanvas的方法为protected权限,可在Native代码中定义一个继承自SkiaCanvas的辅助访问类,直接调用基类的protected方法拿到指针,示例代码:

// 需提前引入对应版本的SkiaCanvas头文件,注意匹配目标系统的类定义
class SkiaCanvasBridge : public SkiaCanvas {
public:
    static SkCanvas* extract(SkiaCanvas* canvasInst) {
        // 注意不同Android版本方法名有差异:部分版本为asSkCanvas(),部分为getCanvas(),对照头文件调整即可
        return canvasInst->asSkCanvas();
    }
};

使用时直接传入强转后的SkiaCanvas指针即可:

SkiaCanvas* skiaCanvas = reinterpret_cast<SkiaCanvas*>(canvasHandle);
SkCanvas* targetCanvas = SkiaCanvasBridge::extract(skiaCanvas);

注意:该方案需要和目标运行设备的Android版本AOSP头文件匹配,若类方法名、继承关系发生变更需要同步调整。从Android 8.0引入Skia作为HWUI后端至今,所有正式版本的Java层Canvas对应的Native实例均为SkiaCanvas派生类,不存在其他实现,类型强转不会出错。

方案2:内存偏移取值(无依赖头文件场景适用)

C++非多态类的成员内存布局在同编译产物下偏移固定,可对照目标版本的SkiaCanvas.h头文件,计算SkCanvas*成员相对于类首地址的偏移量,直接通过地址运算取值,示例:

SkiaCanvas* skiaCanvas = reinterpret_cast<SkiaCanvas*>(canvasHandle);
// 偏移值必须对照目标版本源码计算,以下32为示例值,不可直接使用
uintptr_t skCanvasMemberAddr = reinterpret_cast<uintptr_t>(skiaCanvas) + 32;
SkCanvas* targetCanvas = *reinterpret_cast<SkCanvas**>(skCanvasMemberAddr);

注意:不同Android版本的SkiaCanvas成员顺序会随功能迭代调整,偏移值必须匹配目标系统源码,否则会触发野指针崩溃。

方案3:公开NDK API(Android 12+ 无Hack方案)

从Android 12(API 31)开始,NDK正式暴露了Canvas与Skia对接的公开接口,可直接调用ACanvas_getSkCanvas方法传入之前获取的native canvas句柄,直接拿到合法的SkCanvas指针,不需要任何Hack操作,适合仅面向高版本系统的场景。

注意事项

  • 所有方式获取的SkCanvas指针仅在当前OnDraw()回调生命周期内有效,onDraw执行结束后Canvas会被系统回收,禁止缓存该指针,否则会触发野指针崩溃。
  • 开启硬件加速时,Java层Canvas可能是RecordingCanvas实例,对应的SkCanvas为录制画布,绘制指令会被序列化到RenderThread执行,操作该SkCanvas时不要随意修改matrix、clip等状态,避免破坏系统原有绘制流程。

内容的提问来源于stack exchange,提问作者Sourav Banerjee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 04:24:13