Skia是一个开源2D图形库,提供可跨硬件和软件平台工作的通用API,同时是Google、Chrome、Android、Flutter等多个产品的图形引擎。 然而有点幽默的是,虽然安卓内置了Skia,我们想在ndk下却不能使用。 因为Skia不提供稳定API,因此你要么用JNI方法调用java层封装好的Canvas API,要么就只能自己内置一个。
前几天由于新买的显卡是N卡,Linux下的兼容实在太差,于是只得换回Windows。因此这篇文章尝试在Windows下编译Android版本的Skia。
学习参考
安装工具
首先安装我们编译过程中需要的git、python、ninja等工具。现在Windows下也有包管理器了,比如我们可以这样安装git:
winget install Git.Git
然而我实际体验感觉比Linux还是差很多的,例如Git安装后并没有配置好环境变量。
配置环境变量一般倒是难不倒我们程序员,但Python安装首先恶心了我一下。当我在Windows下的PowerShell窗口下打出python,回车后是打开微软应用商店安装Python的页面。这是因为Windows搞了事情,可以在设置项中找到
理论上可以在Windows应用商店直接安装Python,但隐约记得看过一篇文章说Windows应用商店安装的Python联网会有问题,因此这里就谢绝微软的好意了。
使用winget安装Python并设置环境变量后,在命令行还是找不到python3,这是因为Windows下的Python安装后文件夹中只有一个python.exe,即使你安装的是Python3。解决方法也是简单粗暴——直接复制一份。
拉取源码
唯一注意的是设置网络环境,现在Windows 11默认使用的是powershell,设置代理方式是不一样的。
CMD:
set http_proxy=http://127.0.0.1:7890
set https_proxy=http://127.0.0.1:7890
PowerShell:
$env:HTTP_PROXY="http://127.0.0.1:7890"
$env:HTTPS_PROXY="http://127.0.0.1:7890"
接下来拉取并配置环境。
git clone https://skia.googlesource.com/skia.git
cd skia
python3 tools/git-sync-deps
然后使用默认参数编译
bin/gn gen out/Debug
ninja -C out/Debug
编译通过。
传递更多参数
上面编译成功了Windows版本,但当我尝试编译安卓版本遇到了困难。
PS C:\Users\yqmai\Project\skia> bin/gn gen out/arm64 --args='ndk="/tmp/ndk" target_cpu="arm64"'
ERROR at the command-line "--args":1:5: Invalid token.
ndk=/tmp/ndk target_cpu=arm64
^
I have no idea what this is.
这里信息只能隐约看出是参数传递有问题,尝试数次无果。参考这篇文章,使用python脚本可以方便地传递参数,不用担心引号、斜线等的转义问题。
import os
from os.path import join
from subprocess import check_call
SKIA_SOURCE_PATH = "C:\\Users\\yqmai\\Project\\skia"
args = [
'ndk="C:\\Users\\yqmai\\AppData\\Local\\Android\\Sdk\\ndk\\27.0.11902837"',
'target_cpu="arm64"',
'ndk_api=27',
'is_component_build=true'
]
check_call(
[join(SKIA_SOURCE_PATH, 'bin\\gn'), 'gen', 'out/Android', '--args=' + ' '.join(args)],
shell=True,
cwd=SKIA_SOURCE_PATH,
)
check_call(
['ninja', '-C', join(SKIA_SOURCE_PATH, 'out/Android')],
shell=True,
)
如果你尝试编译一个Release包,你会发现缺好多头文件,因为这些库在系统中没找到。看上去很难解决,所以如果有需要的话还是在Linux/macOS下折腾吧 :{
Yeah, it’s fairly uncommon to have any of that on Windows.
安卓demo
将skia的include拷贝到cpp模块下的skia文件夹下,将libskia.so拷贝到jniLibs/arm64-v8a下,
在CMakeLists.txt添加依赖
# 宿主需要依赖 skia 的头文件
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC ./skia)
# 申请 skia 动态库
add_library( skia
SHARED
IMPORTED )
set_target_properties( skia
PROPERTIES IMPORTED_LOCATION
${PROJECT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libskia.so)
# 依赖 skia
target_link_libraries(${CMAKE_PROJECT_NAME}
# List libraries link to the target library
android
log
skia)
在自定义SurfaceView中把holder传到底层
class SkiaSurfaceView @JvmOverloads constructor(context: Context, defStyleAttr: AttributeSet? = null, defStyleRes: Int = 0) : SurfaceView(context, defStyleAttr, defStyleRes) {
private val surfaceCallback = object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
nativeSurfaceCreated(holder.surface)
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
nativeSurfaceChanged()
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
nativeSurfaceDestroyed()
}
}
init {
holder.addCallback(surfaceCallback)
}
private external fun nativeSurfaceCreated(surface: Surface)
private external fun nativeSurfaceChanged()
private external fun nativeSurfaceDestroyed()
}
在底层绘制三角形
#include <jni.h>
#include <string>
#include "android/native_window.h"
#include "android/native_window_jni.h"
#include "include/core/SkBitmap.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkPath.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkAlphaType.h"
#include "include/core/SkColor.h"
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_skiademo1_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_skiademo1_SkiaSurfaceView_nativeSurfaceCreated(JNIEnv *env, jobject thiz,
jobject surface) {
// 将 Surface 对象转换为 ANativeWindow 对象
auto nativeWindow = ANativeWindow_fromSurface(env, surface);
// 设置 ANativeWindow 宽度、高度和像素格式
ANativeWindow_setBuffersGeometry(
nativeWindow, 400, 400, WINDOW_FORMAT_RGBA_8888);
ANativeWindow_Buffer *buffer = new ANativeWindow_Buffer();
// 锁定 ANativeWindow 的缓冲区,准备开始修改缓冲区的像素数据
ANativeWindow_lock(nativeWindow, buffer, 0);
int bpr = buffer->stride * 4;
// 实际像素对象
SkBitmap bitmap;
// 生成一份位图描述属性
SkImageInfo image_info = SkImageInfo::MakeS32(
buffer->width, buffer->height, SkAlphaType::kPremul_SkAlphaType);
bitmap.setInfo(image_info, bpr);
bitmap.setPixels(buffer->bits);
// 构造一个 canvas 对象,将 canvas 画布和 bitmap 关联上
SkCanvas surfaceCanvas{bitmap};
// 创建一个红色的画刷
SkPaint paint;
paint.setColor(SK_ColorRED);
paint.setStyle(SkPaint::kFill_Style);
// 创建一个绘制路径
SkPath path;
path.moveTo(100.0f, 0.0f);
path.lineTo(0.0f, 100);
path.lineTo(200, 100);
path.close();
// 使用画刷绘制路径
surfaceCanvas.drawPath(path, paint);
// 解锁并提交缓冲
ANativeWindow_unlockAndPost(nativeWindow);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_skiademo1_SkiaSurfaceView_nativeSurfaceChanged(JNIEnv *env, jobject thiz) {
// TODO: implement nativeSurfaceChanged()
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_skiademo1_SkiaSurfaceView_nativeSurfaceDestroyed(JNIEnv *env, jobject thiz) {
// TODO: implement nativeSurfaceDestroyed()
}
恢复环境变量
完成上文后发现一些java程序无法执行,报错 Exception in thread “main” java.lang.UnsupportedClassVersionError: MainKt has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to 52.0
显然这是使用了错误的java版本。经检查发现skia编译过程中会修改系统环境变量中的Path和JAVA_HOME,删除EMSDK开头的几项后问题解决。
最后修改于 2024-07-03