Flutter Android Live2D 2026 实战:模型加载 + 集成渲染 + 显示全流程 + 10 个核心坑( OpenGL )

特别感谢 RealCoolSnow/live2d-android 大佬的开元分享

  • 关于 Flutter 端加载代码:其实只要 Android 原生能正常显示,Flutter 这边用 PlatformView 对接的逻辑很固定,让 AI 生成基础代码就行(比如 Dart 侧的 AndroidView 创建、原生视图注册),不用重复贴冗余代码;
  • 重点说明:这篇文章会聚焦「Android 原生集成核心流程」「跨层调用踩坑」「OpenGL 上下文冲突解决」这些 AI 写不出来的实战细节,都是我实测踩过的硬坑;
  • 恳请大家:觉得有用的话,务必给原作者大佬点个 star🌟RealCoolSnow/live2d-android,再给这篇文章点个赞~ 开源作者的分享不易,你的支持才是他们持续输出的动力,不然以后可就难挖到这么好的免费资源啦!

第一章,要在android上集成live2d

Live2D SDK 集成说明文档

1. 概述

本文档详细说明了在Android Flutter应用中如何集成Live2D SDK,实现从模型加载到显示的完整流程。整个集成涉及Java/Kotlin层、JNI层和C++原生层的协同工作。

2. 架构层次

┌─────────────────────────────────────┐
│   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初始化和模型管理功能。

关键代码:

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++函数调用,实现跨语言交互。

关键代码:

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模型的加载、更新、渲染和交互处理。

关键代码:

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渲染。

关键代码:

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 初始化流程

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 模型加载流程

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 渲染循环流程

每一帧:
1. onDrawFrame回调触发
   ↓
2. glClear清空颜色缓冲区
   ↓
3. model.update()
   ↓
4. _motionManager->UpdateMotion() - 更新动作状态
   ↓
5. _model->Update() - 更新模型参数
   ↓
6. model.draw()
   ↓
7. 设置投影矩阵
   ↓
8. _renderer->DrawModel() - 渲染模型
   ↓
9. OpenGL绘制到屏幕

4.4 交互处理流程

用户触摸:
1. 触摸事件传递到Live2DPlatformView
   ↓
2. 计算触摸坐标
   ↓
3. model.hitTest(hitAreaName, x, y)
   ↓
4. 检测是否击中可交互区域
   ↓
5. 如果击中,触发相应动作:
   - model.startMotion() - 播放动作
   - model.setExpression() - 设置表情
   - model.setParameterValue() - 设置参数

5. 关键技术点

5.1 OpenGL上下文管理

每个OpenGL上下文需要独立初始化Live2D SDK:

override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
    // 每个OpenGL上下文都需要单独初始化
    Live2D_v3.init(context)
    model = LAppModel()
}

5.2 资源路径处理

Android assets路径需要转换为C++可访问的路径:

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 动作管理

动作通过优先级系统管理:

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模型参数实时控制:

// 设置参数值
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. 文件结构

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 添加交互事件

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 播放动作

// 播放指定动作
model.startMotion("TapBody", 0, 3, 
    { group, no -> println("动作开始: $group, $no") },
    { self -> println("动作结束") }
)

// 设置表情
model.setExpression("f01")

8.3 物理模拟

// 设置加速度(用于物理模拟)
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 整体架构

Flutter层 (Dart)
    ↓ PlatformView
Android Kotlin层
    ↓ JNI调用
C++ Native层
    ↓ Live2D SDK
OpenGL ES渲染

1.2 详细调用链路

第一步:Flutter端创建PlatformView

// 在Flutter代码中创建Live2DView
AndroidView(
  viewType: 'com.hornhuang.tomato_plan/live2d_view',
  creationParams: <String, dynamic>{},
  creationParamsCodec: const StandardMessageCodec(),
)

第二步:MainActivity注册PlatformView

// 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

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和渲染器

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

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++层

// Live2D_v3.java
public class LAppModel {
    private final long nativeModel;

    public native void loadModelJson(String fileName);
    public native void update();
    public native void draw();
}
// 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模型

// 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未注册错误

错误信息

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的注册
// 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:编译错误 - 未解析的引用

错误信息

Unresolved reference: LAppModel
Unresolved reference: drag
Unresolved reference: touch
Unresolved reference: release

触发原因

Live2DPlatformView.kt中使用了LAppModel类,但没有导入正确的包。

解决方案

在Live2DPlatformView.kt文件顶部添加导入语句:

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. 错误流程

    用户打开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,支持多次初始化
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中初始化
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中初始化
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中的集中初始化
// 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()
            )
    }
}

修复后的流程

用户打开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 初始化模式

// 推荐的初始化模式
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项目也具有参考价值。

posted @ 2025-12-28 19:21  圆号本昊  阅读(45)  评论(0)    收藏  举报