Glide 如何加载远程 Base64 图片

最近有个需求,后端给出的图片地址并不是正常的 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());

其他地方都不变,即可正常进行请求了。

相关推荐
冻柠檬飞冰走茶几秒前
PTA基础编程题目集 7-17 爬动的蠕虫(C语言实现)
c语言·开发语言·数据结构·算法
咯哦哦哦哦2 分钟前
qt creator x86交叉编译arm 配置
开发语言·qt
Tattoo_Welkin8 分钟前
IDEA 中常用操作记载
java·elasticsearch·intellij-idea
音符犹如代码13 分钟前
Arthas classloader + sc 实战:JVM 类加载与手动加载
java·jvm·spring boot
乐启国际旅行社有限公司29 分钟前
Java线程池实战:文旅系统批量任务性能优化(团期生成/数据导出)
java·开发语言·性能优化
Listen·Rain38 分钟前
用AI开发出一个AI
java·人工智能·spring boot·tomcat·intellij-idea·mybatis·visual studio
AI人工智能+电脑小能手39 分钟前
【大白话说Java面试题 第203题】【09_Zookeeper篇】第4题:ZooKeeper 的节点类型有哪些?
java·zookeeper·分布式锁·分布式协调·znode
宸津-代码粉碎机1 小时前
告别手动Jar部署!生产级无损热部署方案,彻底解决OOM与更新失效问题
java·大数据·开发语言·人工智能·python
米码收割机1 小时前
【移动】线上购物移动端网站(源码+文档)【独一无二】
java·开发语言·前端·python·django
小肝一下1 小时前
多态(上)
android·开发语言·c++·vscode·多态·面向对象·伊蕾娜