最近有個需求,后端給出的圖片地址并不是正常的 URL,而且需要一個接口去請求,但是返回的是 base64 數據流。這里不關心為啥要這么多,原因有很多,可能是系統的問題,也可能是能力問題。當然作為我們 Android 程序員,要緊的是如何解決這個問題。
首先我們拿到接口鏈接,我這次拿到的是這樣的:
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 的請求數據:
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@Overridepublic 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));}@Overridepublic boolean handles(@NonNull String model) {// 判斷是否是文件ID格式,這里簡單判斷不是URLreturn !model.startsWith("http") && !model.startsWith("data:");}// 工廠類,用于創建ModelLoaderpublic static class Factory implements ModelLoaderFactory<String, InputStream> {@NonNull@Overridepublic ModelLoader<String, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {return new Base64ApiModelLoader();}@Overridepublic void teardown() {// 清理資源}}// 數據獲取器,負責從API獲取Base64數據并轉換為InputStreamprivate 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;}@Overridepublic void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {if (isCancelled) {callback.onLoadFailed(new IOException("Cancelled"));return;}// 構建API請求URLString 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);}}}@Overridepublic void cleanup() {if (inputStream != null) {try {inputStream.close();} catch (IOException ignored) {// 忽略關閉錯誤}}}@Overridepublic void cancel() {isCancelled = true;}@NonNull@Overridepublic Class<InputStream> getDataClass() {return InputStream.class;}@NonNull@Overridepublic DataSource getDataSource() {return DataSource.REMOTE;}}
}
定義完成ModelLoader
之后,我們可能就要注冊ModelLoader
了。
@GlideModule
public class MyAppGlideModule extends AppGlideModule {@Overridepublic 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());}@Overridepublic boolean isManifestParsingEnabled() {return false;}
}
當然不能忘記需要有注解處理器
annotationProcessor 'com.github.bumptech.glide:compiler:4.15.1'
處理完之后,我們就可以這么調用了:
Glide.with(imageView.getContext()).load(fileId) // +.placeholder(R.mipmap.default_image).error(R.mipmap.default_image).into(imageView);
我們之前 load 方法中一直調用的是 url
, 這里就直接調用 fileId
即可。因為我們已經定義了
registry.append(String.class, InputStream.class, new Base64ApiModelLoader.Factory());
其他地方都不變,即可正常進行請求了。