litert-lm
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseThis skill provides step-by-step guidance for building an Android app that runs
an open text LLM through the LiteRT-LM Kotlin API
() on the CPU or the GPU. The model is one
file. Images, audio, tool calling, and NPU backends aren't covered.
For vision and audio models with files, suggest the
skill.
com.google.ai.edge.litertlm.litertlm.tflitelitert-runtimeWhen a LiteRT-LM class or method that this skill uses doesn't compile, you must
read the LiteRT-LM Android guide and the LiteRT-LM README before
changing the code.
此 skill 提供分步指南,用于构建一个 Android 应用,该应用通过 LiteRT-LM Kotlin API
() 在 CPU 或 GPU 上运行开放文本 LLM。模型是一个
文件。本 skill 不涵盖图像、音频、工具调用和 NPU 后端。
对于使用 文件的视觉和音频模型,建议使用
skill。
com.google.ai.edge.litertlm.litertlm.tflitelitert-runtime当此 skill 使用的 LiteRT-LM 类或方法无法编译时,在更改代码前,必须
阅读 LiteRT-LM Android 指南 和 LiteRT-LM README。
Prerequisites
前置条件
- A Kotlin Android project (the Android Studio Empty Activity template is enough). The 0.18.0 AAR declares
litertlm-android24.minSdk - The dependency in the app-level file from Google Maven:
build.gradle.kts. Google Maven lists the later releases.implementation("com.google.ai.edge.litertlm:litertlm-android:0.18.0") - For the GPU backend, two entries inside
uses-native-libraryin<application>. Add theAndroidManifest.xmlpermission if the app downloads the model.INTERNET - The model file lives on the device's file system (hundreds of MB to a few GB), never in or in the APK.
assets/
- 一个 Kotlin Android 项目(使用 Android Studio 的 Empty Activity 模板即可)。0.18.0 AAR 声明
litertlm-android24。minSdk - 在 app 级别的 文件中添加来自 Google Maven 的依赖:
build.gradle.kts。Google Maven 列出了后续版本。implementation("com.google.ai.edge.litertlm:litertlm-android:0.18.0") - 对于 GPU 后端,需要在 的
AndroidManifest.xml中添加两个<application>条目。如果应用会下载模型,请添加uses-native-library权限。INTERNET - 模型文件存放在设备的文件系统上(数百 MB 到数 GB),绝不能放在 或 APK 中。
assets/
Critical mandates
关键要求
- Dependency first : Before writing code that imports , add the
com.google.ai.edge.litertlmdependency from Step 1 if the project lacks it. Verify withlitertlm-android../gradlew assembleDebug - Background initialization : loads the weights and blocks for seconds. Call it on a background thread, never on the main thread.
Engine(config).initialize() - Single owner : One single-thread executor owns the and the
Engine. Creation, streaming, cancellation, and closing run on it.Conversation - Close once : and
EngineareConversation. A secondAutoCloseableon either throwsclose. So doesIllegalStateExceptionon an engine whoseclosethrew. Store the engine only afterinitializereturns.initialize - Model outside the APK : Pass an absolute path to . Never bundle a
EngineConfigfile in.litertlm.assets/
- 依赖优先:在编写导入 的代码之前,如果项目尚无该依赖,请按步骤 1 添加
com.google.ai.edge.litertlm依赖。使用litertlm-android进行验证。./gradlew assembleDebug - 后台初始化:会加载权重并阻塞数秒。必须在后台线程调用,绝不能在主线程调用。
Engine(config).initialize() - 单一所有者:使用一个单线程 executor 持有 和
Engine。创建、流式传输、取消和关闭操作都在该 executor 上运行。Conversation - 只关闭一次:和
Engine均实现了Conversation。第二次调用任一对象的AutoCloseable都会抛出close。如果引擎的IllegalStateException抛出异常,对其调用initialize也会抛出异常。仅在close成功返回后存储引擎。initialize - 模型置于 APK 之外:向 传入绝对路径。绝不要将
EngineConfig文件打包到.litertlm中。assets/
Detailed steps
详细步骤
Step 1: Configure Gradle and the manifest
第 1 步:配置 Gradle 和 manifest
Search the project for , , and
before editing build files. Configure and add the
LiteRT-LM dependency in :
<br />
libs.versions.tomlapp/build.gradle.ktsAndroidManifest.xmlminSdkapp/build.gradle.ktsandroid {
defaultConfig {
minSdk = 24
}
}
dependencies {
implementation("com.google.ai.edge.litertlm:litertlm-android:0.18.0")
}If the project uses Hilt, check the Dagger version first.
0.18.0 carries Kotlin 2.4 metadata, which Hilt 2.59.2 can't read. The build
then fails in with . Pick one fix:
litertlm-androidhiltJavaCompileDebugProvided Metadata instance has version 2.4.0, while maximum supported version is 2.3.0- Path A : Upgrade the Hilt version () to 2.60.1 or later. Ask the user before editing
com.google.dagger.gradle/libs.versions.toml - Path B : Keep the Hilt version and add a later version of the metadata reader to the annotation processor path in :
app/build.gradle.kts
dependencies {
ksp("org.jetbrains.kotlin:kotlin-metadata-jvm:2.4.21")
}For the GPU backend, add both lines inside in
. Add the permission outside
when the app downloads the model:
<br />
<application>AndroidManifest.xmlINTERNET<application><uses-permission android:name="android.permission.INTERNET"/>
<application>
<uses-native-library
android:name="libvndksupport.so"
android:required="false"/>
<uses-native-library
android:name="libOpenCL.so"
android:required="false"/>
</application>在编辑构建文件之前,先在项目中搜索 、 和
。配置 ,并在 中添加
LiteRT-LM 依赖:
<br />
libs.versions.tomlapp/build.gradle.ktsAndroidManifest.xmlminSdkapp/build.gradle.ktsandroid {
defaultConfig {
minSdk = 24
}
}
dependencies {
implementation("com.google.ai.edge.litertlm:litertlm-android:0.18.0")
}如果项目使用 Hilt,请先检查 Dagger 版本。
0.18.0 包含 Kotlin 2.4 metadata,而 Hilt 2.59.2 无法读取它。随后构建会在
中失败,并出现以下错误:。请选择以下一种修复方式:
litertlm-androidhiltJavaCompileDebugProvided Metadata instance has version 2.4.0, while maximum supported version is 2.3.0- 路径 A:将 Hilt 版本()升级到 2.60.1 或更高版本。编辑
com.google.dagger前先询问用户。gradle/libs.versions.toml - 路径 B:保持 Hilt 版本不变,并在 的注解处理器路径中添加更高版本的 metadata 读取器:
app/build.gradle.kts
dependencies {
ksp("org.jetbrains.kotlin:kotlin-metadata-jvm:2.4.21")
}对于 GPU 后端,在 的 内添加以下两行。
如果应用会下载模型,请在 外添加 权限:
<br />
AndroidManifest.xml<application><application>INTERNET<uses-permission android:name="android.permission.INTERNET"/>
<application>
<uses-native-library
android:name="libvndksupport.so"
android:required="false"/>
<uses-native-library
android:name="libOpenCL.so"
android:required="false"/>
</application>Step 2: Pick a model from litert-community
第 2 步:从 litert-community 选择模型
Choose a file from litert-community on Hugging Face. Start with
(the file ,
2.6 GB). For a 0.6 GB model, start with (the file
). Both files run on the CPU and the GPU. A SoC suffix such
as or marks an NPU build, which this
skill doesn't cover. The model card names the tested backends and the size.
.litertlmlitert-community/gemma-4-E2B-it-litert-lmgemma-4-E2B-it.litertlmlitert-community/Qwen3-0.6BQwen3-0.6B.litertlm_Google_Tensor_G5.mediatek.mt6993You must read get a model before you choose a model file other than these
two, before you write the in-app download code, and when no file
exists for the model.
.litertlm从 Hugging Face 上的 litert-community 选择一个 文件。可以先使用
(文件 ,
2.6 GB)。如果需要 0.6 GB 的模型,可以先使用 (文件
)。这两个文件都可以在 CPU 和 GPU 上运行。 或
等 SoC 后缀表示 NPU 构建,本 skill 不涵盖这种情况。
model card 会列出经过测试的 backend 和模型大小。
.litertlmlitert-community/gemma-4-E2B-it-litert-lmgemma-4-E2B-it.litertlmlitert-community/Qwen3-0.6BQwen3-0.6B.litertlm_Google_Tensor_G5.mediatek.mt6993除上述两个模型外,在选择其他模型文件之前、编写应用内下载代码之前,
以及不存在对应 文件时,必须阅读 获取模型。
.litertlmStep 3: Put the model file on the device
第 3 步:将模型文件放到设备上
Pick one path:
-
Path A (local testing with) : Run the following commands only when all three are true:
adblists a device, the debug build of the app is installed on it, and the user supplied the localadb devicespath. If any of them is false, don't run the commands. Report them to the user as a post-implementation step instead. Replace.litertlmwith the user's local path and<path>with the app's application ID:<package>adb push <path> /data/local/tmp/model.litertlmadb shell run-as <package> cp /data/local/tmp/model.litertlm files/
-
Path B (in-app download) : Write the download code in the app. Use the app's HTTP client to write into, or
filesDirto write intoDownloadManager.getExternalFilesDir(null)can't write to internal storage. Show the download progress and check the file size against the model card before loading. Never download a model file in the agent's own session.DownloadManager -
Path C (Play for On-Device AI) : Write the code that resolves the path of a fast-follow or on-demand AI pack with the AI Delivery library ():
com.google.android.play:ai-delivery. The call returnsAiPackManagerFactory.getInstance(context).getPackLocation(name)?.assetsPath()until the pack has finished downloading, so show a download state in that case. Creating the AI pack module and uploading the app to Play Console are post-implementation steps for the user.null
On every path, pass the absolute path of the file to the engine.
选择一种路径:
-
路径 A(使用进行本地测试):仅当以下三个条件全部成立时,才运行以下命令:
adb列出了设备、应用的 debug build 已安装到设备上,并且用户提供了本地adb devices路径。如果任一条件不成立,则不要运行这些命令;应将其作为实现后步骤告知用户。将.litertlm替换为用户的本地路径,将<path>替换为应用的 application ID:<package>adb push <path> /data/local/tmp/model.litertlmadb shell run-as <package> cp /data/local/tmp/model.litertlm files/
-
路径 B(应用内下载):在应用中编写下载代码。使用应用的 HTTP 客户端写入,或使用
filesDir写入DownloadManager。getExternalFilesDir(null)无法写入内部存储。显示下载进度,并在加载前根据 model card 检查文件大小。绝不要在 Agent 自身的会话中下载模型文件。DownloadManager -
路径 C(Play for On-Device AI):编写代码,使用 AI Delivery library()解析 fast-follow 或按需 AI pack 的路径:
com.google.android.play:ai-delivery。 在 pack 下载完成之前,该调用会返回AiPackManagerFactory.getInstance(context).getPackLocation(name)?.assetsPath(),因此此时应显示下载状态。创建 AI pack module 和将应用上传到 Play Console 是用户需要完成的后续步骤。null
无论采用哪种路径,都要向引擎传入文件的绝对路径。
Step 4: Initialize the engine off the main thread
第 4 步:在主线程之外初始化引擎
Create the engine with and call on a background thread:
EngineConfig(modelPath = ..., backend = ..., cacheDir = ...)initialize- is the default of
Backend.CPU()and needs nothing from the device.EngineConfig - needs the two manifest lines from Step 1. Without them, the engine initializes, and the first reply ends in the error
Backend.GPU().Can not find OpenCL library on this device - speeds up the second load.
cacheDir
使用 创建引擎,并在线程中调用 :
EngineConfig(modelPath = ..., backend = ..., cacheDir = ...)initialize- 是
Backend.CPU()的默认 backend,不需要对设备进行任何额外配置。EngineConfig - 需要第 1 步中的两个 manifest 配置项。如果没有这些配置,引擎仍会初始化,但首次回复最终会报错
Backend.GPU()。Can not find OpenCL library on this device - 可以加快第二次加载速度。
cacheDir
Step 5: Implement the conversation ViewModel
第 5 步:实现会话 ViewModel
Drive the engine and the conversation from a single-thread executor inside an
, stream the reply, and expose immutable state to the UI:
<br />
AndroidViewModelimport android.app.Application
import androidx.lifecycle.AndroidViewModel
import com.google.ai.edge.litertlm.Backend
import com.google.ai.edge.litertlm.Contents
import com.google.ai.edge.litertlm.Conversation
import com.google.ai.edge.litertlm.ConversationConfig
import com.google.ai.edge.litertlm.Engine
import com.google.ai.edge.litertlm.EngineConfig
import java.util.concurrent.Executors
import kotlin.coroutines.cancellation.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
data class ChatState(
val ready: Boolean = false,
val busy: Boolean = false,
val reply: String = "",
val error: String? = null
)
class ChatViewModel(app: Application) : AndroidViewModel(app) {
private val executor = Executors.newSingleThreadExecutor()
private val scope = CoroutineScope(
SupervisorJob() + executor.asCoroutineDispatcher()
)
private val mutex = Mutex()
private var engine: Engine? = null
private var conversation: Conversation? = null
private val _state = MutableStateFlow(ChatState())
val state: StateFlow<ChatState> = _state.asStateFlow()
fun load(modelPath: String, backend: Backend = Backend.CPU()) {
scope.launch {
mutex.withLock {
if (engine?.engineConfig?.modelPath == modelPath) return@launch
release()
try {
val config = EngineConfig(
modelPath = modelPath,
backend = backend,
cacheDir = getApplication<Application>().cacheDir.path
)
engine = Engine(config).also { it.initialize() }
conversation = checkNotNull(engine).createConversation(
ConversationConfig(
systemInstruction =
Contents.of("You are a helpful assistant.")
)
)
_state.value = ChatState(ready = true)
} catch (e: Exception) {
release()
_state.value = ChatState(error = e.message)
}
}
}
}
fun send(text: String) {
scope.launch {
if (!_state.value.ready || _state.value.busy) return@launch
_state.update { it.copy(busy = true, reply = "", error = null) }
try {
val replies = checkNotNull(conversation).sendMessageAsync(text)
replies.collect { message ->
_state.update { it.copy(reply = it.reply + message) }
}
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
_state.update { it.copy(error = e.message) }
} finally {
_state.update { it.copy(busy = false) }
}
}
}
private suspend fun release() {
_state.update { it.copy(ready = false) }
while (_state.value.busy) {
conversation?.cancelProcess()
delay(100)
}
conversation?.close()
conversation = null
engine?.close()
engine = null
_state.value = ChatState()
}
override fun onCleared() {
scope.launch {
mutex.withLock { release() }
executor.shutdown()
}.invokeOnCompletion { scope.cancel() }
}
}sendMessageAsync(text)Flow<Message>toStringsendMessage(text)sendbusyTo set the sampling, pass in . All three parameters are required,
and and are .
SamplerConfig(topK = 10, topP = 0.95, temperature = 0.8)ConversationConfigtopPtemperatureDoubleloadsendreadybusyreleaseloadload在 内使用单线程 executor 驱动引擎和会话,流式接收回复,并向 UI 暴露不可变状态:
<br />
AndroidViewModelimport android.app.Application
import androidx.lifecycle.AndroidViewModel
import com.google.ai.edge.litertlm.Backend
import com.google.ai.edge.litertlm.Contents
import com.google.ai.edge.litertlm.Conversation
import com.google.ai.edge.litertlm.ConversationConfig
import com.google.ai.edge.litertlm.Engine
import com.google.ai.edge.litertlm.EngineConfig
import java.util.concurrent.Executors
import kotlin.coroutines.cancellation.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
data class ChatState(
val ready: Boolean = false,
val busy: Boolean = false,
val reply: String = "",
val error: String? = null
)
class ChatViewModel(app: Application) : AndroidViewModel(app) {
private val executor = Executors.newSingleThreadExecutor()
private val scope = CoroutineScope(
SupervisorJob() + executor.asCoroutineDispatcher()
)
private val mutex = Mutex()
private var engine: Engine? = null
private var conversation: Conversation? = null
private val _state = MutableStateFlow(ChatState())
val state: StateFlow<ChatState> = _state.asStateFlow()
fun load(modelPath: String, backend: Backend = Backend.CPU()) {
scope.launch {
mutex.withLock {
if (engine?.engineConfig?.modelPath == modelPath) return@launch
release()
try {
val config = EngineConfig(
modelPath = modelPath,
backend = backend,
cacheDir = getApplication<Application>().cacheDir.path
)
engine = Engine(config).also { it.initialize() }
conversation = checkNotNull(engine).createConversation(
ConversationConfig(
systemInstruction =
Contents.of("You are a helpful assistant.")
)
)
_state.value = ChatState(ready = true)
} catch (e: Exception) {
release()
_state.value = ChatState(error = e.message)
}
}
}
}
fun send(text: String) {
scope.launch {
if (!_state.value.ready || _state.value.busy) return@launch
_state.update { it.copy(busy = true, reply = "", error = null) }
try {
val replies = checkNotNull(conversation).sendMessageAsync(text)
replies.collect { message ->
_state.update { it.copy(reply = it.reply + message) }
}
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
_state.update { it.copy(error = e.message) }
} finally {
_state.update { it.copy(busy = false) }
}
}
}
private suspend fun release() {
_state.update { it.copy(ready = false) }
while (_state.value.busy) {
conversation?.cancelProcess()
delay(100)
}
conversation?.close()
conversation = null
engine?.close()
engine = null
_state.value = ChatState()
}
override fun onCleared() {
scope.launch {
mutex.withLock { release() }
executor.shutdown()
}.invokeOnCompletion { scope.cancel() }
}
}sendMessageAsync(text)Flow<Message>toStringsendMessage(text)sendbusy要设置采样参数,请在 中传入 。三个参数均为必填项,
其中 和 的类型为 。
ConversationConfigSamplerConfig(topK = 10, topP = 0.95, temperature = 0.8)topPtemperatureDouble使用已加载的模型路径调用 时不会执行任何操作,无论请求的是哪个
backend。屏幕旋转后的第二次调用会保留已加载的模型,应用回退到其他 backend 后也是如此。
仅在状态为 且非 时运行,与按钮的启用条件相同。 停止回复期间发生的点击不会执行任何操作。
当没有回复正在流式传输时,排在 之后的点击会访问 所加载的模型。
loadsendreadybusyreleaseloadloadStep 6: Wire the Compose screen
第 6 步:连接 Compose 屏幕
viewModelcollectAsStateWithLifecycleandroidx.lifecycle:lifecycle-viewmodel-composeandroidx.lifecycle:lifecycle-runtime-composeimport androidx.compose.foundation.layout.Column
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.ai.edge.litertlm.Backend
@Composable
fun ChatScreen(modelPath: String, viewModel: ChatViewModel = viewModel()) {
val state by viewModel.state.collectAsStateWithLifecycle()
var input by remember { mutableStateOf("") }
LaunchedEffect(modelPath) { viewModel.load(modelPath, Backend.GPU()) }
Column {
Text(
state.reply,
Modifier.weight(1f).verticalScroll(rememberScrollState())
)
state.error?.let { Text(it) }
TextField(value = input, onValueChange = { input = it })
Button(
onClick = { viewModel.send(input); input = "" },
enabled = state.ready && !state.busy
) {
Text("Send")
}
}
}viewModelcollectAsStateWithLifecycleandroidx.lifecycle:lifecycle-viewmodel-composeandroidx.lifecycle:lifecycle-runtime-composeimport androidx.compose.foundation.layout.Column
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.ai.edge.litertlm.Backend
@Composable
fun ChatScreen(modelPath: String, viewModel: ChatViewModel = viewModel()) {
val state by viewModel.state.collectAsStateWithLifecycle()
var input by remember { mutableStateOf("") }
LaunchedEffect(modelPath) { viewModel.load(modelPath, Backend.GPU()) }
Column {
Text(
state.reply,
Modifier.weight(1f).verticalScroll(rememberScrollState())
)
state.error?.let { Text(it) }
TextField(value = input, onValueChange = { input = it })
Button(
onClick = { viewModel.send(input); input = "" },
enabled = state.ready && !state.busy
) {
Text("Send")
}
}
}Step 7: Manage the engine lifecycle
第 7 步:管理引擎生命周期
- Keep the engine in the for as long as its screen lives. When several screens share the engine, keep it in an application-scoped holder.
ViewModel - The keeps its own thread and scope.
ViewModelis cancelled beforeviewModelScoperuns, so a close launched there wouldn't run.onClearedcancelsonClearedonce the close is done.scope - turns
releaseoff and callsreadyuntil the streaming reply's flow has ended. The flow ends at the latest when the reply ends on its own.cancelProcessthen closes the conversation and the engine, each once.release - and a
onClearedthat replaces the model both callloadunder therelease, one at a time, becauseMutexsuspends while a reply stops.release - To start a fresh chat, close the conversation and create a new one with . Keep the engine loaded.
createConversation
- 只要屏幕仍然存在,就在 中保留引擎。当多个屏幕共享引擎时,将其保存在 application 作用域的 holder 中。
ViewModel - 拥有自己的线程和 scope。
ViewModel会在viewModelScope运行之前被取消,因此在那里启动的 close 操作不会执行。onCleared会在 close 完成后取消onCleared。scope - 会关闭
release状态,并调用ready,直到流式回复的 Flow 结束。Flow 最迟会在回复自行结束时结束。随后,cancelProcess会分别关闭 conversation 和 engine,且每个对象只关闭一次。release - 和替换模型的
onCleared都会在load下调用Mutex,并且一次只执行一个,因为release在停止回复期间会挂起。release - 要开始一段全新对话,请关闭 conversation,并使用 创建一个新 conversation,同时保持引擎处于加载状态。
createConversation
Post-implementation steps for the user
面向用户的实现后步骤
Report the steps that apply to the user after the code compiles:
- When Path A in Step 3 didn't run, copy the model to the device with the two commands from Path A.
adb - When the app uses Path C in Step 3, create the AI pack module that holds the file and upload the app to Play Console.
.litertlm - When no file exists for the model, convert the model on a workstation. This skill doesn't convert models.
.litertlm - When the user asks for a complete reference app, point them to Google AI Edge Gallery.
代码编译完成后,向用户报告适用于其情况的步骤:
- 如果第 3 步的路径 A 未执行,请使用路径 A 中的两条 命令将模型复制到设备。
adb - 如果应用使用第 3 步的路径 C,请创建用于保存 文件的 AI pack module,并将应用上传到 Play Console。
.litertlm - 如果模型不存在对应的 文件,请在工作站上进行转换。本 skill 不负责转换模型。
.litertlm - 如果用户需要完整的参考应用,请引导其访问 Google AI Edge Gallery。
Troubleshooting
故障排除
| Symptom | Cause | Fix |
|---|---|---|
| First reply is slow. | | Load at app start and set |
First GPU reply ends in | The manifest lacks the | Add both entries from Step 1 inside |
| The engine or conversation was closed twice, or | Store the engine only after |
Build fails with | Hilt 2.59.2 can't read the Kotlin 2.4 metadata in | Follow Path A or Path B in Step 1. |
| 症状 | 原因 | 修复方案 |
|---|---|---|
| 首次回复较慢。 | | 应用启动时加载模型并设置 |
首次 GPU 回复以 | manifest 缺少 | 在 |
| engine 或 conversation 被关闭了两次,或者 | 仅在 |
构建因 | Hilt 2.59.2 无法读取 | 按第 1 步的路径 A 或路径 B 处理。 |
Checklist
检查清单
- [ ] Is set to
minSdkor higher and is24added incom.google.ai.edge.litertlm:litertlm-android:0.18.0?app/build.gradle.kts - [ ] For the GPU backend, does declare
AndroidManifest.xmlandlibvndksupport.sowithlibOpenCL.soinsideandroid:required="false"?<application> - [ ] Is the file loaded from an absolute path on the device's file system, never from
.litertlm?assets/ - [ ] Do ,
initialize,sendMessageAsync, andcancelProcessall run on the same single-thread background executor?close - [ ] Is the conversation closed before the engine, each exactly once?
- [ ] Does the project compile with ?
./gradlew assembleDebug
- [ ] 是否已将 设置为
minSdk或更高,并在24中添加了app/build.gradle.kts?com.google.ai.edge.litertlm:litertlm-android:0.18.0 - [ ] 对于 GPU backend,是否在
AndroidManifest.xml内声明了<application>和libvndksupport.so,并设置libOpenCL.so?android:required="false" - [ ] 是否通过设备文件系统中的绝对路径加载 文件,而没有从
.litertlm加载?assets/ - [ ] 、
initialize、sendMessageAsync和cancelProcess是否都在同一个单线程后台 executor 上运行?close - [ ] 是否先关闭 conversation、再关闭 engine,并且每个对象恰好关闭一次?
- [ ] 项目能否通过 编译?
./gradlew assembleDebug
Antipatterns
反模式
- Never call on the Android main thread.
Engine.initialize - Never place a file in
.litertlmor ship it inside the APK.assets/ - Never call on an engine whose
closethrew.initialize - Never close the engine while a reply is streaming; call until the flow ends first.
cancelProcess - Never close or
EnginefromConversationinviewModelScope; that scope is already cancelled.onCleared
- 绝不要在 Android 主线程调用 。
Engine.initialize - 绝不要将 文件放在
.litertlm中或打包到 APK 内。assets/ - 绝不要对 抛出异常的 engine 调用
initialize。close - 绝不要在回复仍在流式传输时关闭 engine;应先调用 ,直到 Flow 结束。
cancelProcess - 绝不要在 中通过
onCleared关闭viewModelScope或Engine;该 scope 已经被取消。Conversation