Z-Image-Turbo-辉夜巫女移动开发:Android Studio集成模型API的简易图库应用
Z-Image-Turbo-辉夜巫女移动开发Android Studio集成模型API的简易图库应用你是不是也想过能不能在自己开发的手机App里直接输入一句话就生成一张精美的图片比如在做一个旅行日记App时用户输入“夕阳下的金色海滩”就能立刻生成对应的风景图直接插入到日记里。听起来很酷但感觉技术门槛很高其实现在这件事已经变得简单多了。借助像Z-Image-Turbo-辉夜巫女这样的图像生成模型我们完全可以把AIGC人工智能生成内容的能力轻松集成到Android应用中。今天我就带你一步步实现这个想法从零开始在Android Studio里构建一个简易的图库应用。这个App的核心功能就是用户输入文字描述调用模型API生成图片然后把生成的图片展示在App里还能一键保存到手机相册。整个过程我们会用到Android开发的基础知识以及OkHttp这个好用的网络库。不用担心复杂我会把每一步都讲清楚确保你跟着做就能跑起来。最终你会得到一个可以实际运行、体验AIGC魅力的Demo应用这或许能为你下一个创意App项目打开一扇新的大门。1. 项目准备与环境搭建在开始敲代码之前我们得先把“厨房”收拾好把必要的“食材”和“工具”准备齐全。这里主要就是安装Android Studio和创建一个新的项目。1.1 获取与安装Android StudioAndroid Studio是谷歌官方推荐的Android应用开发环境集成了代码编辑、调试、模拟器等一系列工具。对于新手来说它是入门的不二之选。首先你需要访问Android开发者官网找到Android Studio的下载页面。选择适合你电脑操作系统Windows、macOS或Linux的版本进行下载。安装过程基本上是“下一步”到底但有几个地方可以留意一下安装类型建议选择“Standard”标准安装它会自动帮你配置好Android SDK和默认设置省去很多手动配置的麻烦。SDK组件安装程序会下载必要的Android SDK组件确保网络通畅。SDK软件开发工具包里包含了编译和运行App所需的各种库和工具。安装位置如果你C盘空间紧张可以自定义SDK的安装路径但建议Android Studio本体还是安装在默认位置避免一些潜在的路径问题。安装完成后第一次启动可能会花点时间因为它要完成一些初始化和组件索引的工作。耐心等待就好。1.2 创建新项目与基础配置打开Android Studio你会看到欢迎界面。点击“New Project”新建项目开始。选择模板在模板选择界面我们选最基础的“Empty Views Activity”空视图活动。这个模板只包含一个最简单的界面和一个主活动Activity正好适合我们从零开始构建不会被多余的代码干扰。配置项目Name给你的应用起个名字比如“AIGC Gallery”。Package name包名通常是“com.你的名字.应用名”的格式这是App的唯一标识。保持默认或按喜好修改即可。Save location选择项目存放的文件夹。Language选择“Kotlin”。Kotlin现在是Android开发的官方首选语言比Java更简洁现代。Minimum SDK选择最低支持的Android版本。为了兼顾更多的用户设备可以选择“API 24: Android 7.0 (Nougat)”。这个版本已经能很好地支持我们接下来要用到的所有特性了。完成创建点击“Finish”Android Studio会自动创建项目并开始构建。第一次构建可能会下载一些Gradle依赖需要稍等片刻。项目创建好后你会在左侧的“Project”面板中看到标准的Android项目结构。其中app/src/main/java目录下是我们的Kotlin源代码app/src/main/res目录下存放着布局、图片等资源文件。2. 设计应用界面与布局一个简单的界面能让我们的想法快速可视化。我们的App界面需要三个核心部分一个让用户输入文字描述的编辑框一个触发生成图片的按钮以及一个用来展示生成图片的区域。我们通过修改activity_main.xml这个布局文件来实现。打开app/src/main/res/layout/activity_main.xml将里面的代码替换为以下内容?xml version1.0 encodingutf-8? LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android xmlns:apphttp://schemas.android.com/apk/res-auto xmlns:toolshttp://schemas.android.com/tools android:layout_widthmatch_parent android:layout_heightmatch_parent android:orientationvertical android:padding16dp tools:context.MainActivity !-- 输入描述的区域 -- TextView android:layout_widthwrap_content android:layout_heightwrap_content android:text请输入图片描述 android:textSize18sp android:layout_marginBottom8dp/ EditText android:idid/et_prompt android:layout_widthmatch_parent android:layout_heightwrap_content android:hint例如一只戴着礼帽的卡通猫 android:inputTypetextMultiLine android:minLines3 android:gravitytop android:layout_marginBottom16dp/ !-- 生成按钮 -- Button android:idid/btn_generate android:layout_widthmatch_parent android:layout_heightwrap_content android:text生成图片 android:textAllCapsfalse android:layout_marginBottom24dp/ !-- 显示生成状态或错误的文本 -- TextView android:idid/tv_status android:layout_widthmatch_parent android:layout_heightwrap_content android:text等待生成... android:gravitycenter android:textColor#757575 android:layout_marginBottom16dp/ !-- 图片展示区域 -- ImageView android:idid/iv_generated_image android:layout_width300dp android:layout_height300dp android:layout_gravitycenter_horizontal android:scaleTypecenterCrop android:background#f0f0f0 android:contentDescription生成的图片 android:layout_marginBottom16dp/ !-- 保存图片按钮 -- Button android:idid/btn_save android:layout_widthmatch_parent android:layout_heightwrap_content android:text保存到相册 android:textAllCapsfalse android:enabledfalse / /LinearLayout这段布局代码做了以下几件事使用LinearLayout垂直排列所有元素。EditText(et_prompt) 让用户输入多行文本描述。Button(btn_generate) 用于触发生成图片的请求。TextView(tv_status) 用来显示“生成中”、“生成成功”或错误信息。ImageView(iv_generated_image) 是展示生成图片的核心区域我们给它设定了固定宽高和一个灰色背景。另一个Button(btn_save) 用于将图片保存到相册初始状态设为不可用(enabled”false”)等图片生成成功后再激活它。界面设计好了接下来就是让这个界面“活”起来处理用户的点击和网络请求。3. 集成网络库与处理权限我们的App需要和远端的Z-Image-Turbo-辉夜巫女模型API服务器通信所以必须引入网络库并申请网络权限。同时保存图片到相册还需要读写外部存储的权限。3.1 添加OkHttp依赖我们选择OkHttp作为网络请求库它功能强大且使用简单。打开项目根目录下的build.gradle.kts (Module :app)文件在dependencies块中添加OkHttp的依赖dependencies { // ... 其他已有的依赖 implementation(com.squareup.okhttp3:okhttp:4.12.0) // 添加OkHttp }添加后Android Studio右上角会出现一个“Sync Now”的提示点击它同步项目Gradle会自动下载这个库。3.2 声明应用权限App需要获得用户的授权才能访问网络和存储空间。打开app/src/main/AndroidManifest.xml文件在application标签之前添加以下权限声明?xml version1.0 encodingutf-8? manifest xmlns:androidhttp://schemas.android.com/apk/res/android xmlns:toolshttp://schemas.android.com/tools !-- 允许应用访问网络 -- uses-permission android:nameandroid.permission.INTERNET / !-- 允许应用写入外部存储用于保存图片到相册 -- uses-permission android:nameandroid.permission.WRITE_EXTERNAL_STORAGE tools:ignoreScopedStorage / !-- 针对Android 10及以上版本还需要此权限来保存媒体文件 -- uses-permission android:nameandroid.permission.ACCESS_MEDIA_LOCATION / application ... ... /application /manifestINTERNET权限是必须的否则无法进行任何网络请求。WRITE_EXTERNAL_STORAGE权限用于将图片文件保存到公共的相册目录。注意tools:ignore”ScopedStorage”是为了在较新Android版本上兼容性处理实际生产环境需要更精细地适配分区存储。ACCESS_MEDIA_LOCATION是针对Android 10的权限有助于更准确地管理媒体文件。权限声明了但Android 6.0 (API 23) 之后部分危险权限如存储权限需要在运行时动态向用户申请。我们会在后面用到保存功能时再处理这个逻辑。4. 实现图片生成与展示逻辑这是整个应用最核心的部分。我们需要在MainActivity.kt中编写代码连接界面按钮构造API请求处理网络响应并最终把图片显示出来。4.1 构建API请求与处理响应首先你需要有一个可用的Z-Image-Turbo-辉夜巫女模型的API端点URL和可能的API密钥。这里我们用[你的API地址]和[你的API密钥]作为占位符你需要替换成真实的信息。打开MainActivity.kt开始编写代码import android.graphics.Bitmap import android.graphics.BitmapFactory import android.os.Bundle import android.widget.Button import android.widget.EditText import android.widget.ImageView import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import okhttp3.* import okhttp3.MediaType.Companion.toMediaType import okhttp3.RequestBody.Companion.toRequestBody import org.json.JSONObject import java.io.IOException class MainActivity : AppCompatActivity() { private lateinit var etPrompt: EditText private lateinit var btnGenerate: Button private lateinit var tvStatus: TextView private lateinit var ivGeneratedImage: ImageView private lateinit var btnSave: Button private val okHttpClient OkHttpClient() // TODO: 替换为你的真实API地址和密钥 private val apiUrl https://[你的API地址]/v1/images/generations private val apiKey [你的API密钥] override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 初始化界面控件 etPrompt findViewById(R.id.et_prompt) btnGenerate findViewById(R.id.btn_generate) tvStatus findViewById(R.id.tv_status) ivGeneratedImage findViewById(R.id.iv_generated_image) btnSave findViewById(R.id.btn_save) // 设置生成按钮的点击事件 btnGenerate.setOnClickListener { val prompt etPrompt.text.toString().trim() if (prompt.isEmpty()) { Toast.makeText(this, 请输入图片描述, Toast.LENGTH_SHORT).show() returnsetOnClickListener } generateImage(prompt) } // 保存按钮的点击事件功能在下一节实现 btnSave.setOnClickListener { // 暂时先提示 Toast.makeText(this, 保存功能待实现, Toast.LENGTH_SHORT).show() } } private fun generateImage(prompt: String) { // 更新状态禁用按钮防止重复点击 tvStatus.text 正在生成请稍候... btnGenerate.isEnabled false // 使用协程在后台线程执行网络请求 CoroutineScope(Dispatchers.IO).launch { try { // 1. 构建JSON请求体 val jsonBody JSONObject().apply { put(model, z-image-turbo) // 根据实际模型名调整 put(prompt, prompt) put(n, 1) // 生成1张图片 put(size, 512x512) // 图片尺寸 } // 2. 构建OkHttp请求 val requestBody jsonBody.toString().toRequestBody(application/json.toMediaType()) val request Request.Builder() .url(apiUrl) .post(requestBody) .addHeader(Authorization, Bearer $apiKey) .addHeader(Content-Type, application/json) .build() // 3. 执行网络请求 val response okHttpClient.newCall(request).execute() // 4. 处理响应 withContext(Dispatchers.Main) { if (response.isSuccessful) { val responseBody response.body?.string() // 解析返回的JSON获取图片URL val imageUrl parseImageUrlFromResponse(responseBody) if (!imageUrl.isNullOrEmpty()) { tvStatus.text 生成成功正在加载图片... // 下载并显示图片 loadImageFromUrl(imageUrl) } else { tvStatus.text 生成失败未找到图片URL btnGenerate.isEnabled true } } else { tvStatus.text 生成失败${response.code} - ${response.message} btnGenerate.isEnabled true } } } catch (e: IOException) { withContext(Dispatchers.Main) { tvStatus.text 网络请求失败${e.message} btnGenerate.isEnabled true } } catch (e: Exception) { withContext(Dispatchers.Main) { tvStatus.text 发生错误${e.message} btnGenerate.isEnabled true } } } } private fun parseImageUrlFromResponse(jsonString: String?): String? { // 这是一个示例解析逻辑你需要根据你的API返回的实际JSON结构进行调整 return try { val jsonObject JSONObject(jsonString) // 假设返回结构是 { data: [ { url: https://... } ] } val dataArray jsonObject.getJSONArray(data) if (dataArray.length() 0) { val firstItem dataArray.getJSONObject(0) firstItem.getString(url) } else { null } } catch (e: Exception) { e.printStackTrace() null } } private fun loadImageFromUrl(imageUrl: String) { CoroutineScope(Dispatchers.IO).launch { try { // 再次使用OkHttp下载图片字节流 val request Request.Builder().url(imageUrl).build() val response okHttpClient.newCall(request).execute() if (response.isSuccessful) { val inputStream response.body?.byteStream() val bitmap BitmapFactory.decodeStream(inputStream) inputStream?.close() withContext(Dispatchers.Main) { // 在主线程更新UI ivGeneratedImage.setImageBitmap(bitmap) tvStatus.text 图片加载完成 btnGenerate.isEnabled true // 激活保存按钮并将bitmap暂存以备保存 btnSave.isEnabled true // TODO: 将bitmap保存到全局变量或ViewModel中供保存功能使用 } } else { withContext(Dispatchers.Main) { tvStatus.text 图片下载失败 btnGenerate.isEnabled true } } } catch (e: IOException) { withContext(Dispatchers.Main) { tvStatus.text 下载图片时网络错误 btnGenerate.isEnabled true } } } } }这段代码的关键点协程处理使用kotlinx.coroutines在后台线程执行耗时的网络操作避免阻塞主线程导致界面卡顿。OkHttp请求构建一个POST请求将用户输入的描述prompt以JSON格式发送给API。响应解析parseImageUrlFromResponse函数负责从API返回的JSON数据中提取出生成图片的URL。请注意这个解析逻辑需要你根据Z-Image-Turbo-辉夜巫女模型API的实际返回格式进行调整。图片加载获取到图片URL后再次发起一个GET请求下载图片数据并将其解码为Bitmap最后通过setImageBitmap方法显示在ImageView上。4.2 实现图片保存到相册功能图片生成并显示后用户自然想要保存它。我们需要实现保存按钮的功能将Bitmap保存为文件并插入到系统的媒体库使其能在相册App中看到。首先在MainActivity类中添加一个变量来暂存生成的Bitmap并完善保存按钮的点击事件class MainActivity : AppCompatActivity() { // ... 之前的变量声明 ... private var generatedBitmap: Bitmap? null // 新增保存生成的位图 override fun onCreate(savedInstanceState: Bundle?) { // ... 之前的初始化代码 ... btnSave.setOnClickListener { generatedBitmap?.let { bitmap - saveImageToGallery(bitmap) } ?: run { Toast.makeText(this, 没有可保存的图片, Toast.LENGTH_SHORT).show() } } } // 在 loadImageFromUrl 的 success 回调中保存bitmap private fun loadImageFromUrl(imageUrl: String) { CoroutineScope(Dispatchers.IO).launch { try { // ... 下载图片代码 ... withContext(Dispatchers.Main) { ivGeneratedImage.setImageBitmap(bitmap) tvStatus.text 图片加载完成 btnGenerate.isEnabled true btnSave.isEnabled true generatedBitmap bitmap // 保存bitmap引用 } // ... 错误处理 ... } } } private fun saveImageToGallery(bitmap: Bitmap) { // 动态申请存储权限简化示例生产环境需更完整处理 if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) ! PackageManager.PERMISSION_GRANTED) { requestPermissions(arrayOf(android.Manifest.permission.WRITE_EXTERNAL_STORAGE), REQUEST_CODE_WRITE_STORAGE) return } // 已有权限执行保存 performSaveImage(bitmap) } private fun performSaveImage(bitmap: Bitmap) { CoroutineScope(Dispatchers.IO).launch { try { // 1. 创建图片文件 val filename AIGC_${System.currentTimeMillis()}.jpg val contentValues ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, filename) put(MediaStore.MediaColumns.MIME_TYPE, image/jpeg) if (Build.VERSION.SDK_INT Build.VERSION_CODES.Q) { put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES /AIGC_Gallery) } } // 2. 获取ContentResolver并插入文件信息 val resolver applicationContext.contentResolver val uri resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) uri?.let { // 3. 打开输出流压缩并写入Bitmap val outputStream resolver.openOutputStream(it) if (outputStream ! null) { bitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream) outputStream.close() withContext(Dispatchers.Main) { Toast.makeText(thisMainActivity, 图片已保存到相册, Toast.LENGTH_LONG).show() } } } } catch (e: Exception) { withContext(Dispatchers.Main) { Toast.makeText(thisMainActivity, 保存失败: ${e.message}, Toast.LENGTH_SHORT).show() } } } } // 处理权限申请结果 override fun onRequestPermissionsResult(requestCode: Int, permissions: Arrayout String, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode REQUEST_CODE_WRITE_STORAGE) { if (grantResults.isNotEmpty() grantResults[0] PackageManager.PERMISSION_GRANTED) { generatedBitmap?.let { performSaveImage(it) } } else { Toast.makeText(this, 需要存储权限才能保存图片, Toast.LENGTH_SHORT).show() } } } companion object { private const val REQUEST_CODE_WRITE_STORAGE 1001 } }这段保存功能的代码利用了Android的MediaStoreAPI这是Android 10及以上版本推荐的方式用于将文件保存到公共的媒体目录如相册。它会自动处理文件路径和媒体库更新。5. 运行测试与效果体验代码都写好了是时候看看成果了。点击Android Studio工具栏上的“Run”按钮绿色的三角形选择一个已连接的Android设备或启动一个模拟器。应用启动后你应该能看到我们设计的界面。尝试在输入框里写下一段描述比如“星空下的孤独城堡”然后点击“生成图片”按钮。观察状态文本的变化如果一切顺利几分钟内取决于API速度生成的图片就会显示在下方。图片显示后“保存到相册”按钮会变得可用。点击它如果这是第一次请求权限系统会弹出授权对话框。同意后图片就会被保存。你可以打开手机自带的“相册”或“文件”应用在“Pictures/AIGC_Gallery”文件夹里找到它。在这个过程中你可能会遇到一些问题比如网络错误、API返回格式不对、或者权限问题。查看Android Studio的“Logcat”窗口可以获取详细的错误信息帮助你进行调试。关键是要确保API地址、密钥以及响应解析逻辑是正确的。6. 总结走完这一趟我们成功地把一个云端强大的AIGC图像生成模型塞进了一个我们自己编写的Android应用里。从搭建环境、设计界面到处理网络请求、解析数据、显示和保存图片我们完成了一个功能完整的闭环。这个简易的图库应用虽然界面简单但它清晰地演示了移动端集成AI模型API的核心流程构造请求、处理响应、更新UI。在实际开发中你可以在这个基础上做很多扩展。比如增加一个历史记录列表保存用户生成过的所有图片和对应的描述或者加入更详细的生成参数设置如图片尺寸、风格权重等还可以优化用户体验添加生成进度条、图片缓存机制让应用更加流畅。更重要的是这个模式可以迁移。你今天学会了集成一个图像生成模型明天就可以用类似的思路去集成一个文本对话模型、语音合成模型为你的App注入各种AI能力。移动端AI应用的开发正变得越来越触手可及。希望这个简单的实践能成为你探索这个有趣领域的第一块敲门砖。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。