Flutter Android Live2D 2026 實(shí)戰(zhàn):模型加載 + 集成渲染 + 顯示全流程 + 10 個(gè)核心坑( OpenGL )

特別感謝 RealCoolSnow/live2d-android 大佬的開元分享

  • 關(guān)于 Flutter 端加載代碼:其實(shí)只要 Android 原生能正常顯示,F(xiàn)lutter 這邊用 PlatformView 對(duì)接的邏輯很固定,讓 AI 生成基礎(chǔ)代碼就行(比如 Dart 側(cè)的 AndroidView 創(chuàng)建、原生視圖注冊(cè)),不用重復(fù)貼冗余代碼;
  • 重點(diǎn)說明:這篇文章會(huì)聚焦「Android 原生集成核心流程」「跨層調(diào)用踩坑」「OpenGL 上下文沖突解決」這些 AI 寫不出來的實(shí)戰(zhàn)細(xì)節(jié),都是我實(shí)測(cè)踩過的硬坑;
  • 懇請(qǐng)大家:覺得有用的話,務(wù)必給原作者大佬點(diǎn)個(gè) star??RealCoolSnow/live2d-android,再給這篇文章點(diǎn)個(gè)贊~ 開源作者的分享不易,你的支持才是他們持續(xù)輸出的動(dòng)力,不然以后可就難挖到這么好的免費(fèi)資源啦!

第一章,要在android上集成live2d

Live2D SDK 集成說明文檔

1. 概述

本文檔詳細(xì)說明了在Android Flutter應(yīng)用中如何集成Live2D SDK,實(shí)現(xiàn)從模型加載到顯示的完整流程。整個(gè)集成涉及Java/Kotlin層、JNI層和C++原生層的協(xié)同工作。

2. 架構(gòu)層次

┌─────────────────────────────────────┐
│   Flutter層 (Dart)                  │
│   - Live2DView Widget               │
└──────────────┬──────────────────────┘
               │ PlatformView
┌──────────────▼──────────────────────┐
│   Android Kotlin層                  │
│   - Live2DPlatformView              │
│   - Live2D_v3 (Java接口)            │
└──────────────┬──────────────────────┘
               │ JNI調(diào)用
┌──────────────▼──────────────────────┐
│   C++ JNI層                         │
│   - lapp_model.cpp (JNI綁定)        │
└──────────────┬──────────────────────┘
               │
┌──────────────▼──────────────────────┐
│   Live2D SDK C++層                  │
│   - LAppModel (模型管理)            │
│   - CubismRenderer (渲染器)         │
│   - CubismModel (模型數(shù)據(jù))          │
└─────────────────────────────────────┘

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

關(guān)鍵代碼:

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

負(fù)責(zé)將Java/Kotlin方法調(diào)用轉(zhuǎn)換為C++函數(shù)調(diào)用,實(shí)現(xiàn)跨語言交互。

關(guān)鍵代碼:

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

實(shí)現(xiàn)Live2D模型的加載、更新、渲染和交互處理。

關(guān)鍵代碼:

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);
    
    // 設(shè)置模型
    SetupModel(_modelSetting);
    
    // 設(shè)置紋理
    SetupTextures();
    
    // 預(yù)加載動(dòng)作
    PreloadMotionGroup(MotionGroupIdle);
}

void LAppModel::SetupModel(Csm::Model::ICubismModelSetting* setting) {
    // 從模型設(shè)置中加載模型數(shù)據(jù)
    _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());
    
    // 設(shè)置模型參數(shù)
    _model->SaveParameters();
    
    // 創(chuàng)建渲染器
    _renderer = new Csm::Rendering::CubismRenderer_OpenGLES2();
    _renderer->Initialize(_model);
}

void LAppModel::Update() {
    const Csm::csmFloat32 deltaTimeSeconds = LAppPal::GetDeltaTime();
    _userTimeSeconds += deltaTimeSeconds;
    
    // 更新動(dòng)作
    _motionManager->UpdateMotion(_model, deltaTimeSeconds);
    
    // 更新模型參數(shù)
    _model->Update();
}

void LAppModel::Draw() {
    // 設(shè)置投影矩陣
    Csm::CubismMatrix44 projection;
    projection.Scale(1.0f, 1.0f);
    _renderer->SetMvpMatrix(&projection);
    
    // 渲染模型
    _renderer->DrawModel();
}

3.4 Live2DPlatformView.kt - Flutter平臺(tái)視圖

文件位置: android/app/src/main/kotlin/com/hornhuang/tomato_plan/Live2DPlatformView.kt

實(shí)現(xiàn)Flutter的PlatformView,在Flutter中嵌入Live2D渲染。

關(guān)鍵代碼:

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)
        
        // 創(chuàng)建模型實(shí)例
        model = LAppModel().apply {
            // 加載模型文件
            loadModelJson("assets://mianfeimox/llny.model3.json")
        }
    }

    override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
        // 調(diào)整模型尺寸
        model.resize(width, height)
    }

    override fun onDrawFrame(gl: GL10?) {
        // 清空緩沖區(qū)
        Live2D_v3.clearBuffer(0f, 1f, 0f, 0f)
        
        // 更新模型狀態(tài)
        model.update()
        
        // 設(shè)置參數(shù)(例如眨眼)
        model.setParameterValue("Param14", 1f)
        
        // 繪制模型
        model.draw()
    }
}

4. 完整流程說明

4.1 初始化流程

1. Flutter啟動(dòng)
   ↓
2. 創(chuàng)建Live2DPlatformView
   ↓
3. GLSurfaceView創(chuàng)建OpenGL ES 2.0上下文
   ↓
4. onSurfaceCreated回調(diào)觸發(fā)
   ↓
5. 調(diào)用Live2D_v3.init(context)
   ↓
6. Csm_CubismFramework.initialize() 初始化Live2D框架
   ↓
7. 創(chuàng)建LAppModel實(shí)例
   ↓
8. 調(diào)用loadModelJson加載模型

4.2 模型加載流程

1. loadModelJson("assets://mianfeimox/llny.model3.json")
   ↓
2. JNI調(diào)用: Java_com_hornhuang_tomato_1plan_Live2D_1v3_00024LAppModel_loadModelJson
   ↓
3. LAppModel::LoadAssets(fileName)
   ↓
4. 讀取.model3.json文件
   ↓
5. 解析JSON獲取模型文件路徑、紋理、動(dòng)作等信息
   ↓
6. SetupModel() - 加載.moc3模型文件
   ↓
7. SetupTextures() - 加載紋理圖片
   ↓
8. PreloadMotionGroup() - 預(yù)加載動(dòng)作文件
   ↓
9. 創(chuàng)建CubismRenderer并初始化

4.3 渲染循環(huán)流程

每一幀:
1. onDrawFrame回調(diào)觸發(fā)
   ↓
2. glClear清空顏色緩沖區(qū)
   ↓
3. model.update()
   ↓
4. _motionManager->UpdateMotion() - 更新動(dòng)作狀態(tài)
   ↓
5. _model->Update() - 更新模型參數(shù)
   ↓
6. model.draw()
   ↓
7. 設(shè)置投影矩陣
   ↓
8. _renderer->DrawModel() - 渲染模型
   ↓
9. OpenGL繪制到屏幕

4.4 交互處理流程

用戶觸摸:
1. 觸摸事件傳遞到Live2DPlatformView
   ↓
2. 計(jì)算觸摸坐標(biāo)
   ↓
3. model.hitTest(hitAreaName, x, y)
   ↓
4. 檢測(cè)是否擊中可交互區(qū)域
   ↓
5. 如果擊中,觸發(fā)相應(yīng)動(dòng)作:
   - model.startMotion() - 播放動(dòng)作
   - model.setExpression() - 設(shè)置表情
   - model.setParameterValue() - 設(shè)置參數(shù)

5. 關(guān)鍵技術(shù)點(diǎn)

5.1 OpenGL上下文管理

每個(gè)OpenGL上下文需要獨(dú)立初始化Live2D SDK:

override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
    // 每個(gè)OpenGL上下文都需要單獨(dú)初始化
    Live2D_v3.init(context)
    model = LAppModel()
}

5.2 資源路徑處理

Android assets路徑需要轉(zhuǎn)換為C++可訪問的路徑:

void LAppModel::LoadAssets(const Csm::csmChar* fileName) {
    // assets://路徑轉(zhuǎn)換為實(shí)際文件路徑
    std::filesystem::path p = std::filesystem::u8path(fileName);
    _modelHomeDir = p.parent_path().u8string().c_str();
}

5.3 動(dòng)作管理

動(dòng)作通過優(yōu)先級(jí)系統(tǒng)管理:

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 參數(shù)控制

Live2D模型參數(shù)實(shí)時(shí)控制:

// 設(shè)置參數(shù)值
model.setParameterValue("ParamEyeLOpen", 0.5f)

// 增加參數(shù)值
model.addParameterValue("ParamAngleX", 0.1f)

// 設(shè)置部件透明度
model.setPartsOpacity("PartArmL", 0.8f)

6. 常見問題與解決方案

6.1 OpenGL上下文錯(cuò)誤

問題: GL_INVALID_VALUE (0x501)錯(cuò)誤

原因: 在不同的OpenGL上下文中使用同一個(gè)Live2D實(shí)例

解決方案: 每個(gè)OpenGL上下文獨(dú)立初始化Live2D SDK和模型實(shí)例

6.2 模型加載失敗

問題: 模型無法顯示或崩潰

檢查項(xiàng):

  • 模型文件路徑是否正確
  • .model3.json文件格式是否正確
  • 紋理文件是否存在
  • 動(dòng)作文件是否完整

6.3 性能優(yōu)化

建議:

  • 使用RENDERMODE_WHEN_DIRTY而非RENDERMODE_CONTINUOUSLY
  • 預(yù)加載常用動(dòng)作
  • 合理設(shè)置動(dòng)作優(yōu)先級(jí)
  • 避免頻繁創(chuàng)建銷毀模型實(shí)例

7. 文件結(jié)構(gòu)

android/
├── app/
│   ├── src/
│   │   ├── main/
│   │   │   ├── kotlin/com/hornhuang/tomato_plan/
│   │   │   │   ├── Live2D_v3.java          # Java/Kotlin接口
│   │   │   │   ├── Live2DPlatformView.kt    # Flutter平臺(tái)視圖
│   │   │   │   └── 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. 擴(kuò)展功能

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 播放動(dòng)作

// 播放指定動(dòng)作
model.startMotion("TapBody", 0, 3, 
    { group, no -> println("動(dòng)作開始: $group, $no") },
    { self -> println("動(dòng)作結(jié)束") }
)

// 設(shè)置表情
model.setExpression("f01")

8.3 物理模擬

// 設(shè)置加速度(用于物理模擬)
model.setAcceleration(0.5f, 0.0f, 0.0f)

9. 總結(jié)

Live2D SDK在Android Flutter應(yīng)用中的集成涉及多個(gè)層次的協(xié)作:

  1. Flutter層: 通過PlatformView嵌入原生視圖
  2. Java/Kotlin層: 提供Live2D SDK的接口封裝
  3. JNI層: 實(shí)現(xiàn)Java與C++的跨語言調(diào)用
  4. C++層: 實(shí)現(xiàn)Live2D模型的核心功能

關(guān)鍵要點(diǎn):

  • 每個(gè)OpenGL上下文需要獨(dú)立初始化Live2D SDK
  • 模型加載遵循: JSON解析 → 資源加載 → 渲染器初始化
  • 渲染循環(huán): 更新動(dòng)作 → 更新模型 → 繪制
  • 交互通過參數(shù)控制和動(dòng)作播放實(shí)現(xiàn)

通過合理管理OpenGL上下文和資源生命周期,可以實(shí)現(xiàn)穩(wěn)定高效的Live2D模型渲染效果。

第二章 Flutter側(cè)的渲染與展示

Flutter調(diào)用Android Native組件展示Live2D - 問題解決總結(jié)

1. Flutter調(diào)用Android Native組件展示Live2D的完整流程

1.1 整體架構(gòu)

Flutter層 (Dart)
    ↓ PlatformView
Android Kotlin層
    ↓ JNI調(diào)用
C++ Native層
    ↓ Live2D SDK
OpenGL ES渲染

1.2 詳細(xì)調(diào)用鏈路

第一步:Flutter端創(chuàng)建PlatformView

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

第二步:MainActivity注冊(cè)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創(chuàng)建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創(chuàng)建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)
        
        // 創(chuàng)建模型實(shí)例
        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調(diào)用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);
    
    // 設(shè)置模型和紋理
    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未注冊(cè)錯(cuò)誤

錯(cuò)誤信息

Trying to create a platform view of unregistered type: com.hornhuang.tomato_plan/live2d_view

觸發(fā)原因

刪除了NativeTextView相關(guān)文件后,MainActivity.kt中仍然保留著NativeTextViewFactory的注冊(cè)代碼,但沒有注冊(cè)Live2DPlatformViewFactory。

解決方案

  1. 刪除MainActivity.kt中的NativeTextViewFactory注冊(cè)代碼
  2. 添加Live2DPlatformViewFactory的注冊(cè)
// 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:編譯錯(cuò)誤 - 未解析的引用

錯(cuò)誤信息

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

觸發(fā)原因

Live2DPlatformView.kt中使用了LAppModel類,但沒有導(dǎo)入正確的包。

解決方案

在Live2DPlatformView.kt文件頂部添加導(dǎo)入語句:

import com.hornhuang.tomato_plan.Live2D_v3.LAppModel

問題3:Live2DActivity能顯示,但Flutter的Live2DView顯示空白(核心問題)

現(xiàn)象描述

  • 打開Live2DActivity:Live2D模型正常顯示
  • 返回Flutter首頁:Live2DView顯示一片空白(綠色背景)
  • 日志中出現(xiàn)OpenGL錯(cuò)誤:glGetError() returned error 0x501

觸發(fā)原因分析

根本原因:OpenGL上下文沖突

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

    • Live2DActivity有自己的GLSurfaceView,創(chuàng)建獨(dú)立的OpenGL上下文
    • Live2DPlatformView也有自己的GLSurfaceView,創(chuàng)建另一個(gè)獨(dú)立的OpenGL上下文
  2. Live2D SDK的初始化問題

    • 最初的實(shí)現(xiàn)中,Live2D_v3.init()在MainActivity中只調(diào)用一次
    • 這導(dǎo)致Live2D SDK的靜態(tài)資源(如著色器程序)在第一個(gè)OpenGL上下文中創(chuàng)建
    • 當(dāng)切換到第二個(gè)OpenGL上下文時(shí),這些資源無效
  3. 著色器程序的OpenGL上下文綁定

    • OpenGL的著色器程序是上下文綁定的
    • 在上下文A中創(chuàng)建的著色器程序在上下文B中無法使用
    • CubismRenderer在創(chuàng)建時(shí)會(huì)編譯和鏈接著色器程序
    • 這些程序只在創(chuàng)建它們的上下文中有效
  4. 錯(cuò)誤流程

    用戶打開Live2DActivity
    ↓
    Live2DActivity的OpenGL上下文創(chuàng)建
    ↓
    Live2D_v3.init() 在MainActivity中調(diào)用(第一次)
    ↓
    著色器程序在Live2DActivity的上下文中創(chuàng)建
    ↓
    Live2D模型正常顯示
    
    用戶返回Flutter首頁
    ↓
    Live2DPlatformView的OpenGL上下文創(chuàng)建
    ↓
    Live2D_v3.init() 檢測(cè)到已初始化,跳過
    ↓
    Live2DPlatformView嘗試使用Live2D SDK
    ↓
    嘗試使用在Live2DActivity上下文中創(chuàng)建的著色器程序
    ↓
    OpenGL錯(cuò)誤 0x501 (GL_INVALID_VALUE)
    ↓
    渲染失敗,顯示空白
    

解決方案

方案:每個(gè)OpenGL上下文獨(dú)立初始化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?) {
        // 在每個(gè)OpenGL上下文中獨(dú)立初始化Live2D SDK
        Live2D_v3.init(context)
        
        // 創(chuàng)建模型實(shí)例
        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()
            )
    }
}

修復(fù)后的流程

用戶打開Live2DActivity
↓
Live2DActivity的OpenGL上下文創(chuàng)建
↓
MyRenderer.onSurfaceCreated() 調(diào)用
↓
Live2D_v3.init() 第一次初始化
↓
著色器程序在Live2DActivity的上下文中創(chuàng)建
↓
Live2D模型正常顯示

用戶返回Flutter首頁
↓
Live2DPlatformView的OpenGL上下文創(chuàng)建
↓
Live2DRenderer.onSurfaceCreated() 調(diào)用
↓
Live2D_v3.init() 檢測(cè)到已初始化,跳過框架初始化
↓
創(chuàng)建新的LAppModel實(shí)例
↓
CubismRenderer在Live2DPlatformView的上下文中創(chuàng)建新的著色器程序
↓
Live2D模型正常顯示

3. 關(guān)鍵技術(shù)點(diǎn)總結(jié)

3.1 OpenGL上下文隔離

  • 每個(gè)GLSurfaceView創(chuàng)建獨(dú)立的OpenGL上下文
  • OpenGL資源(著色器程序、紋理等)是上下文綁定的
  • 不同上下文之間不能共享OpenGL資源

3.2 Live2D SDK的初始化策略

  • 框架初始化(Csm_CubismFramework.initialize()):只需一次
  • 渲染器初始化(CubismRenderer):每個(gè)OpenGL上下文需要獨(dú)立創(chuàng)建
  • 模型實(shí)例:每個(gè)OpenGL上下文需要獨(dú)立的LAppModel實(shí)例

3.3 Flutter PlatformView的生命周期

  • PlatformView創(chuàng)建時(shí),GLSurfaceView也會(huì)創(chuàng)建
  • GLSurfaceView的onSurfaceCreated在OpenGL上下文創(chuàng)建時(shí)調(diào)用
  • onSurfaceChanged在視圖尺寸變化時(shí)調(diào)用
  • onDrawFrame在每一幀渲染時(shí)調(diào)用

4. 問題排查過程

4.1 初步排查

  1. 檢查Live2DActivity能正常顯示,說明模型文件和Live2D SDK本身沒有問題
  2. 檢查Flutter的Live2DView顯示綠色背景,說明GLSurfaceView正常工作
  3. 日志中出現(xiàn)OpenGL錯(cuò)誤,指向渲染問題

4.2 深入分析

  1. 分析Live2DActivity和Live2DPlatformView的代碼差異
  2. 發(fā)現(xiàn)兩者都使用Live2D_v3.init(),但調(diào)用位置不同
  3. 研究OpenGL上下文的管理機(jī)制
  4. 確認(rèn)著色器程序的上下文綁定特性

4.3 驗(yàn)證假設(shè)

  1. 在Live2DPlatformView的onSurfaceCreated中添加日志
  2. 觀察Live2D_v3.init()的調(diào)用時(shí)機(jī)
  3. 確認(rèn)OpenGL錯(cuò)誤的觸發(fā)時(shí)機(jī)
  4. 驗(yàn)證獨(dú)立初始化方案的有效性

5. 經(jīng)驗(yàn)教訓(xùn)

5.1 OpenGL上下文管理

  • 在Android中使用多個(gè)GLSurfaceView時(shí),要注意上下文隔離
  • OpenGL資源不能跨上下文共享
  • 每個(gè)上下文需要獨(dú)立初始化和清理資源

5.2 Live2D SDK集成

  • Live2D SDK的框架初始化可以全局進(jìn)行
  • 但渲染相關(guān)的資源(著色器程序、紋理等)需要每個(gè)上下文獨(dú)立創(chuàng)建
  • 模型實(shí)例也應(yīng)該每個(gè)上下文獨(dú)立創(chuàng)建

5.3 Flutter PlatformView開發(fā)

  • PlatformView的生命周期與原生View一致
  • 需要正確處理OpenGL上下文的創(chuàng)建和銷毀
  • 避免在PlatformView中使用靜態(tài)的OpenGL資源

6. 最佳實(shí)踐

6.1 初始化模式

// 推薦的初始化模式
class MyRenderer(private val context: Context) : GLSurfaceView.Renderer {
    private lateinit var model: LAppModel

    override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
        // 每個(gè)OpenGL上下文獨(dú)立初始化
        Live2D_v3.init(context)
        
        // 創(chuàng)建獨(dú)立的模型實(shí)例
        model = LAppModel()
        model.loadModelJson("assets://model/xxx.model3.json")
    }

    override fun onSurfaceDestroyed(gl: GL10?) {
        // 清理資源
        // 注意:Live2D_v3.dispose()不應(yīng)該在這里調(diào)用
        // 因?yàn)榭赡苓€有其他OpenGL上下文在使用
    }
}

6.2 資源管理

  • 避免在多個(gè)OpenGL上下文之間共享OpenGL資源
  • 每個(gè)上下文獨(dú)立創(chuàng)建和管理自己的資源
  • 注意資源的生命周期,避免內(nèi)存泄漏

6.3 錯(cuò)誤處理

  • 使用glGetError()檢測(cè)OpenGL錯(cuò)誤
  • 記錄詳細(xì)的日志以便排查問題
  • 理解OpenGL錯(cuò)誤碼的含義(如0x501 = GL_INVALID_VALUE)

7. 總結(jié)

通過這次問題排查和修復(fù),我們深入理解了:

  1. Flutter PlatformView的工作原理
  2. OpenGL上下文的管理機(jī)制
  3. Live2D SDK的正確集成方式
  4. 跨上下文資源共享的限制

核心解決方案是:每個(gè)OpenGL上下文獨(dú)立初始化Live2D相關(guān)的渲染資源,確保著色器程序等OpenGL資源在正確的上下文中創(chuàng)建和使用。

這個(gè)經(jīng)驗(yàn)對(duì)于其他需要集成OpenGL渲染的Flutter項(xiàng)目也具有參考價(jià)值。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容