← 返回首页

给安卓做一个 AI 绘图客户端:文生图容易,图生图才是坑

先说结论:文生图半天能跑通,图生图要花一周,剩下的时间全在处理「这家 API 和那家 API 不一样」。

这个 App 叫 Android-AI-Image-Studio,Java 写的,接任何兼容 OpenAI 格式的绘图接口。下面是我做完之后觉得值得记下来的部分。

为什么自己写一个

三个理由凑一起就够开工了:

结构:三层就够

技术栈没有花活:Java + AndroidX,页面用 Navigation Component 串,网络 OkHttp 4,JSON 用 Gson,图片交给 Glide。minSdk 定在 24(Android 7.0)——再往下要处理一堆兼容,收益不值。

分三层:

不引 Room 是因为一张表就够,JSON 文件更好调试——出问题时能直接打开看,而不是去翻 SQLite。

文生图:一条请求的事

POST /v1/images/generations,body 里三个关键字段:prompt、size、quality(或 n)。响应有两种形态,得都处理:

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 支持什么,五级降级可能只需要两级。

下一篇:从零开始 →