【2025最新】Flutter 加载显示 Live2D 角色,实战与踩坑全链路分享

特别感谢 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应用中的集成涉及多个层次的协作:

  1. Flutter层: 通过PlatformView嵌入原生视图
  2. Java/Kotlin层: 提供Live2D SDK的接口封装
  3. JNI层: 实现Java与C++的跨语言调用
  4. 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。

解决方案
  1. 删除MainActivity.kt中的NativeTextViewFactory注册代码
  2. 添加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上下文冲突

  1. Live2DActivity和Live2DPlatformView使用不同的OpenGL上下文

    • Live2DActivity有自己的GLSurfaceView,创建独立的OpenGL上下文
    • Live2DPlatformView也有自己的GLSurfaceView,创建另一个独立的OpenGL上下文
  2. Live2D SDK的初始化问题

    • 最初的实现中,Live2D_v3.init()在MainActivity中只调用一次
    • 这导致Live2D SDK的静态资源(如着色器程序)在第一个OpenGL上下文中创建
    • 当切换到第二个OpenGL上下文时,这些资源无效
  3. 着色器程序的OpenGL上下文绑定

    • OpenGL的着色器程序是上下文绑定的
    • 在上下文A中创建的着色器程序在上下文B中无法使用
    • CubismRenderer在创建时会编译和链接着色器程序
    • 这些程序只在创建它们的上下文中有效
  4. 错误流程

    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

  1. 修改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;
        }
    }
}
  1. 修改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()
    }
}
  1. 修改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()
    }
}
  1. 移除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 初步排查

  1. 检查Live2DActivity能正常显示,说明模型文件和Live2D SDK本身没有问题
  2. 检查Flutter的Live2DView显示绿色背景,说明GLSurfaceView正常工作
  3. 日志中出现OpenGL错误,指向渲染问题

4.2 深入分析

  1. 分析Live2DActivity和Live2DPlatformView的代码差异
  2. 发现两者都使用Live2D_v3.init(),但调用位置不同
  3. 研究OpenGL上下文的管理机制
  4. 确认着色器程序的上下文绑定特性

4.3 验证假设

  1. 在Live2DPlatformView的onSurfaceCreated中添加日志
  2. 观察Live2D_v3.init()的调用时机
  3. 确认OpenGL错误的触发时机
  4. 验证独立初始化方案的有效性

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. 总结

通过这次问题排查和修复,我们深入理解了:

  1. Flutter PlatformView的工作原理
  2. OpenGL上下文的管理机制
  3. Live2D SDK的正确集成方式
  4. 跨上下文资源共享的限制

核心解决方案是:每个OpenGL上下文独立初始化Live2D相关的渲染资源,确保着色器程序等OpenGL资源在正确的上下文中创建和使用。

这个经验对于其他需要集成OpenGL渲染的Flutter项目也具有参考价值。

相关推荐
千里马学框架16 小时前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台16 小时前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone17 小时前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc17 小时前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo19 小时前
Kotlin 协程中的 Job 结构化并发与取消
android
sun00770019 小时前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼20 小时前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone21 小时前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen21 小时前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone21 小时前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui