MiniCPM-V 多模态模型 Android APP 集成指南本教程主要讲述的是如何将 MiniCPM-V 系列多模态模型图文/视频理解集成到你自己的 Android App原始项目地址https://github.com/OpenBMB/MiniCPM-V-Apps1. 前置条件为了避免出现其他兼容性问题建议与原始项目MiniCPM-V-demo-Android中的版本保持一致项目要求Android Studio2024.1支持 AGP 8.9AGP8.9.1Gradle8.11.1NDK27.0.12077973CMake3.22.1Kotlin2.0.21minSdk24目标 ABIarm64-v8a仅支持 64 位 ARM磁盘空间≥ 10GBllama.cpp 编译 模型文件Java112. 文件清单必须复制Kotlin层LlamaEngine.kt引擎单例封装全部 JNI 调用ModelInfo.kt模型元数据下载源、文件名、MD5校验等ModelDownloadService.kt模型下载服务CpuFeatures.ktCPU 特性检测选优化的 .soJNI层CMakeLists.txtCMake 配置llama_jni.cppJNI 桥接logging.h日志可选文件MarkdownEscape.ktV4.6 输出的\n转真换行显示优化VideoFrameExtractor.kt视频理解功能只有MiniCPM-V 4.6模型支持3. 步骤一搭建 Native 层3.1 添加 llama.cpp 子模块在项目根目录cd/path/to/your/projectgitsubmoduleadd-bSupport-iOS-Demo https://github.com/tc-mb/llama.cpp.git llama.cppcdllama.cpp⚠️重要必须使用Support-iOS-Demo分支它包含 MiniCPM-V 的 clip.cpp 兼容代码主线分支的 mmproj 转换格式不兼容。3.2 复制 C 文件把llama_jni.cpp、logging.h、CMakeLists.txt复制到你项目的app/src/main/cpp/下。3.3 修改 CMakeLists.txt 中的 llama.cpp 路径打开CMakeLists.txt修改第 38-39 行的LLAMA_SRC路径# 默认在项目4级目录上找 llama.cpp if(NOT DEFINED LLAMA_SRC) set(LLAMA_SRC ${CMAKE_CURRENT_LIST_DIR}/../../../../../llama.cpp) endif() # 如果你的llama.cpp 在项目根目录下改成 if(NOT DEFINED LLAMA_SRC) set(LLAMA_SRC ${CMAKE_CURRENT_LIST_DIR}/../../../../llama.cpp) endif()路径关系app/src/main/cpp/→ 上 4 级到项目根 →llama.cpp/3.4 修改 JNI 函数名llama_jni.cpp中的 JNI 函数名基于包名com.example.minicpm_v_demo格式为Java_包名_类名_方法名. → __ → _1当前函数名示例Java_com_example_minicpm_1v_1demo_LlamaEngine_load如果你的包名是com.yourapp.ai需要把所有 JNI 函数前缀改为Java_com_yourapp_ai_LlamaEngine_load完整替换列表在llama_jni.cpp中搜索替换原始前缀新前缀Java_com_example_minicpm_1v_1demo_LlamaEngine_Java_com_yourapp_ai_LlamaEngine_4. 步骤二配置 Gradle 构建4.1gradle/libs.versions.toml[versions] agp 8.9.0 kotlin 2.0.21 coreKtx 1.10.1 appcompat 1.6.1 material 1.10.0 constraintlayout 2.1.4 lifecycleRuntimeKtx 2.6.2 activityKtx 1.8.0 [libraries] androidx-core-ktx { group androidx.core, name core-ktx, version.ref coreKtx } androidx-appcompat { group androidx.appcompat, name appcompat, version.ref appcompat } material { group com.google.android.material, name material, version.ref material } androidx-constraintlayout { group androidx.constraintlayout, name constraintlayout, version.ref constraintlayout } androidx-lifecycle-runtime-ktx { group androidx.lifecycle, name lifecycle-runtime-ktx, version.ref lifecycleRuntimeKtx } androidx-activity-ktx { group androidx.activity, name activity-ktx, version.ref activityKtx } [plugins] android-application { id com.android.application, version.ref agp } kotlin-android { id org.jetbrains.kotlin.android, version.ref kotlin }4.2gradle/wrapper/gradle-wrapper.propertiesdistributionUrlhttps\://services.gradle.org/distributions/gradle-8.11.1-bin.zip4.3app/build.gradle.ktsplugins{alias(libs.plugins.android.application)alias(libs.plugins.kotlin.android)alias(libs.plugins.kotlin.compose)}android{namespacecom.xxx.xxx//改为自己的项目包路径compileSdk36ndkVersion27.0.12077973defaultConfig{applicationIdcom.xxx.xxxminSdk24targetSdk36versionCode1versionName1.0testInstrumentationRunnerandroidx.test.runner.AndroidJUnitRunnerndk{abiFilters.add(arm64-v8a)// 只构建 ARM64}//cmake相关的配置externalNativeBuild{cmake{arguments-DCMAKE_BUILD_TYPEReleasearguments-DBUILD_SHARED_LIBSONarguments-DLLAMA_BUILD_COMMONONarguments-DLLAMA_OPENSSLOFFarguments-DGGML_NATIVEOFFarguments-DGGML_LLAMAFILEONarguments-DGGML_CPU_ARM_ARCHarmv8.2-adotprodfp16}}}buildTypes{release{isMinifyEnabledfalseproguardFiles(getDefaultProguardFile(proguard-android-optimize.txt),proguard-rules.pro)}}compileOptions{sourceCompatibilityJavaVersion.VERSION_11 targetCompatibilityJavaVersion.VERSION_11}kotlinOptions{jvmTarget11}externalNativeBuild{cmake{pathfile(src/main/cpp/CMakeLists.txt)version3.22.1}}buildFeatures{composetrue}}dependencies{implementation(libs.androidx.core.ktx)implementation(libs.androidx.lifecycle.runtime.ktx)implementation(libs.androidx.activity.compose)implementation(platform(libs.androidx.compose.bom))implementation(libs.androidx.ui)implementation(libs.androidx.ui.graphics)implementation(libs.androidx.ui.tooling.preview)implementation(libs.androidx.material3)implementation(libs.androidx.material.icons.extended)testImplementation(libs.junit)androidTestImplementation(libs.androidx.junit)androidTestImplementation(libs.androidx.espresso.core)androidTestImplementation(platform(libs.androidx.compose.bom))androidTestImplementation(libs.androidx.ui.test.junit4)debugImplementation(libs.androidx.ui.tooling)debugImplementation(libs.androidx.ui.test.manifest)}5. 步骤三复制 Kotlin 核心文件5.1 必须文件把以上提到的必须复制文件复制到你的包名目录下并修改package声明。5.2 LlamaEngine.kt 的 JNI 声明如果你的包名改变了需要同步修改LlamaEngine.kt中的external函数声明。Kotlin 的external函数名必须和 C 端一致不改 Kotlin 声明改 C 端的函数名来匹配。6. 步骤四配置 AndroidManifestmanifest...!-- 网络权限下载模型必须 --uses-permissionandroid:nameandroid.permission.INTERNET/uses-permissionandroid:nameandroid.permission.ACCESS_NETWORK_STATE/!-- 以下权限仅在集成 ModelDownloadService 时需要 --uses-permissionandroid:nameandroid.permission.FOREGROUND_SERVICE/uses-permissionandroid:nameandroid.permission.FOREGROUND_SERVICE_DATA_SYNC/uses-permissionandroid:nameandroid.permission.WAKE_LOCK/uses-permissionandroid:nameandroid.permission.POST_NOTIFICATIONS/applicationandroid:networkSecurityConfigxml/network_security_config...!-- 如果使用 ModelDownloadService --serviceandroid:name.ModelDownloadServiceandroid:exportedfalseandroid:foregroundServiceTypedataSync//application/manifestnetwork_security_config.xml?xml version1.0 encodingutf-8?network-security-configbase-configcleartextTrafficPermittedtrue//network-security-config7. 步骤五模型文件管理7.1 模型存储结构/data/data/com.yourapp.ai/files/models/ ├── minicpm-v-4/ # ModelInfo.id │ ├── ggml-model-Q4_K_M.gguf # LLM 主模型 │ └── mmproj-model-f16.gguf # 视觉投影模型 └── minicpm-v-4_6-instruct/ # 另一个模型 ├── MiniCPM-V-4_6-Q4_K_M.gguf └── mmproj-model-merger-f16.gguf7.2 下载源模型大小下载策略MiniCPM-V-4 (Q4_K_M)~4.1BHuggingFace → ModelScope 回退MiniCPM-V-4.6 (Q4_K_M)~1.2B华为云 OBS 直链7.3 下载方式方式 AApp 内下载推荐集成ModelDownloadService.kt用户点击下载按钮即可支持断点续传和 MD5 校验。//启动下载ModelDownloadService.start(context)//下载状态lifecycleScope.launch{ModelDownloadController.status.collect{status-when(status){isModelDownloadController.Status.Running-showProgress(status.message)isModelDownloadController.Status.Completed-onDownloadComplete()isModelDownloadController.Status.Failed-showError(status.message)else-{}}}}方式 Badb 侧载#推送模型文件到设备 测试时可以这样adb push ggml-model-Q4_K_M.gguf /data/data/com.yourapp.ai/files/models/minicpm-v-4/ adb push mmproj-model-f16.gguf /data/data/com.yourapp.ai/files/models/minicpm-v-4/8. 步骤六在 App 中使用// 初始化引擎 条件加载模型 这里建议放到MainActivity的onCreate中 在启动的时候自动加载模型valengineLlamaEngine.getInstance(this)if(LlamaEngine.modelsExist(this)){lifecycleScope.launch{engine.state.collect{state-if(stateisLlamaState.Initialized){Log.i(ModelLoad,模型文件已就绪开始加载...)try{engine.loadModel(LlamaEngine.modelPath(thisMainActivity),LlamaEngine.mmprojPath(thisMainActivity))Log.i(ModelLoad,模型加载成功)}catch(e:Exception){Log.e(ModelLoad,模型加载失败,e)}returncollect// 只触发一次}}}}9. API 表LlamaEngine 公开 API方法简要说明getInstance(context)获取单例loadModel(path, mmprojPath?)加载模型视觉模型setSystemPrompt(prompt)设置系统提示词sendUserPrompt(msg, predictLen)发送用户消息prefillImage(imageData)预填充图片prefillVideoFrames(frames, onProgress)预填充视频帧clearContext()清空上下文unloadModel()卸载模型cancelGeneration()取消当前生成destroy()销毁引擎setImageMaxSliceNums(n)设置图片切片数state引擎状态流isVisionSupported是否支持图片isVideoUnderstandingSupported是否支持视频LlamaEngine 静态 API方法简要说明getSelectedModel(context)获取当前选择的模型setSelectedModel(context, id)选择模型modelPath(context)GGUF 文件路径mmprojPath(context)mmproj 文件路径modelsExist(context)模型文件是否都在modelDir(context)模型根目录modelDirFor(context, model)某模型目录getImageMaxSliceNums(context)图片切片数setImageMaxSliceNumsPref(context, n)持久化切片数downloadModels(context, onProgress)下载模型文件migrateLegacyLayoutIfNeeded(context)迁移旧文件布局9.1 自定义推理参数在llama_jni.cpp中修改constexprintN_THREADS4;// 推理 线程数影响推理速度 亲测 这里可以稍微调大constexprintDEFAULT_CONTEXT_SIZE4096;// KV cache 大小constexprintV46_CONTEXT_SIZE8192;// V4.6 的 KV cacheconstexprintBATCH_SIZE2048;// 批处理大小 这里也可以稍微调大一些constexprfloatDEFAULT_SAMPLER_TEMP0.7f;// 温度