首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計疑難解答

Android圖片加載框架設計:實現網絡斷開時的離線緩存與自動恢復機制

     編輯:程式設計疑難解答

需求場景

App 圖片列表/詳情頁,網絡正常時加載網絡圖,斷網/弱網時自動展示本地緩存,網絡恢復自動重試加載高清圖,用戶無感知。

架構分層


+------------------+
|   View Layer     |  ImageView / RecyclerView
+------------------+
        |
+------------------+
|  Request Layer   |  RequestBuilder (Glide/Picasso/Coil/Fresco API)
+------------------+
        |
+------------------+
|  Engine Layer    |  Dispatcher → Cache → Network → Decode → Transform
+------------------+
        |
+------------------+
|  Cache Layer     |  MemoryCache (LruCache) + DiskCache (DiskLruCache)
+------------------+
        |
+------------------+
|  Network Layer   |  OkHttp / HttpURLConnection (Interceptor, Retry)
+------------------+

核心機制:DiskCache + 僅緩存策略

1. Glide 實現 (行業標準)


// 依賴
implementation 'com.github.bumptech.glide:glide:4.16.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'

// 全局配置
@GlideModule
public class MyAppGlideModule extends AppGlideModule {
    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
        // 磁盤緩存 200MB
        builder.setDiskCache(new InternalCacheDiskCacheFactory(context, "image_cache", 200 * 1024 * 1024));
        // 內存緩存 50% 可用內存
        builder.setMemoryCache(new LruResourceCacheCalculator(context));
    }
}

// 使用:自動支持離線緩存
Glide.with(fragment)
    .load(url)
    .diskCacheStrategy(DiskCacheStrategy.ALL) // 緩存原圖+轉換後
    .placeholder(R.drawable.placeholder)
    .error(R.drawable.error)
    .into(imageView);

✅ `DiskCacheStrategy.ALL`:下載後同時緩存**原始數據**和**轉換後圖片** (縮略、裁剪)。斷網時直接讀取磁盤緩存解碼顯示,無需重新下載轉換。

2. 手動實現核心邏輯 (教學/輕量級)


public class ImageLoader {
    private LruCache memoryCache;
    private DiskLruCache diskCache;
    private ExecutorService executor = Executors.newFixedThreadPool(3);
    private OkHttpClient client = new OkHttpClient.Builder()
        .retryOnConnectionFailure(true)
        .build();

    public void loadImage(String url, ImageView view) {
        // 1. 內存緩存
        Bitmap bitmap = memoryCache.get(url);
        if (bitmap != null) { view.setImageBitmap(bitmap); return; }

        // 2. 磁盤緩存 (異步)
        executor.execute(() -> {
            Bitmap diskBitmap = getFromDiskCache(url);
            if (diskBitmap != null) {
                putToMemoryCache(url, diskBitmap);
                runOnUiThread(() -> view.setImageBitmap(diskBitmap));
                return;
            }

            // 3. 網絡下載
            Request request = new Request.Builder().url(url).build();
            try (Response response = client.newCall(request).execute()) {
                if (response.isSuccessful()) {
                    byte[] data = response.body().bytes();
                    Bitmap netBitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
                    
                    // 寫入雙層緩存
                    putToMemoryCache(url, netBitmap);
                    putToDiskCache(url, data); // 存原始字節
                    
                    runOnUiThread(() -> view.setImageBitmap(netBitmap));
                }
            } catch (IOException e) {
                // 4. 彻底失敗:展示佔位/錯誤圖
                runOnUiThread(() -> view.setImageResource(R.drawable.error));
            }
        });
    }
}

關鍵策略對比

策略DiskCacheStrategy.NONEDATARESOURCEALL (推薦)
原始數據❌✅❌✅
轉換後圖片❌❌✅✅
斷網展示原圖❌✅ (需重新解碼)❌✅ (最快)
斷網展示縮略圖❌❌✅✅
磁盤佔用0中中大 (2倍)

進階:網絡恢復自動重試高清圖


// Glide 自動重試機制:連接失敗會自動重試 3 次 (默認)
// 自定義重試策略
@GlideModule
public class MyAppGlideModule extends AppGlideModule {
    @Override
    public void registerComponents(Context context, Glide glide, Registry registry) {
        OkHttpClient client = new OkHttpClient.Builder()
            .retryOnConnectionFailure(true)
            .addInterceptor(chain -> {
                Request request = chain.request().newBuilder()
                    .header("Accept", "image/webp,image/*")
                    .build();
                return chain.proceed(request);
            })
            .build();
        registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(client));
    }
}

// 業務層監聽網絡變化,主動重載
ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
NetworkCallback callback = new NetworkCallback() {
    @Override public void onAvailable(Network network) {
        // 網絡恢復:重新加載列表可見項
        adapter.notifyDataSetChanged(); // 或 recyclerView.getLayoutManager().findVisibleViews()
    }
};
cm.registerDefaultNetworkCallback(callback);

避坑指南

  • ⚠️ **RecyclerView 復用導致圖片錯位**:`Glide.with(fragment).load(url).into(view)` 正確綁定生命周期,自動取消舊請求。
  • ⚠️ **大圖 OOM**:必須 `.override(width, height)` 或 `RequestOptions().downsample()` 縮放加載,不可直接加載 4000x3000 原圖。
  • ⚠️ **磁盤緩存未生效**:檢查 `DiskCacheStrategy`、OkHttp `Cache-Control` 頭 (服務端 `no-store` 會禁用緩存)、Glide `signature` 是否變化導致緩存失效。
  • ⚠️ **內存洩漏**:`Glide.with(context)` 用 ApplicationContext 會導致請求不隨 Activity 銷毀而取消,**必須用 `Glide.with(fragment/activity/view)`**。

總結

離線圖片加載核心是 **`DiskCacheStrategy.ALL` + 適配器復用綁定生命周期**。生產項目**直接用 Glide/Coil/Fresco 成熟框架**,配置緩存大小、策略、重試攔截器,即可獲得生產級離線體驗。手寫框架僅供教學,邊界情況 (GIF、WebP、EXIF 旋轉、內存抖動) 極難處理完備。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved