Z-Image-Turbo-辉夜巫女移动端适配:Android Studio中的模型调用示例

张开发
2026/4/18 2:19:00 15 分钟阅读

分享文章

Z-Image-Turbo-辉夜巫女移动端适配:Android Studio中的模型调用示例
Z-Image-Turbo-辉夜巫女移动端适配Android Studio中的模型调用示例最近在做一个图片处理相关的App需要用到一些AI图片生成和编辑的功能。自己训练模型成本太高部署和维护也是个麻烦事。后来发现直接把模型部署在云端然后让手机App通过网络去调用是个挺不错的方案。这不我就用Z-Image-Turbo-辉夜巫女这个模型试了试效果还挺好。今天就来聊聊怎么在Android Studio里让你的App能方便地调用这个云端图片模型服务。我会从怎么发起网络请求到怎么处理图片上传下载再到一些移动端特有的坑比如网络不好、图片太大都过一遍。如果你也在做类似的功能希望这篇内容能帮到你。1. 准备工作环境与依赖在开始写代码之前得先把“舞台”搭好。这里主要就是配置Android Studio项目和引入必要的库。1.1 项目基础配置首先确保你的Android Studio项目已经创建好。然后打开项目根目录下的build.gradle文件确认已经配置了必要的仓库。通常默认的就行但如果你要引入一些特定库可能需要添加JitPack之类的仓库。接下来打开你的App模块下的build.gradle文件通常是app/build.gradle。我们需要在这里添加几个关键的依赖库。这些库分别负责网络请求、图片加载和异步任务处理是移动端调用云端服务的“三驾马车”。dependencies { // 网络请求库 - Retrofit用于调用云端API implementation com.squareup.retrofit2:retrofit:2.9.0 implementation com.squareup.retrofit2:converter-gson:2.9.0 // 用于JSON解析 implementation com.squareup.okhttp3:logging-interceptor:4.10.0 // 用于查看网络请求日志调试用 // 图片加载库 - Glide用于加载和显示网络图片 implementation com.github.bumptech.glide:glide:4.15.1 annotationProcessor com.github.bumptech.glide:compiler:4.15.1 // 或者你也可以选择Coil一个基于Kotlin协程的轻量级图片加载库 // implementation io.coil-kt:coil:2.4.0 // Kotlin协程用于简化异步操作 implementation org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4 implementation androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1 // 在ViewModel中使用协程 }添加完依赖后点击Sync Now同步一下项目。同步成功就说明基础环境准备好了。1.2 网络权限与模型服务地址手机App要访问网络必须获得用户的许可。在app/src/main/AndroidManifest.xml文件中添加网络权限声明manifest ... uses-permission android:nameandroid.permission.INTERNET / uses-permission android:nameandroid.permission.ACCESS_NETWORK_STATE / ... /manifestINTERNET权限是必须的ACCESS_NETWORK_STATE权限可以用来检查当前网络状态方便我们在无网络时给用户提示。另一个关键信息是云端模型服务的地址。你需要知道Z-Image-Turbo-辉夜巫女服务部署在哪里以及它的API端点Endpoint是什么。假设你的服务部署后生成图片的API地址是https://your-model-service.com/api/v1/generate。请务必将这里的示例地址替换成你实际的服务地址。2. 构建网络请求层这是连接手机App和云端模型的核心部分。我们会用Retrofit来构建一个清晰、易用的网络层。2.1 定义数据模型首先根据模型服务的API文档定义请求和响应的数据结构。假设生成图片的API需要接收一个文本描述prompt并返回图片的URL。我们可以创建两个Kotlin数据类// 请求体 data class ImageGenerationRequest( val prompt: String, // 图片描述文本 val negative_prompt: String? null, // 反向提示词可选 val steps: Int 20, // 生成步数可选参数给默认值 val width: Int 512, // 图片宽度 val height: Int 512 // 图片高度 ) // 响应体 data class ImageGenerationResponse( val success: Boolean, val message: String?, val data: ImageData? ) data class ImageData( val image_url: String // 云端生成图片后返回的临时访问地址 )数据类用起来很方便Retrofit配合Gson可以自动把JSON转换成这些对象。2.2 创建Retrofit服务接口接下来定义一个Retrofit接口来描述我们的API。这里以生成图片的POST请求为例import retrofit2.http.Body import retrofit2.http.POST interface ModelApiService { POST(generate) // 相对路径会和Base URL拼接 suspend fun generateImage(Body request: ImageGenerationRequest): ImageGenerationResponse }注意这里用了suspend关键字这意味着这是一个挂起函数可以在协程中调用避免阻塞主线程。这是Kotlin协程和Retrofit配合的推荐写法。2.3 配置Retrofit实例然后我们需要创建一个Retrofit实例。通常会把它封装在一个单例或者依赖注入框架中方便全局使用。这里写一个简单的单例示例import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import java.util.concurrent.TimeUnit object RetrofitClient { // 替换成你实际的模型服务基础地址 private const val BASE_URL https://your-model-service.com/api/v1/ private val loggingInterceptor HttpLoggingInterceptor().apply { level HttpLoggingInterceptor.Level.BODY // 打印请求和响应的Body调试完成后可改为Level.BASIC或NONE } private val okHttpClient OkHttpClient.Builder() .addInterceptor(loggingInterceptor) .connectTimeout(60, TimeUnit.SECONDS) // 连接超时设置长一些图片生成可能较慢 .readTimeout(60, TimeUnit.SECONDS) // 读取超时 .writeTimeout(60, TimeUnit.SECONDS) // 写入超时 .build() private val retrofit Retrofit.Builder() .baseUrl(BASE_URL) .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create()) .build() val modelApiService: ModelApiService by lazy { retrofit.create(ModelApiService::class.java) } }这里有几个点需要注意超时时间AI图片生成通常比较耗时所以超时时间connectTimeout,readTimeout要设置得足够长比如60秒避免请求被意外中断。日志拦截器HttpLoggingInterceptor在开发阶段非常有用可以查看具体的请求和响应内容。发布版本前记得关闭或降低日志级别。by lazy延迟初始化只有在第一次访问modelApiService时才创建节省资源。3. 在界面中调用与显示网络层准备好了现在我们需要在Activity或Fragment里调用它并把生成的图片显示出来。3.1 使用协程发起请求在ViewModel中发起网络请求是个好习惯这样即使界面旋转请求也不会中断。我们使用协程来处理异步操作。import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.coroutines.Dispatchers class ImageGenerationViewModel : ViewModel() { private val _generationResult MutableLiveDataResultString() val generationResult: LiveDataResultString _generationResult fun generateImage(prompt: String) { viewModelScope.launch { _generationResult.value Result.loading() // 通知UI开始加载 try { // 在IO线程池执行网络请求 val response withContext(Dispatchers.IO) { RetrofitClient.modelApiService.generateImage( ImageGenerationRequest(prompt prompt) ) } if (response.success !response.data?.image_url.isNullOrEmpty()) { // 请求成功拿到图片URL _generationResult.value Result.success(response.data!!.image_url) } else { // 服务端返回了错误信息 _generationResult.value Result.error(response.message ?: 生成失败) } } catch (e: Exception) { // 网络异常或其他错误 _generationResult.value Result.error(e.message ?: 网络请求失败) } } } } // 一个简单的密封类用来封装请求状态 sealed class Resultout T { data class Successout T(val data: T) : ResultT() data class Error(val message: String) : ResultNothing() object Loading : ResultNothing() companion object { fun T success(data: T): ResultT Success(data) fun error(message: String): ResultNothing Error(message) fun loading(): ResultNothing Loading } }3.2 使用Glide加载并显示图片当ViewModel拿到图片URL后我们需要在Activity/Fragment中观察这个结果并用Glide把图片加载到ImageView里。首先在布局文件里放一个按钮和一个ImageView?xml version1.0 encodingutf-8? LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightmatch_parent android:orientationvertical android:padding16dp EditText android:idid/promptEditText android:layout_widthmatch_parent android:layout_heightwrap_content android:hint请输入图片描述例如一只在星空下奔跑的狐狸 android:inputTypetextMultiLine/ Button android:idid/generateButton android:layout_widthmatch_parent android:layout_heightwrap_content android:layout_marginTop16dp android:text生成图片 / ProgressBar android:idid/loadingProgressBar android:layout_widthwrap_content android:layout_heightwrap_content android:layout_gravitycenter android:layout_marginTop32dp android:visibilitygone / ImageView android:idid/resultImageView android:layout_widthmatch_parent android:layout_height300dp android:layout_marginTop32dp android:scaleTypecenterCrop android:background#f0f0f0/ /LinearLayout然后在Activity中处理逻辑import android.os.Bundle import android.view.View import android.widget.Toast import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import com.bumptech.glide.Glide import com.yourpackage.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private val viewModel: ImageGenerationViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) binding.generateButton.setOnClickListener { val prompt binding.promptEditText.text.toString().trim() if (prompt.isNotEmpty()) { viewModel.generateImage(prompt) } else { Toast.makeText(this, 请输入描述, Toast.LENGTH_SHORT).show() } } // 观察生成结果 viewModel.generationResult.observe(this) { result - when (result) { is Result.Loading - { binding.loadingProgressBar.visibility View.VISIBLE binding.resultImageView.visibility View.GONE } is Result.Success - { binding.loadingProgressBar.visibility View.GONE binding.resultImageView.visibility View.VISIBLE val imageUrl result.data // 使用Glide加载网络图片 Glide.with(this) .load(imageUrl) .placeholder(R.drawable.placeholder) // 加载中的占位图 .error(R.drawable.error) // 加载失败的图片 .into(binding.resultImageView) } is Result.Error - { binding.loadingProgressBar.visibility View.GONE Toast.makeText(this, 错误${result.message}, Toast.LENGTH_LONG).show() } } } } }这样一个完整的“输入描述 - 点击生成 - 显示图片”的流程就跑通了。Glide会自动处理图片的下载、缓存和显示非常省心。4. 处理移动端特有挑战在移动端调用云端AI服务和网页端或桌面端有些不一样得特别注意以下几个问题。4.1 图片上传的压缩与优化如果你的应用场景是图片编辑比如图生图那么就需要上传手机本地的图片。手机拍的照片动辄几MB甚至十几MB直接上传不仅耗流量速度慢还可能被服务器拒绝。解决方案是在上传前对图片进行压缩。这里不建议使用有损压缩导致图片质量严重下降而是采用尺寸缩放和适度质量压缩。import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.Matrix import java.io.ByteArrayOutputStream import java.io.File import java.io.FileOutputStream object ImageCompressor { /** * 压缩图片文件 * param sourceFile 原图文件 * param maxWidth 最大宽度 * param maxHeight 最大高度 * param quality 压缩质量 (0-100) * return 压缩后的文件 */ fun compressImageFile(sourceFile: File, maxWidth: Int, maxHeight: Int, quality: Int 80): File { // 1. 解码图片并获取尺寸 val options BitmapFactory.Options().apply { inJustDecodeBounds true } BitmapFactory.decodeFile(sourceFile.absolutePath, options) val sourceWidth options.outWidth val sourceHeight options.outHeight // 2. 计算缩放比例 var scale 1f if (sourceWidth maxWidth || sourceHeight maxHeight) { val widthScale maxWidth.toFloat() / sourceWidth val heightScale maxHeight.toFloat() / sourceHeight scale widthScale.coerceAtMost(heightScale) } // 3. 按比例解码图片 val matrix Matrix().apply { postScale(scale, scale) } val bitmap BitmapFactory.decodeFile(sourceFile.absolutePath)?.let { Bitmap.createBitmap(it, 0, 0, it.width, it.height, matrix, true) } ?: throw IllegalArgumentException(无法解码图片文件) // 4. 压缩并保存到新文件 val outputStream ByteArrayOutputStream() bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream) val compressedData outputStream.toByteArray() val compressedFile File.createTempFile(compressed_, .jpg, cacheDir) FileOutputStream(compressedFile).use { fos - fos.write(compressedData) } bitmap.recycle() return compressedFile } }使用的时候可以先压缩再用Retrofit的Multipart功能上传压缩后的文件。这样能显著减少上传数据量提升用户体验。4.2 网络状态感知与省流模式用户可能在移动网络环境下使用App流量宝贵。我们可以提供一个“省流模式”选项在设置里让用户选择。省流模式的核心逻辑是在上传图片进行编辑时使用更强的压缩参数在下载生成的图片时请求服务器返回缩略图或较低分辨率的版本这需要云端API支持。同时在发起网络请求前应该检查当前的网络状态import android.content.Context import android.net.ConnectivityManager import android.net.NetworkCapabilities fun isNetworkAvailable(context: Context): Boolean { val connectivityManager context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val network connectivityManager.activeNetwork ?: return false val capabilities connectivityManager.getNetworkCapabilities(network) ?: return false return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) } // 在ViewModel中调用前检查 fun generateImageSafely(prompt: String, context: Context) { if (!isNetworkAvailable(context)) { _generationResult.value Result.error(网络不可用请检查连接) return } generateImage(prompt) }4.3 异步处理与生命周期管理移动端界面生命周期变化频繁比如切屏、回桌面。我们必须确保网络请求不会因为界面销毁而导致内存泄漏或崩溃。上面的例子已经做了很好的示范使用ViewModel将网络请求放在ViewModel中这样即使Activity重建请求逻辑和LiveData数据依然存在。使用viewModelScope在ViewModel中启动协程这个协程作用域与ViewModel生命周期绑定。当ViewModel被清除时所有在该作用域内启动的协程会自动取消避免了资源泄漏。使用LiveData观察结果在Activity/Fragment中观察LiveData当界面处于活跃状态时才会收到数据更新避免了在后台更新UI可能导致的崩溃。这就是移动端开发中“响应式”和“生命周期感知”的常见做法能有效提升应用的健壮性。5. 总结把Z-Image-Turbo-辉夜巫女这样的AI模型服务集成到Android应用里听起来复杂但拆解开来就是几个明确的步骤配置网络库、定义数据接口、在后台发起请求、在前端显示结果。核心思路就是把手机当成一个终端复杂的模型计算交给云端。实际做下来我感觉Retrofit加协程这个组合用起来很顺手代码写出来清晰异步处理也简单。Glide加载图片更是省了不少事缓存什么的都帮你管好了。移动端那些特有的问题比如图片太大、网络不稳只要提前想到都有比较成熟的方案可以应对比如上传前压缩、做好网络状态判断和超时处理。这种云端调用的方式特别适合不想在App里塞进一个大模型或者需要频繁更新模型能力的场景。开发起来快后期维护也相对轻松。如果你正在考虑给App添加AI图片能力不妨试试这个方案。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。

更多文章