特别感谢 RealCoolSnow/live2d-android 大佬的开元分享
- flutter 加载的代码我就不放上来了,android能显示,flutter就能加载,让AI写就行,下面我说下整体思路
- 大家记得点赞和给大佬star,不然人开源分享对大家都不利
第一章,要在android上集成live2d
Live2D SDK 集成说明文档
1. 概述
本文档详细说明了在Android Flutter应用中如何集成Live2D SDK,实现从模型加载到显示的完整流程。整个集成涉及Java/Kotlin层、JNI层和C++原生层的协同工作。
2. 架构层次
scss
┌─────────────────────────────────────┐
│ Flutter层 (Dart) │
│ - Live2DView Widget │
└──────────────┬──────────────────────┘
│ PlatformView
┌──────────────▼──────────────────────┐
│ Android Kotlin层 │
│ - Live2DPlatformView │
│ - Live2D_v3 (Java接口) │
└──────────────┬──────────────────────┘
│ JNI调用
┌──────────────▼──────────────────────┐
│ C++ JNI层 │
│ - lapp_model.cpp (JNI绑定) │
└──────────────┬──────────────────────┘
│
┌──────────────▼──────────────────────┐
│ Live2D SDK C++层 │
│ - LAppModel (模型管理) │
│ - CubismRenderer (渲染器) │
│ - CubismModel (模型数据) │
└─────────────────────────────────────┘
3. 核心组件说明
3.1 Live2D_v3.java - Java/Kotlin接口层
文件位置 : android/app/src/main/kotlin/com/hornhuang/tomato_plan/Live2D_v3.java
这是Live2D SDK的Java/Kotlin接口,提供SDK初始化和模型管理功能。
关键代码:
java
public class Live2D_v3 {
private static boolean initialized = false;
private static Context context;
public static void init(Context ctx) {
if (!initialized) {
context = ctx;
Csm_CubismFramework.initialize();
initialized = true;
}
}
public static void dispose() {
if (initialized) {
Csm_CubismFramework.dispose();
initialized = false;
}
}
public static void clearBuffer(float r, float g, float b, float a) {
GLES20.glClearColor(r, g, b, a);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
}
public static class LAppModel {
private final long nativeModel;
public LAppModel() {
nativeModel = createNativeModel();
}
private native long createNativeModel();
private native void destroyNativeModel(long model);
public native void loadModelJson(String fileName);
public native void resize(int ww, int wh);
public native void update();
public native void draw();
public native void startMotion(String group, int no, int priority,
OnBeganMotionCallback onStart, OnFinishedMotionCallback onFinish);
public native void setExpression(String expressionID);
public native void setParameterValue(String id, float value);
public native void setParameterValueByIndex(int index, float value);
public native void addParameterValue(String id, float value);
public native void addParameterValueByIndex(int index, float value);
public native void setPartsOpacity(String id, float opacity);
public native void setPartsOpacityByIndex(int index, float opacity);
public native void hitTest(String hitAreaName, float x, float y);
public native void setDragging(float x, float y);
public native void setAcceleration(float x, float y, float z);
}
}
3.2 lapp_model.cpp - JNI绑定层
文件位置 : android/app/src/main/cpp/lapp_model.cpp
负责将Java/Kotlin方法调用转换为C++函数调用,实现跨语言交互。
关键代码:
cpp
extern "C" JNIEXPORT jlong JNICALL
Java_com_hornhuang_tomato_1plan_Live2D_1v3_00024LAppModel_createNativeModel(JNIEnv *, jobject) {
auto *model = new LAppModel();
return reinterpret_cast<long>(model);
}
extern "C" JNIEXPORT void JNICALL
Java_com_hornhuang_tomato_1plan_Live2D_1v3_00024LAppModel_loadModelJson(JNIEnv *env, jobject thiz,
jstring file_name) {
const char *json_file = env->GetStringUTFChars(file_name, nullptr);
getModel(env, thiz)->LoadAssets(json_file);
env->ReleaseStringUTFChars(file_name, json_file);
}
extern "C" JNIEXPORT void JNICALL
Java_com_hornhuang_tomato_1plan_Live2D_1v3_00024LAppModel_resize(JNIEnv *env, jobject thiz,
jint ww, jint wh) {
getModel(env, thiz)->Resize(ww, wh);
}
extern "C" JNIEXPORT void JNICALL
Java_com_hornhuang_tomato_1plan_Live2D_1v3_00024LAppModel_update(JNIEnv *env, jobject thiz) {
getModel(env, thiz)->Update();
}
extern "C" JNIEXPORT void JNICALL
Java_com_hornhuang_tomato_1plan_Live2D_1v3_00024LAppModel_draw(JNIEnv *env, jobject thiz) {
getModel(env, thiz)->Draw();
}
3.3 LAppModel.cpp - 模型管理核心
文件位置 : android/app/src/main/cpp/Main/src/LAppModel.cpp
实现Live2D模型的加载、更新、渲染和交互处理。
关键代码:
cpp
void LAppModel::LoadAssets(const Csm::csmChar* fileName) {
// 解析模型文件路径
std::filesystem::path p = std::filesystem::u8path(fileName);
_modelHomeDir = p.parent_path().u8string().c_str();
// 读取并解析.model3.json文件
Csm::csmSizeInt size;
Csm::csmByte* buffer = CreateBuffer(fileName, &size);
_modelSetting = Csm::Model::CubismModelSettingJson::Create(buffer, size);
DeleteBuffer(buffer, fileName);
// 设置模型
SetupModel(_modelSetting);
// 设置纹理
SetupTextures();
// 预加载动作
PreloadMotionGroup(MotionGroupIdle);
}
void LAppModel::SetupModel(Csm::Model::ICubismModelSetting* setting) {
// 从模型设置中加载模型数据
_modelJson = setting->GetModelFileName();
std::string modelPath = _modelHomeDir + "/" + _modelJson;
Csm::csmSizeInt size;
Csm::csmByte* buffer = CreateBuffer(modelPath.c_str(), &size);
LoadModel(buffer, size);
DeleteBuffer(buffer, modelPath.c_str());
// 设置模型参数
_model->SaveParameters();
// 创建渲染器
_renderer = new Csm::Rendering::CubismRenderer_OpenGLES2();
_renderer->Initialize(_model);
}
void LAppModel::Update() {
const Csm::csmFloat32 deltaTimeSeconds = LAppPal::GetDeltaTime();
_userTimeSeconds += deltaTimeSeconds;
// 更新动作
_motionManager->UpdateMotion(_model, deltaTimeSeconds);
// 更新模型参数
_model->Update();
}
void LAppModel::Draw() {
// 设置投影矩阵
Csm::CubismMatrix44 projection;
projection.Scale(1.0f, 1.0f);
_renderer->SetMvpMatrix(&projection);
// 渲染模型
_renderer->DrawModel();
}
3.4 Live2DPlatformView.kt - Flutter平台视图
文件位置 : android/app/src/main/kotlin/com/hornhuang/tomato_plan/Live2DPlatformView.kt
实现Flutter的PlatformView,在Flutter中嵌入Live2D渲染。
关键代码:
kotlin
class Live2DPlatformView(context: Context, id: Int, creationParams: Map<String, Any>?)
: PlatformView {
private val glSurfaceView = GLSurfaceView(context)
private val renderer = Live2DRenderer(context)
init {
glSurfaceView.setEGLContextClientVersion(2)
glSurfaceView.setRenderer(renderer)
glSurfaceView.renderMode = GLSurfaceView.RENDERMODE_CONTINUOUSLY
}
override fun getView(): View = glSurfaceView
override fun dispose() {
// 清理资源
}
}
class Live2DRenderer(private val context: Context) : GLSurfaceView.Renderer {
lateinit var model: LAppModel
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
// 初始化Live2D SDK
Live2D_v3.init(context)
// 创建模型实例
model = LAppModel().apply {
// 加载模型文件
loadModelJson("assets://mianfeimox/llny.model3.json")
}
}
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
// 调整模型尺寸
model.resize(width, height)
}
override fun onDrawFrame(gl: GL10?) {
// 清空缓冲区
Live2D_v3.clearBuffer(0f, 1f, 0f, 0f)
// 更新模型状态
model.update()
// 设置参数(例如眨眼)
model.setParameterValue("Param14", 1f)
// 绘制模型
model.draw()
}
}
4. 完整流程说明
4.1 初始化流程
markdown
1. Flutter启动
↓
2. 创建Live2DPlatformView
↓
3. GLSurfaceView创建OpenGL ES 2.0上下文
↓
4. onSurfaceCreated回调触发
↓
5. 调用Live2D_v3.init(context)
↓
6. Csm_CubismFramework.initialize() 初始化Live2D框架
↓
7. 创建LAppModel实例
↓
8. 调用loadModelJson加载模型
4.2 模型加载流程
markdown
1. loadModelJson("assets://mianfeimox/llny.model3.json")
↓
2. JNI调用: Java_com_hornhuang_tomato_1plan_Live2D_1v3_00024LAppModel_loadModelJson
↓
3. LAppModel::LoadAssets(fileName)
↓
4. 读取.model3.json文件
↓
5. 解析JSON获取模型文件路径、纹理、动作等信息
↓
6. SetupModel() - 加载.moc3模型文件
↓
7. SetupTextures() - 加载纹理图片
↓
8. PreloadMotionGroup() - 预加载动作文件
↓
9. 创建CubismRenderer并初始化
4.3 渲染循环流程
markdown
每一帧:
1. onDrawFrame回调触发
↓
2. glClear清空颜色缓冲区
↓
3. model.update()
↓
4. _motionManager->UpdateMotion() - 更新动作状态
↓
5. _model->Update() - 更新模型参数
↓
6. model.draw()
↓
7. 设置投影矩阵
↓
8. _renderer->DrawModel() - 渲染模型
↓
9. OpenGL绘制到屏幕
4.4 交互处理流程
markdown
用户触摸:
1. 触摸事件传递到Live2DPlatformView
↓
2. 计算触摸坐标
↓
3. model.hitTest(hitAreaName, x, y)
↓
4. 检测是否击中可交互区域
↓
5. 如果击中,触发相应动作:
- model.startMotion() - 播放动作
- model.setExpression() - 设置表情
- model.setParameterValue() - 设置参数
5. 关键技术点
5.1 OpenGL上下文管理
每个OpenGL上下文需要独立初始化Live2D SDK:
kotlin
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
// 每个OpenGL上下文都需要单独初始化
Live2D_v3.init(context)
model = LAppModel()
}
5.2 资源路径处理
Android assets路径需要转换为C++可访问的路径:
cpp
void LAppModel::LoadAssets(const Csm::csmChar* fileName) {
// assets://路径转换为实际文件路径
std::filesystem::path p = std::filesystem::u8path(fileName);
_modelHomeDir = p.parent_path().u8string().c_str();
}
5.3 动作管理
动作通过优先级系统管理:
cpp
void LAppModel::StartMotion(const Csm::csmChar* group, Csm::csmInt32 no,
Csm::csmInt32 priority,
Csm::FinishedMotionCallback onFinishedMotionHandler) {
if (priority == _priority) {
_motionManager->SetReservePriority(priority);
} else if (priority < _motionManager->GetReservePriority()) {
return;
}
_motionManager->StartMotionPriority(
_model,
group,
no,
priority,
onFinishedMotionHandler
);
}
5.4 参数控制
Live2D模型参数实时控制:
kotlin
// 设置参数值
model.setParameterValue("ParamEyeLOpen", 0.5f)
// 增加参数值
model.addParameterValue("ParamAngleX", 0.1f)
// 设置部件透明度
model.setPartsOpacity("PartArmL", 0.8f)
6. 常见问题与解决方案
6.1 OpenGL上下文错误
问题: GL_INVALID_VALUE (0x501)错误
原因: 在不同的OpenGL上下文中使用同一个Live2D实例
解决方案: 每个OpenGL上下文独立初始化Live2D SDK和模型实例
6.2 模型加载失败
问题: 模型无法显示或崩溃
检查项:
- 模型文件路径是否正确
- .model3.json文件格式是否正确
- 纹理文件是否存在
- 动作文件是否完整
6.3 性能优化
建议:
- 使用RENDERMODE_WHEN_DIRTY而非RENDERMODE_CONTINUOUSLY
- 预加载常用动作
- 合理设置动作优先级
- 避免频繁创建销毁模型实例
7. 文件结构
bash
android/
├── app/
│ ├── src/
│ │ ├── main/
│ │ │ ├── kotlin/com/hornhuang/tomato_plan/
│ │ │ │ ├── Live2D_v3.java # Java/Kotlin接口
│ │ │ │ ├── Live2DPlatformView.kt # Flutter平台视图
│ │ │ │ └── MainActivity.kt # 主Activity
│ │ │ ├── cpp/
│ │ │ │ ├── lapp_model.cpp # JNI绑定
│ │ │ │ ├── live2d.cpp # Live2D初始化
│ │ │ │ └── Main/src/
│ │ │ │ └── LAppModel.cpp # 模型管理
│ │ │ └── assets/
│ │ │ └── mianfeimox/ # Live2D模型资源
│ │ │ ├── llny.model3.json
│ │ │ ├── llny.moc3
│ │ │ ├── textures/
│ │ │ └── motions/
8. 扩展功能
8.1 添加交互事件
kotlin
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
model.hitTest("Head", event.x, event.y)
}
MotionEvent.ACTION_MOVE -> {
model.setDragging(event.x, event.y)
}
MotionEvent.ACTION_UP -> {
model.setDragging(0f, 0f)
}
}
return true
}
8.2 播放动作
kotlin
// 播放指定动作
model.startMotion("TapBody", 0, 3,
{ group, no -> println("动作开始: $group, $no") },
{ self -> println("动作结束") }
)
// 设置表情
model.setExpression("f01")
8.3 物理模拟
kotlin
// 设置加速度(用于物理模拟)
model.setAcceleration(0.5f, 0.0f, 0.0f)
9. 总结
Live2D SDK在Android Flutter应用中的集成涉及多个层次的协作:
- Flutter层: 通过PlatformView嵌入原生视图
- Java/Kotlin层: 提供Live2D SDK的接口封装
- JNI层: 实现Java与C++的跨语言调用
- C++层: 实现Live2D模型的核心功能
关键要点:
- 每个OpenGL上下文需要独立初始化Live2D SDK
- 模型加载遵循: JSON解析 → 资源加载 → 渲染器初始化
- 渲染循环: 更新动作 → 更新模型 → 绘制
- 交互通过参数控制和动作播放实现
通过合理管理OpenGL上下文和资源生命周期,可以实现稳定高效的Live2D模型渲染效果。
第二章 Flutter侧的渲染与展示
Flutter调用Android Native组件展示Live2D - 问题解决总结
1. Flutter调用Android Native组件展示Live2D的完整流程
1.1 整体架构
markdown
Flutter层 (Dart)
↓ PlatformView
Android Kotlin层
↓ JNI调用
C++ Native层
↓ Live2D SDK
OpenGL ES渲染
1.2 详细调用链路
第一步:Flutter端创建PlatformView
dart
// 在Flutter代码中创建Live2DView
AndroidView(
viewType: 'com.hornhuang.tomato_plan/live2d_view',
creationParams: <String, dynamic>{},
creationParamsCodec: const StandardMessageCodec(),
)
第二步:MainActivity注册PlatformView
kotlin
// MainActivity.kt
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
flutterEngine
.platformViewsController
.registry
.registerViewFactory(
"com.hornhuang.tomato_plan/live2d_view",
Live2DPlatformViewFactory()
)
}
}
第三步:Live2DPlatformViewFactory创建PlatformView
kotlin
class Live2DPlatformViewFactory : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
override fun create(context: Context, viewId: Int, args: Any?): PlatformView {
return Live2DPlatformView(context, viewId, args as Map<String, Any>?)
}
}
第四步:Live2DPlatformView创建GLSurfaceView和渲染器
kotlin
class Live2DPlatformView(context: Context, id: Int, creationParams: Map<String, Any>?)
: PlatformView {
private val glSurfaceView = GLSurfaceView(context)
private val renderer = Live2DRenderer(context)
init {
glSurfaceView.setEGLContextClientVersion(2)
glSurfaceView.setRenderer(renderer)
glSurfaceView.renderMode = GLSurfaceView.RENDERMODE_CONTINUOUSLY
}
override fun getView(): View = glSurfaceView
}
第五步:Live2DRenderer初始化Live2D SDK
kotlin
class Live2DRenderer(private val context: Context) : GLSurfaceView.Renderer {
lateinit var model: LAppModel
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
// 初始化Live2D SDK
Live2D_v3.init(context)
// 创建模型实例
model = LAppModel().apply {
loadModelJson("assets://mianfeimox/llny.model3.json")
}
}
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
model.resize(width, height)
}
override fun onDrawFrame(gl: GL10?) {
Live2D_v3.clearBuffer(0f, 1f, 0f, 0f)
model.update()
model.setParameterValue("Param14", 1f)
model.draw()
}
}
第六步:JNI调用C++层
java
// Live2D_v3.java
public class LAppModel {
private final long nativeModel;
public native void loadModelJson(String fileName);
public native void update();
public native void draw();
}
cpp
// lapp_model.cpp
extern "C" JNIEXPORT void JNICALL
Java_com_hornhuang_tomato_1plan_Live2D_1v3_00024LAppModel_loadModelJson(JNIEnv *env, jobject thiz,
jstring file_name) {
const char *json_file = env->GetStringUTFChars(file_name, nullptr);
getModel(env, thiz)->LoadAssets(json_file);
env->ReleaseStringUTFChars(file_name, json_file);
}
extern "C" JNIEXPORT void JNICALL
Java_com_hornhuang_tomato_1plan_Live2D_1v3_00024LAppModel_draw(JNIEnv *env, jobject thiz) {
getModel(env, thiz)->Draw();
}
第七步:C++层加载和渲染Live2D模型
cpp
// LAppModel.cpp
void LAppModel::LoadAssets(const Csm::csmChar* fileName) {
// 解析.model3.json文件
Csm::csmByte* buffer = CreateBuffer(fileName, &size);
_modelSetting = Csm::Model::CubismModelSettingJson::Create(buffer, size);
DeleteBuffer(buffer, fileName);
// 设置模型和纹理
SetupModel(_modelSetting);
SetupTextures();
PreloadMotionGroup(MotionGroupIdle);
}
void LAppModel::Draw() {
Csm::CubismMatrix44 projection;
projection.Scale(1.0f, 1.0f);
_renderer->SetMvpMatrix(&projection);
_renderer->DrawModel();
}
2. 遇到的问题和Bug
问题1:Platform View未注册错误
错误信息
bash
Trying to create a platform view of unregistered type: com.hornhuang.tomato_plan/live2d_view
触发原因
删除了NativeTextView相关文件后,MainActivity.kt中仍然保留着NativeTextViewFactory的注册代码,但没有注册Live2DPlatformViewFactory。
解决方案
- 删除MainActivity.kt中的NativeTextViewFactory注册代码
- 添加Live2DPlatformViewFactory的注册
kotlin
// MainActivity.kt
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
flutterEngine
.platformViewsController
.registry
.registerViewFactory(
"com.hornhuang.tomato_plan/live2d_view",
Live2DPlatformViewFactory()
)
}
}
问题2:编译错误 - 未解析的引用
错误信息
yaml
Unresolved reference: LAppModel
Unresolved reference: drag
Unresolved reference: touch
Unresolved reference: release
触发原因
Live2DPlatformView.kt中使用了LAppModel类,但没有导入正确的包。
解决方案
在Live2DPlatformView.kt文件顶部添加导入语句:
kotlin
import com.hornhuang.tomato_plan.Live2D_v3.LAppModel
问题3:Live2DActivity能显示,但Flutter的Live2DView显示空白(核心问题)
现象描述
- 打开Live2DActivity:Live2D模型正常显示
- 返回Flutter首页:Live2DView显示一片空白(绿色背景)
- 日志中出现OpenGL错误:
glGetError() returned error 0x501
触发原因分析
根本原因:OpenGL上下文冲突
-
Live2DActivity和Live2DPlatformView使用不同的OpenGL上下文
- Live2DActivity有自己的GLSurfaceView,创建独立的OpenGL上下文
- Live2DPlatformView也有自己的GLSurfaceView,创建另一个独立的OpenGL上下文
-
Live2D SDK的初始化问题
- 最初的实现中,Live2D_v3.init()在MainActivity中只调用一次
- 这导致Live2D SDK的静态资源(如着色器程序)在第一个OpenGL上下文中创建
- 当切换到第二个OpenGL上下文时,这些资源无效
-
着色器程序的OpenGL上下文绑定
- OpenGL的着色器程序是上下文绑定的
- 在上下文A中创建的着色器程序在上下文B中无法使用
- CubismRenderer在创建时会编译和链接着色器程序
- 这些程序只在创建它们的上下文中有效
-
错误流程
scss用户打开Live2DActivity ↓ Live2DActivity的OpenGL上下文创建 ↓ Live2D_v3.init() 在MainActivity中调用(第一次) ↓ 着色器程序在Live2DActivity的上下文中创建 ↓ Live2D模型正常显示 用户返回Flutter首页 ↓ Live2DPlatformView的OpenGL上下文创建 ↓ Live2D_v3.init() 检测到已初始化,跳过 ↓ Live2DPlatformView尝试使用Live2D SDK ↓ 尝试使用在Live2DActivity上下文中创建的着色器程序 ↓ OpenGL错误 0x501 (GL_INVALID_VALUE) ↓ 渲染失败,显示空白
解决方案
方案:每个OpenGL上下文独立初始化Live2D SDK
- 修改Live2D_v3.java,支持多次初始化
java
public class Live2D_v3 {
private static boolean initialized = false;
private static Context context;
public static void init(Context ctx) {
if (!initialized) {
context = ctx;
Csm_CubismFramework.initialize();
initialized = true;
}
}
}
- 修改Live2DPlatformView.kt,在onSurfaceCreated中初始化
kotlin
class Live2DRenderer(private val context: Context) : GLSurfaceView.Renderer {
lateinit var model: LAppModel
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
// 在每个OpenGL上下文中独立初始化Live2D SDK
Live2D_v3.init(context)
// 创建模型实例
model = LAppModel().apply {
loadModelJson("assets://mianfeimox/llny.model3.json")
}
}
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
model.resize(width, height)
}
override fun onDrawFrame(gl: GL10?) {
Live2D_v3.clearBuffer(0f, 1f, 0f, 0f)
model.update()
model.setParameterValue("Param14", 1f)
model.draw()
}
}
- 修改Live2DActivity.kt,在MyRenderer中初始化
kotlin
class Live2DActivity : AppCompatActivity() {
private lateinit var glSurfaceView: GLSurfaceView
private lateinit var renderer: MyRenderer
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_live2d)
glSurfaceView = findViewById(R.id.glSurfaceView)
renderer = MyRenderer(this)
glSurfaceView.setEGLContextClientVersion(2)
glSurfaceView.setRenderer(renderer)
}
}
class MyRenderer(private val context: Context) : GLSurfaceView.Renderer {
private lateinit var model: LAppModel
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
// 在Live2DActivity的OpenGL上下文中初始化Live2D SDK
Live2D_v3.init(context)
model = LAppModel().apply {
loadModelJson("assets://mianfeimox/llny.model3.json")
}
}
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
model.resize(width, height)
}
override fun onDrawFrame(gl: GL10?) {
Live2D_v3.clearBuffer(0f, 0f, 0f, 0f)
model.update()
model.setParameterValue("Param14", 1f)
model.draw()
}
}
- 移除MainActivity中的集中初始化
kotlin
// MainActivity.kt - 不再在这里初始化Live2D
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
flutterEngine
.platformViewsController
.registry
.registerViewFactory(
"com.hornhuang.tomato_plan/live2d_view",
Live2DPlatformViewFactory()
)
}
}
修复后的流程
scss
用户打开Live2DActivity
↓
Live2DActivity的OpenGL上下文创建
↓
MyRenderer.onSurfaceCreated() 调用
↓
Live2D_v3.init() 第一次初始化
↓
着色器程序在Live2DActivity的上下文中创建
↓
Live2D模型正常显示
用户返回Flutter首页
↓
Live2DPlatformView的OpenGL上下文创建
↓
Live2DRenderer.onSurfaceCreated() 调用
↓
Live2D_v3.init() 检测到已初始化,跳过框架初始化
↓
创建新的LAppModel实例
↓
CubismRenderer在Live2DPlatformView的上下文中创建新的着色器程序
↓
Live2D模型正常显示
3. 关键技术点总结
3.1 OpenGL上下文隔离
- 每个GLSurfaceView创建独立的OpenGL上下文
- OpenGL资源(着色器程序、纹理等)是上下文绑定的
- 不同上下文之间不能共享OpenGL资源
3.2 Live2D SDK的初始化策略
- 框架初始化(Csm_CubismFramework.initialize()):只需一次
- 渲染器初始化(CubismRenderer):每个OpenGL上下文需要独立创建
- 模型实例:每个OpenGL上下文需要独立的LAppModel实例
3.3 Flutter PlatformView的生命周期
- PlatformView创建时,GLSurfaceView也会创建
- GLSurfaceView的onSurfaceCreated在OpenGL上下文创建时调用
- onSurfaceChanged在视图尺寸变化时调用
- onDrawFrame在每一帧渲染时调用
4. 问题排查过程
4.1 初步排查
- 检查Live2DActivity能正常显示,说明模型文件和Live2D SDK本身没有问题
- 检查Flutter的Live2DView显示绿色背景,说明GLSurfaceView正常工作
- 日志中出现OpenGL错误,指向渲染问题
4.2 深入分析
- 分析Live2DActivity和Live2DPlatformView的代码差异
- 发现两者都使用Live2D_v3.init(),但调用位置不同
- 研究OpenGL上下文的管理机制
- 确认着色器程序的上下文绑定特性
4.3 验证假设
- 在Live2DPlatformView的onSurfaceCreated中添加日志
- 观察Live2D_v3.init()的调用时机
- 确认OpenGL错误的触发时机
- 验证独立初始化方案的有效性
5. 经验教训
5.1 OpenGL上下文管理
- 在Android中使用多个GLSurfaceView时,要注意上下文隔离
- OpenGL资源不能跨上下文共享
- 每个上下文需要独立初始化和清理资源
5.2 Live2D SDK集成
- Live2D SDK的框架初始化可以全局进行
- 但渲染相关的资源(着色器程序、纹理等)需要每个上下文独立创建
- 模型实例也应该每个上下文独立创建
5.3 Flutter PlatformView开发
- PlatformView的生命周期与原生View一致
- 需要正确处理OpenGL上下文的创建和销毁
- 避免在PlatformView中使用静态的OpenGL资源
6. 最佳实践
6.1 初始化模式
kotlin
// 推荐的初始化模式
class MyRenderer(private val context: Context) : GLSurfaceView.Renderer {
private lateinit var model: LAppModel
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
// 每个OpenGL上下文独立初始化
Live2D_v3.init(context)
// 创建独立的模型实例
model = LAppModel()
model.loadModelJson("assets://model/xxx.model3.json")
}
override fun onSurfaceDestroyed(gl: GL10?) {
// 清理资源
// 注意:Live2D_v3.dispose()不应该在这里调用
// 因为可能还有其他OpenGL上下文在使用
}
}
6.2 资源管理
- 避免在多个OpenGL上下文之间共享OpenGL资源
- 每个上下文独立创建和管理自己的资源
- 注意资源的生命周期,避免内存泄漏
6.3 错误处理
- 使用glGetError()检测OpenGL错误
- 记录详细的日志以便排查问题
- 理解OpenGL错误码的含义(如0x501 = GL_INVALID_VALUE)
7. 总结
通过这次问题排查和修复,我们深入理解了:
- Flutter PlatformView的工作原理
- OpenGL上下文的管理机制
- Live2D SDK的正确集成方式
- 跨上下文资源共享的限制
核心解决方案是:每个OpenGL上下文独立初始化Live2D相关的渲染资源,确保着色器程序等OpenGL资源在正确的上下文中创建和使用。
这个经验对于其他需要集成OpenGL渲染的Flutter项目也具有参考价值。