先说结论:文生图半天能跑通,图生图要花一周,剩下的时间全在处理「这家 API 和那家 API 不一样」。
这个 App 叫 Android-AI-Image-Studio,Java 写的,接任何兼容 OpenAI 格式的绘图接口。下面是我做完之后觉得值得记下来的部分。
为什么自己写一个
三个理由凑一起就够开工了:
- 官方 App 在国内基本用不上,网页版在手机上操作难受;
- 我有自己的中转 API,不想每次都开浏览器粘 key;
- 想做成能存历史、能反复改提示词的工作流,而不是一次性网页。
结构:三层就够
技术栈没有花活:Java + AndroidX,页面用 Navigation Component 串,网络 OkHttp 4,JSON 用 Gson,图片交给 Glide。minSdk 定在 24(Android 7.0)——再往下要处理一堆兼容,收益不值。
分三层:
- UI:对话页(Fragment)、历史页、设置页;
- 网络:一个
ApiClient封装 OkHttp,一个Repository管请求和重试; - 存储:会话用 Gson 序列化后落本地文件,不做数据库。
不引 Room 是因为一张表就够,JSON 文件更好调试——出问题时能直接打开看,而不是去翻 SQLite。
文生图:一条请求的事
POST /v1/images/generations,body 里三个关键字段:prompt、size、quality(或 n)。响应有两种形态,得都处理:
b64_json:直接 base64 解码成 Bitmap;url:下载后交给 Glide 缓存,顺便存一份本地路径。
RequestBody body = RequestBody.create(json, JSON);
Request req = new Request.Builder()
.url(baseUrl + "/v1/images/generations")
.addHeader("Authorization", "Bearer " + apiKey)
.post(body)
.build();
真正的坑在超时。生图不是秒回,默认 10 秒必挂。我把 connect 设 30 秒、read 设 120 秒,并且给绘图请求单独一个 OkHttpClient 实例——别跟普通请求共用连接池和超时配置。
图生图:为什么要五级降级
文生图各家的字段基本一致,图生图不是。同样是「上传一张图让模型改」,实际差异能列一页:
| 差异点 | 表现 |
|---|---|
| 请求格式 | 有的只吃 multipart/form-data,有的要求 JSON 里塞 base64 |
| 端点 | 有的在 /images/edits,有的塞进 /chat/completions 走多模态 |
| 遮罩 | 有的必须传 mask,有的传了就报错 |
| 模型名 | 同一家不同版本对 model 的取值挑剔 |
| 返回 | 这次给 url,下次给 b64_json |
所以我没写「判断是哪家 API」的分支——那种代码三个月后自己都看不懂。改成策略链:把每次尝试抽象成一个 strategy,按顺序试,第一个成功就停,全失败才报错。
for (Strategy s : strategies) {
try {
Result r = s.execute(image, prompt, config);
if (r != null) return r; // 成功即停
} catch (ApiException e) {
log("strategy " + s.name() + " failed: " + e.code());
// 继续下一个
}
}
throw new AllStrategiesFailedException();
五级降级的方向大致是:标准 multipart → 去掉 mask → 换 JSON + base64 → 换模型名 → 换端点。顺序按「兼容性从高到低」排,越靠前的成功率越高、代价越小。
关键不是这五步具体是什么,而是每次失败都留下日志。没有日志的降级等于在猜。
历史会话:别急着上数据库
每个会话存成一个对象:标题、创建时间、消息数组。消息里区分文本和图片,图片只存路径或 URL,不存 base64——存了文件会迅速膨胀到几十 MB,读的时候还卡。
列表页按时间倒序,点进去恢复上下文,能接着在原来那条对话里继续生成。这个「继续」比想象中重要:改提示词重试是常态,每次都要重开一局会很烦。
几个具体决定
- 设置页全开放:API 地址、模型、尺寸、质量都能改。写死等于把这个 App 绑死在一家服务上。
- Glide 管缓存:生成图动辄 1–4MB,不做磁盘缓存,翻历史就是一场灾难。
- 错误要说人话:把 HTTP 状态码翻译成「key 无效」「余额不足」「内容被拒」「服务超时」,比抛一个
400有用得多。 - MIT 协议开源:代码在 GitHub 上,Release 里有 APK,想改自己拉。
做完之后
最花时间的不是写代码,是挨个试接口、记下谁在哪一步不兼容。这部分工作没有捷径,只能一个个撞。
如果你也想做一个:先只做文生图,跑通再说。图生图留到你真的需要它的时候——那时候你已经知道自己的 API 支持什么,五级降级可能只需要两级。