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());

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

相关推荐
兔子蟹子18 分钟前
JAVA中Spring全局异常处理@ControllerAdvice解析
java·spring
prinrf('千寻)21 分钟前
项目右键没有add as maven project选项
java·maven
爱编程的鱼24 分钟前
C# 继承详解
开发语言·c#
工业互联网专业25 分钟前
基于springboot+vue的健康健身追踪系统
java·vue.js·spring boot·毕业设计·源码·课程设计·健康健身追踪系统
MyhEhud27 分钟前
kotlin flatMap 变换函数的特点和使用场景
开发语言·windows·kotlin
杰仔正在努力29 分钟前
Java + Seleium4.X + TestNG自动化技术
java·开发语言·自动化
神仙别闹35 分钟前
基于C#窗体+GDI+绘图实现分形树
开发语言·c#
爱凤的小光1 小时前
图漾官网Sample_V1版本C++语言完整参考例子---单相机版本
开发语言·c++·数码相机
weixin_307779131 小时前
Azure Synapse Dedicated SQL pool企业权限管理
开发语言·数据仓库·sql·azure·etl
三思而后行,慎承诺1 小时前
Kotlin 常见问题
开发语言·面试·kotlin