最近有个需求,后端给出的图片地址并不是正常的 URL,而且需要一个接口去请求,但是返回的是 base64 数据流。这里不关心为啥要这么多,原因有很多,可能是系统的问题,也可能是能力问题。当然作为我们 Android 程序员,要紧的是如何解决这个问题。
首先我们拿到接口链接,我这次拿到的是这样的:
bash
https://www.example.com/cdn/attach/{fileId}/base64
这里的{fileId}
是指图片的 id,那么正常的图片地址可以理解为:
https://www.example.com/cdn/attach/1000/base64
https://www.example.com/cdn/attach/1002/base64
熟悉 Glide 加载逻辑的人,应该很熟悉,这种方式可能需要我们自定义ModelLoader
来解决问题,们可以让 Glide 将 API 接口当作一种图片源来处理,就像处理普通的图片 URL 一样。
实现方案
我们可以自定义 Base64ApiModelLoader 来处理 base64 的请求数据:
java
public class Base64ApiModelLoader implements ModelLoader<String, InputStream> {
private static final String BASE_URL = "https://your-api-base-url/";
private static final String API_PATH = "cdn/attach/";
private final OkHttpClient okHttpClient;
public Base64ApiModelLoader() {
this.okHttpClient = new OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.build();
}
@Nullable
@Override
public LoadData<InputStream> buildLoadData(@NonNull String fileId, int width, int height, @NonNull Options options) {
// 创建缓存键,使用文件ID作为唯一标识
Key key = new ObjectKey(API_PATH + fileId);
// 返回加载数据,包含缓存键和数据获取器
return new LoadData<>(key, new Base64ApiFetcher(fileId, okHttpClient));
}
@Override
public boolean handles(@NonNull String model) {
// 判断是否是文件ID格式,这里简单判断不是URL
return !model.startsWith("http") && !model.startsWith("data:");
}
// 工厂类,用于创建ModelLoader
public static class Factory implements ModelLoaderFactory<String, InputStream> {
@NonNull
@Override
public ModelLoader<String, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
return new Base64ApiModelLoader();
}
@Override
public void teardown() {
// 清理资源
}
}
// 数据获取器,负责从API获取Base64数据并转换为InputStream
private static class Base64ApiFetcher implements DataFetcher<InputStream> {
private final String fileId;
private final OkHttpClient okHttpClient;
private InputStream inputStream;
private volatile boolean isCancelled;
Base64ApiFetcher(String fileId, OkHttpClient okHttpClient) {
this.fileId = fileId;
this.okHttpClient = okHttpClient;
}
@Override
public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
if (isCancelled) {
callback.onLoadFailed(new IOException("Cancelled"));
return;
}
// 构建API请求URL
String apiUrl = BASE_URL + API_PATH + fileId + "/base64";
Request request = new Request.Builder()
.url(apiUrl)
.build();
try {
// 执行请求
Response response = okHttpClient.newCall(request).execute();
if (!response.isSuccessful()) {
callback.onLoadFailed(new IOException("Failed to load Base64 data: " + response.code()));
return;
}
// 解析响应体
ResponseBody responseBody = response.body();
if (responseBody == null) {
callback.onLoadFailed(new IOException("Empty response"));
return;
}
// 解析JSON响应
String jsonString = responseBody.string();
JSONObject jsonObject = new JSONObject(jsonString);
// 检查响应码
int code = jsonObject.optInt("code", -1);
if (code != 200) {
callback.onLoadFailed(new IOException("API error: " + jsonObject.optString("message", "Unknown error")));
return;
}
// 获取Base64数据
String base64Data = jsonObject.optString("data", "");
if (base64Data.isEmpty()) {
callback.onLoadFailed(new IOException("Empty Base64 data"));
return;
}
// 处理可能存在的Data URI前缀
if (base64Data.contains(",")) {
base64Data = base64Data.split(",")[1];
}
// 解码Base64数据
byte[] imageBytes = Base64.decode(base64Data, Base64.DEFAULT);
// 创建输入流
inputStream = new ByteArrayInputStream(imageBytes);
// 回调成功
callback.onDataReady(inputStream);
} catch (IOException | JSONException | IllegalArgumentException e) {
if (!isCancelled) {
callback.onLoadFailed(e);
}
}
}
@Override
public void cleanup() {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException ignored) {
// 忽略关闭错误
}
}
}
@Override
public void cancel() {
isCancelled = true;
}
@NonNull
@Override
public Class<InputStream> getDataClass() {
return InputStream.class;
}
@NonNull
@Override
public DataSource getDataSource() {
return DataSource.REMOTE;
}
}
}
定义完成ModelLoader
之后,我们可能就要注册ModelLoader
了。
java
@GlideModule
public class MyAppGlideModule extends AppGlideModule {
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
super.registerComponents(context, glide, registry);
// 注册我们的自定义ModelLoader,用于处理Base64 API请求
registry.append(String.class, InputStream.class, new Base64ApiModelLoader.Factory());
}
@Override
public boolean isManifestParsingEnabled() {
return false;
}
}
当然不能忘记需要有注解处理器
groovy
annotationProcessor 'com.github.bumptech.glide:compiler:4.15.1'
处理完之后,我们就可以这么调用了:
java
Glide.with(imageView.getContext())
.load(fileId) // +
.placeholder(R.mipmap.default_image)
.error(R.mipmap.default_image)
.into(imageView);
我们之前 load 方法中一直调用的是 url
, 这里就直接调用 fileId
即可。因为我们已经定义了
java
registry.append(String.class, InputStream.class, new Base64ApiModelLoader.Factory());
其他地方都不变,即可正常进行请求了。