初探多模态AI:用 Jetpack Compose 和 Gemini API 打造智能识图应用
引言——AI 助力,移动应用新体验
1.1 传统移动开发的局限与机遇
在今天的移动应用中,我们常常需要处理各种信息。文字、图片、视频、音频……用户希望应用能像人一样“看”懂图片,“听”懂语音。但传统的移动应用在理解这些非结构化数据方面往往力不从心。我们常常需要自己编写复杂的逻辑来识别图像中的物体,或者从大量文字中提取关键信息。这不仅耗时,而且效果有限。
而现在,多模态 AI 的出现为我们带来了新的机遇。它能像人类一样,同时处理和理解不同类型的信息(如图片和文字),让应用变得更加智能和交互。
1.2 为什么选择 Gemini API 和 Jetpack Compose?
- Gemini API:这是 Google 推出的一系列多模态模型,它能够同时接收图片、文本甚至视频作为输入,并生成智能的回答。想象一下,你的应用不再是简单地展示图片,而是能“看”懂图片,并根据用户的提问给出专业的解释。Gemini API 提供了强大的多模态理解能力,并且在 Android 端有方便集成的 SDK。
- Jetpack Compose:这是 Android 官方推荐的现代化 UI 工具包。它采用声明式编程范式,让 UI 开发变得更加直观和高效。对于我们这样处理 AI 响应的应用来说,Compose 的响应式特性可以让我们轻松地根据 AI 返回的结果实时更新界面,大大简化了 UI 状态管理。
Gemini API 强大的智能理解能力与 Jetpack Compose 灵活高效的 UI 构建方式相结合,为我们开发新一代智能移动应用提供了完美的平台!
1.3 本文目标:打造一个简单的“智能识图助手”
本文将带你实现一个有趣的 “智能识图助手” 应用。用户可以上传一张图片,然后输入一个问题(比如:“这张图里有什么?”或者“这是什么动物?”),应用会调用 Gemini API 来理解图片和问题,并返回智能的回答。这个应用将让你亲身体验多模态 AI 的魅力,并掌握 Jetpack Compose 与 Gemini API 集成的基本流程。
技术基石——准备好你的开发环境
2.1 Gemini API 核心能力速览
Gemini 模型家族包含多种规模,例如 Gemini 2.5 Pro 适用于处理大量信息和复杂任务,而 Gemini 2.5 Flash 则更轻量高效,适合在设备端或对延迟敏感的场景。对于我们的智能识图应用,我们将主要关注其 多模态输入处理 (Multimodal Input) 能力,即同时接收图片和文字输入。
2.2 Jetpack Compose 基础概念
- 声明式 UI:你只需要描述 UI 在不同状态下应该是什么样子,而不是手动操作 UI 元素。
- Composable 函数:构建 UI 的基本单元,用
@Composable注解标记的函数。 - 状态管理 (State):Compose 应用中的数据变化,使用
remember和mutableStateOf来管理 UI 状态,并让 UI 响应式地更新。 - ViewModel:作为 UI 层的状态持有者,帮助我们隔离业务逻辑和网络请求,让 Composable 保持“傻瓜式”,更易于测试和维护。
核心实践——构建你的智能识图助手
接下来实操一波。我们将构建一个界面,让用户可以:
- 选择一张图片。
- 输入一个问题。
- 点击按钮发送图片和问题给 Gemini API。
- 显示 Gemini API 返回的回答。
3.1 环境准备与 Gemini API 交互核心逻辑
这是整个应用的核心部分。我们将配置 Gemini API Key,添加必要的依赖,并编写 ViewModel 来处理与 Gemini API 的所有交互逻辑,包括图片和文本的格式转换,以及请求的发送与响应的处理。
3.1.1 配置 Gemini API Key
- 为了安全地使用 Gemini API,你的 API Key 不应该直接硬编码在代码中。推荐的做法是将其存储在项目的
local.properties文件中,并通过 Gradle 构建系统注入到BuildConfig类中。
在项目根目录下的local.properties文件中添加你的 Gemini API Key。 将YOUR_KEY_HERE替换为你在 Google AI Studio 获取到的实际 API Key。
# local.properties
GEMINI_API_KEY=YOUR_KEY_HERE
- 修改
build.gradle.kts (Module: app)以读取local.properties中的 Key 并注入到BuildConfig。 这确保你的应用在编译时能访问到 API Key,同时避免了将其直接暴露在版本控制中。请确保将localProperties变量定义在android块之前,以保证其在android块内部被正确引用。
// app/build.gradle.kts
import java.util.Properties
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
}
val localProperties = Properties().apply {
val localPropertiesFile = rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
localPropertiesFile.inputStream().use { load(it) }
}
}
android {
namespace = "com.tovl.smartimageassistant"
compileSdk = 36
defaultConfig {
applicationId = "com.tovl.smartimageassistant"
minSdk = 24
targetSdk = 36
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
// 【在 android {} 块内部, buildTypes {} 块之后,统一为所有构建类型设置 GEMINI_API_KEY】
val geminiApiKey = localProperties.getProperty("GEMINI_API_KEY")
if (geminiApiKey != null && geminiApiKey.isNotBlank()) {
buildTypes.all {
buildConfigField("String", "GEMINI_API_KEY", "\"$geminiApiKey\"")
}
} else {
buildTypes.all {
buildConfigField("String", "GEMINI_API_KEY", "\"\"")
}
// 警告信息...
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
buildFeatures {
compose = true
buildConfig = true // 【确保此行存在,用于生成 BuildConfig 类】
}
}
dependencies {
implementation(platform(libs.androidx.compose.bom))
// ...
// Jetpack Compose 基础
implementation(platform("androidx.compose:compose-bom:2025.12.00"))
implementation("androidx.compose.material:material-icons-core")
implementation("androidx.compose.material:material-icons-extended")
// Gemini API 客户端库
implementation("com.google.ai.client.generativeai:generativeai:0.9.0")
// Coil for image loading (用于加载选中的图片)
implementation("io.coil-kt:coil-compose:2.7.0")
// ViewModel 和 LiveData/StateFlow 整合
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.10.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.10.0")
implementation("androidx.activity:activity-compose:1.12.1")
}
3.1.2 添加必要的 Android 权限
为了让应用能够访问网络(与 Gemini API 通信)和读取设备存储中的图片,我们需要在 AndroidManifest.xml 中声明相应的权限。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.tovl.smartimageassistant">
<!-- 访问网络的权限,用于调用 Gemini API -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 从相册选择图片所需的权限,maxSdkVersion 32 适用于旧版本Android -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<!-- Android 13 (API 33) 及以上读取媒体图片所需的权限 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.SmartImageAssistant">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.SmartImageAssistant">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
3.1.3 构建 SmartImageViewModel 处理业务逻辑
SmartImageViewModel 将负责管理应用的状态(如选定的图片、用户输入的问题、Gemini API 的响应、加载状态),并封装与 Gemini API 交互的逻辑。
// SmartImageViewModel.kt
import com.google.ai.client.generativeai.type.content // 导入 content 构建器
// ...
import com.tovl.smartimageassistant.BuildConfig // 导入 BuildConfig 以访问 API Key
class SmartImageViewModel : ViewModel() {
// 保存用户选择的图片 Uri
private val _selectedImageUri = MutableStateFlow<Uri?>(null)
val selectedImageUri: StateFlow<Uri?> = _selectedImageUri.asStateFlow()
// 保存用户输入的问题文本
private val _questionText = MutableStateFlow("")
val questionText: StateFlow<String> = _questionText.asStateFlow()
// 保存 Gemini API 的响应文本
private val _responseText = MutableStateFlow("等待你的提问...")
val responseText: StateFlow<String> = _responseText.asStateFlow()
// 表示当前是否正在加载/等待 API 响应
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
// Gemini AI 模型实例,现在是可空的,因为初始化可能失败
private var generativeModel: GenerativeModel? = null
init {
// 在 ViewModel 初始化时尝试创建 GenerativeModel 实例
val apiKey = BuildConfig.GEMINI_API_KEY // 访问 BuildConfig 中的 API Key
if (apiKey.isBlank()) {
_responseText.value = "错误:Gemini API Key 未配置!请检查 local.properties。"
// 如果 API Key 缺失,generativeModel 保持为 null
} else {
generativeModel = GenerativeModel(
modelName = "gemini-2.5-flash", // 【模型选择】使用支持视觉输入的多模态模型。这里我选择 gemini-2.5-flash.
apiKey = apiKey
)
}
}
/**
* 更新选中的图片 Uri。
* @param uri 新的图片 Uri,如果为 null 则表示没有选择图片。
*/
fun setSelectedImageUri(uri: Uri?) {
_selectedImageUri.value = uri
// 根据是否有图片选择,更新响应文本提示
_responseText.value = if (uri != null) "图片已选择,请输入问题。" else "请选择图片并输入问题!"
}
/**
* 更新用户输入的问题文本。
* @param text 用户输入的问题字符串。
*/
fun setQuestionText(text: String) {
_questionText.value = text
}
/**
* 发送图片和问题给 Gemini API。
* @param context 当前应用的 Context,用于 Uri 到 Bitmap 的转换。
*/
fun sendQuery(context: Context) {
// 确保 generativeModel 已初始化
val currentModel = generativeModel
if (currentModel == null) {
_responseText.value = "错误:Gemini API Key 未配置,无法发送请求。"
_isLoading.value = false // 确保即使出错也要停止加载状态
return
}
// 确保有图片和问题才能发送请求
if (_selectedImageUri.value == null || _questionText.value.isBlank()) {
_responseText.value = "请选择图片并输入问题!"
return
}
viewModelScope.launch { // 在 ViewModel 的作用域内启动协程
_isLoading.value = true // 设置加载状态为 true
_responseText.value = "思考中..." // 显示加载状态提示
try {
// 1. 将 Uri 转换为 Bitmap
val bitmap = uriToBitmap(context, _selectedImageUri.value!!)
?: throw IllegalStateException("无法加载图片,请尝试其他图片。")
// 2. 构建 Gemini API 所需的 Content 对象
// Content 可以同时包含多个 Part,这里我们包含一个图片和一个文本
val content = content {
image(bitmap) // 直接传递 Bitmap 对象给 image 函数
text(_questionText.value) // 添加文本部分
}
// 3. 调用 Gemini API
val response = currentModel.generateContent(content) // 使用已检查的 currentModel
_responseText.value = response.text ?: "未获取到回答,请稍后再试或换个问题。"
} catch (e: Exception) {
// 处理可能发生的错误,如网络问题、API 错误等
_responseText.value = "请求失败: ${e.message}"
e.printStackTrace() // 打印错误栈,方便调试
} finally {
_isLoading.value = false // 无论成功失败,都结束加载状态
}
}
}
/**
* 辅助函数:将 Uri 转换为 Bitmap。
* 兼容不同 Android 版本获取图片。
* @param context 应用上下文。
* @param uri 图片的 Uri。
* @return 转换后的 Bitmap 对象,如果失败则返回 null。
*/
private fun uriToBitmap(context: Context, uri: Uri): Bitmap? {
return try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { // Android P (API 28) 及以上
val source = ImageDecoder.createSource(context.contentResolver, uri)
ImageDecoder.decodeBitmap(source)
} else { // Android P 以下
@Suppress("DEPRECATION") // 抑制对旧API的警告
MediaStore.Images.Media.getBitmap(context.contentResolver, uri)
}
} catch (e: Exception) {
e.printStackTrace()
null
}
}
}
温馨提示:
- API Key:你的 Gemini API Key 应该妥善保管,不要直接硬编码在代码中。通常做法是放在
local.properties文件中,并通过build.gradle.kts配置到BuildConfig中。
怎么获取?使用你的谷歌帐号,在 -> https://aistudio.google.com/ 获取。 - 模型选择:对于图片理解,请确保选择支持视觉输入的 Gemini 模型,例如
gemini-2.5-flash-image或gemini-2.5-flash。 - 权限:别忘了在
AndroidManifest.xml中添加读取外部存储的权限(如果你的目标 SDK 版本需要),以及网络权限。
3.2 应用 UI 布局
应用的主界面将采用 Jetpack Compose 构建,提供简洁直观的用户体验。MainActivity 作为应用的入口点,会设置 Compose 的内容视图,并管理 SmartImageAppScreen Composable 的生命周期。
// MainActivity.kt
// ...
// 【确保 Icons 和 Icons.Default.AddAPhoto 的导入】
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AddAPhoto
import androidx.compose.material3.* // 导入所有 Material3 相关 Composable
import androidx.compose.runtime.* // 导入所有 Compose Runtime 相关,包括 @Composable, remember, collectAsState 等
// ...
// 【确保 LocalContext 的导入】
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.viewmodel.compose.viewModel // 导入 viewModel() 函数
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
SmartImageAssistantTheme {
// Scaffold 提供了基本的布局结构,如顶部栏、抽屉、浮动按钮等
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
// 将 ViewModel 注入到 Compose 层次结构中
val viewModel: SmartImageViewModel = viewModel()
SmartImageAppScreen(
viewModel = viewModel,
modifier = Modifier.padding(innerPadding)
)
}
}
}
}
}
/**
* 智能识图助手应用的主 Composable 屏幕。
* @param viewModel 负责处理业务逻辑和状态的 ViewModel。
* @param modifier 适用于此 Composable 的 Modifier。
*/
@OptIn(ExperimentalMaterial3Api::class) // 标记为实验性 API,因为 TopAppBar 是 M3 的新组件
@Composable
fun SmartImageAppScreen(
viewModel: SmartImageViewModel,
modifier: Modifier = Modifier
) {
// 从 ViewModel 收集状态,当状态变化时会触发 UI 重组
val selectedImageUri by viewModel.selectedImageUri.collectAsState()
val questionText by viewModel.questionText.collectAsState()
val responseText by viewModel.responseText.collectAsState()
val isLoading by viewModel.isLoading.collectAsState()
// 用于启动图片选择器的 Activity Result Launcher
val pickImageLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.GetContent() // 用于获取图片内容
) { uri: Uri? ->
viewModel.setSelectedImageUri(uri) // 将选中的 Uri 传给 ViewModel
}
val context = LocalContext.current
// 使用 Column 垂直布局所有元素,并添加滚动能力
Column(
modifier = modifier
.fillMaxSize()
.padding(16.dp)
.verticalScroll(rememberScrollState()), // 使内容可滚动,防止内容溢出
horizontalAlignment = Alignment.CenterHorizontally // 水平居中所有子元素
) {
// 顶部应用栏
TopAppBar(
title = { Text("智能识图助手") },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.primary,
),
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
// 1. 图片选择和预览区域
Card(
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
// 仅在非加载状态下允许点击选择图片
.clickable(enabled = !isLoading) { pickImageLauncher.launch("image/*") }
.clip(RoundedCornerShape(8.dp)), // 设置圆角
shape = RoundedCornerShape(8.dp) // Card 自身也需要设置圆角
) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center // Box 内内容居中
) {
if (selectedImageUri != null) {
// 使用 Coil 库异步加载图片并显示
Image(
painter = rememberAsyncImagePainter(model = selectedImageUri),
contentDescription = "Selected Image",
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop // 裁剪以填充空间,保持图片比例
)
} else {
Text("点击选择图片", style = MaterialTheme.typography.titleMedium)
}
// 蒙层和图标提示,增加交互感
if (!isLoading) {
Icon(
imageVector = Icons.Default.AddAPhoto, // 添加图片图标
contentDescription = "Select Image",
tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), // 半透明效果
modifier = Modifier.size(48.dp)
)
}
}
}
Spacer(modifier = Modifier.height(16.dp))
// 2. 问题输入框
OutlinedTextField(
value = questionText,
onValueChange = { viewModel.setQuestionText(it) }, // 更新 ViewModel 中的问题文本
label = { Text("请输入你的问题...") },
modifier = Modifier.fillMaxWidth(),
singleLine = false, // 允许多行输入
maxLines = 3,
enabled = !isLoading // 加载时禁用输入框
)
Spacer(modifier = Modifier.height(16.dp))
// 3. 发送按钮
Button(
onClick = { viewModel.sendQuery(context) }, // 传入当前 Context 给 ViewModel
modifier = Modifier.fillMaxWidth(),
// 仅在非加载、已选择图片且问题不为空时启用按钮
enabled = !isLoading && selectedImageUri != null && questionText.isNotBlank()
) {
if (isLoading) {
CircularProgressIndicator(
modifier = Modifier.size(24.dp),
color = MaterialTheme.colorScheme.onPrimary // 进度条颜色
)
Spacer(modifier = Modifier.width(8.dp))
Text("发送中...")
} else {
Text("发送给 Gemini")
}
}
Spacer(modifier = Modifier.height(16.dp))
// 4. AI 响应显示区域
Text(
text = "Gemini 的回答:\n$responseText",
modifier = Modifier.fillMaxWidth(),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface,
textAlign = TextAlign.Start // 左对齐
)
}
}
3.3 图片选择与预览
应用的用户界面设计围绕着直观的图片选择和实时预览展开。这部分通过 ActivityResultContracts.GetContent() 启动系统图库选择图片,并利用 Coil 库高效地加载和显示所选图片。
3.3.1 图片选择器集成
通过 rememberLauncherForActivityResult 和 ActivityResultContracts.GetContent(),我们创建了一个用于启动图片选择器的 Activity Result Launcher。当用户点击图片预览区域时,会触发图片选择器。
// ...
val selectedImageUri by viewModel.selectedImageUri.collectAsState()
// ...
// 用于启动图片选择器的 Activity Result Launcher
val pickImageLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.GetContent() // 用于获取图片内容 (如 "image/*")
) { uri: Uri? ->
viewModel.setSelectedImageUri(uri) // 将选中的 Uri 传给 ViewModel
}
// ...
// 1. 图片选择和预览区域
Card(
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
// 仅在非加载状态下允许点击选择图片
.clickable(enabled = !isLoading) { pickImageLauncher.launch("image/*") }
.clip(RoundedCornerShape(8.dp)), // 设置圆角
shape = RoundedCornerShape(8.dp) // Card 自身也需要设置圆角
) {
// ... (预览逻辑) ...
}
// ...
3.3.2 图片预览显示
选中的图片会立即显示在应用界面上的 Card 区域。我们使用 Coil 库(一个流行的 Android 图片加载库)来异步加载和展示图片,确保 UI 的流畅性。同时,当没有图片被选中时,会显示一个提示文本和图标。
// ...
// 1. 图片选择和预览区域
Card(
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.clickable(enabled = !isLoading) { pickImageLauncher.launch("image/*") }
.clip(RoundedCornerShape(8.dp)),
shape = RoundedCornerShape(8.dp)
) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
if (selectedImageUri != null) {
// 使用 Coil 库异步加载图片并显示
Image(
painter = rememberAsyncImagePainter(model = selectedImageUri),
contentDescription = "Selected Image",
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop // 裁剪以填充空间,保持图片比例
)
} else {
Text("点击选择图片", style = MaterialTheme.typography.titleMedium)
}
// 蒙层和图标提示,增加交互感
if (!isLoading) {
Icon(
imageVector = Icons.Default.AddAPhoto, // 添加图片图标
contentDescription = "Select Image",
tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), // 半透明效果
modifier = Modifier.size(48.dp)
)
}
}
}
// ...
3.4 效果展示
点击智能视图助手中间的相机按钮,上传本地图片。然后询问 Gemini AI 一段话。点击发送。
使用效果如图:
- 小猫图片识别:

- 花图识别:

(可以看出,gemini-2.5-flash 这个模型的图片识别能力还是不错的~很多特征描述的很准确)
当然,界面可能稍显简陋,不过可改进的方向也很明确,比如流式输出以避免UI阻塞、Markdown格式渲染、甚至Latex公式的渲染支持等等。
总结与展望
4.1 总结
通过这个简单的“智能识图助手”应用,我们成功地将 Gemini API 的多模态理解能力与 Jetpack Compose 现代化、响应式的 UI 框架结合起来。你已经学会了:
- 如何在 Android 项目中集成 Gemini API。
- 如何构建支持图片选择和文本输入的 Compose UI。
- 如何将图片转换为 Gemini API 可识别的格式。
- 如何发送多模态请求并处理 AI 的响应。
这只是多模态 AI 应用的冰山一角,但它为你打开了通往更智能、更具交互性的移动应用世界的大门!
4.2 展望未来:更多可能
- Function Calling(函数调用):这是一个非常强大的功能!它允许 Gemini 模型在理解用户意图后,调用你应用中的本地函数。比如,如果用户问“这张照片里的花叫什么名字,附近哪里有花店?”,Gemini 在识别出花的同时,可以触发一个本地的 findNearbyFlowerShops(flowerName: String) 函数,然后将函数返回的结果(比如花店列表)再总结成自然的语言回复给用户。这会极大地扩展 AI 应用的交互能力。你可以将其作为下一个进阶学习的目标。
- 实时视频流分析:将图片输入扩展到实时视频流,实现实时物体识别、行为分析等。
- 音频输入与理解:除了文本和图片,还可以集成语音识别,让用户通过语音提问。
- 更丰富的 UI 展现:例如,在识别出的物体上叠加边框或标签,甚至使用动画(如 Lottie 动画)来模拟 AI 的“思考”过程,让用户体验更生动。
- 错误处理与用户引导:完善错误提示和加载动画,提供更好的用户引导。
仓库链接……
我将本文项目代码上传至了GitHub,欢迎下载学习!⭐️
→ SmartImageAssistant
作者:徐宇强
原文链接:初探多模态AI:用 Jetpack Compose 和 Gemini API 打造智能识图应用
更多推荐



所有评论(0)