Android性能優化之實現雙緩存的圖片異步加載工具(LruCache+SoftReference)

jopen 9年前發布 | 22K 次閱讀 Android開發 移動開發 LRUCache

之前在郭大神的博客看到使用LruCache算法實現圖片緩存的.這里仿效他的思路,自己也寫了一個. 并加入ConcurrentHashMap<String, SoftReference<Bitmap>>去實現二級緩存,因為ConcurrentHashMap是多個鎖的線程安全,支持高并發.很適合這種頻繁訪問讀取內存的操作.


下面整個思路是,使用了系統提供的LruCache類做一級緩存, 大小為運行內存的1/8,當LruCache容量要滿的時候,會自動將系統移除的圖片放到二級緩存中,但為了避免OOM的問題,這里將 SoftReference軟引用加入來,當系統快要OOM的時候會自動清除里面的圖片內存,當然內存充足時就會繼續保存這些二級緩存的圖片.強調一點, 不要用SoftReference去做一級緩存,現在的java中垃圾回收加強了對SoftReference軟引用的回收機制,它只適合臨時的保存一些數據緩存,并不適合長期的(相對臨時而言,并不是真正的長期).


直接上代碼,拿來即用哦:

/**

  • Created on 3/11/2015
  • <br>圖片異步加載工具(支持本地圖片加載,網絡圖片URL和項目內圖片資源加載)
  • <br>支持雙緩存: LruCache和SoftReference
  • @author Mr.Et / public class ImageLoadManager { / 圖片源類型: 文件,網絡,資源ID / public enum IMAGE_LOAD_TYPE {

     FILE_PATH,FILE_URL,FILE_RESOURCE_ID
    

    }

    private String TAG = "ImageLoadManager...";

    private Context context;

    private Set<ImageLoadTask> taskCollection;

    / 最大內存 / final static int maxCacheSize = (int)(Runtime.getRuntime().maxMemory() / 8);

    / 建立線程安全,支持高并發的容器 / private static ConcurrentHashMap<String, SoftReference<Bitmap>> currentHashmap

     = new ConcurrentHashMap<String, SoftReference<Bitmap>>();
public ImageLoadManager(Context context)
{
    super();
    this.context = context;
    taskCollection = new HashSet<ImageLoadManager.ImageLoadTask>();
}

private static LruCache<String, Bitmap> BitmapMemoryCache = new LruCache<String, Bitmap>(maxCacheSize)
{
    @Override
    protected int sizeOf(String key, Bitmap value)
    {
        if(value != null)
        {
            return value.getByteCount();
            //return value.getRowBytes() * value.getHeight();   //舊版本的方法
        }
        else
        {
            return 0;
        }
    }

    //這個方法當LruCache的內存容量滿的時候會調用,將oldValue的元素移除出來騰出空間給新的元素加入
    @Override
    protected void entryRemoved(boolean evicted, String key,Bitmap oldValue, Bitmap newValue)
    {
        if(oldValue != null)
        {
            // 當硬引用緩存容量已滿時,會使用LRU算法將最近沒有被使用的圖片轉入軟引用緩存    
            currentHashmap.put(key, new SoftReference<Bitmap>(oldValue));
        }
    }

};

/**
 * 針對提供圖片資源ID來顯示圖片的方法
 * @param loadType  圖片加載類型
 * @param imageResourceID   圖片資源id
 * @param imageView 顯示圖片的ImageView
 */
public void setImageView(IMAGE_LOAD_TYPE loadType, int imageResourceID, ImageView imageView)
{
    if(loadType == IMAGE_LOAD_TYPE.FILE_RESOURCE_ID)
    {

// if(ifResourceIdExist(imageResourceID)) // { // imageView.setImageResource(imageResourceID); //
// }else{ //映射無法獲取該圖片,則顯示默認圖片 // imageView.setImageResource(R.drawable.pic_default); // } try { imageView.setImageResource(imageResourceID); return; } catch (Exception e) { Log.e(TAG, "Can find the imageID of "+imageResourceID); e.printStackTrace(); } //默認圖片 imageView.setImageResource(R.drawable.pic_default); } }

/**
 * 針對提供圖片文件鏈接或下載鏈接來顯示圖片的方法
 * @param loadType  圖片加載類型
 * @param imageFilePath 圖片文件的本地文件地址或網絡URL的下載鏈接
 * @param imageView 顯示圖片的ImageView
 */
public void setImageView(IMAGE_LOAD_TYPE loadType, String imageFilePath, ImageView imageView)
{
    if(imageFilePath == null || imageFilePath.trim().equals(""))
    {
        imageView.setImageResource(R.drawable.pic_default);

    }else{
        Bitmap bitmap = getBitmapFromMemoryCache(imageFilePath);
        if(bitmap != null)
        {
            imageView.setImageBitmap(bitmap);
        }
        else
        {
            imageView.setImageResource(R.drawable.pic_default);
            ImageLoadTask task = new ImageLoadTask(loadType, imageView);
            taskCollection.add(task);
            task.execute(imageFilePath);
        }
    }
}

/**
 * 從LruCache中獲取一張圖片,如果不存在就返回null
 * @param key  鍵值可以是圖片文件的filePath,可以是圖片URL地址
 * @return Bitmap對象,或者null
 */
public Bitmap getBitmapFromMemoryCache(String key)
{   
    try 
    {
        if(BitmapMemoryCache.get(key) == null)
        {
            if(currentHashmap.get(key) != null)
            {
                return currentHashmap.get(key).get();
            }
        }
        return BitmapMemoryCache.get(key);

    } catch (Exception e) {
        e.printStackTrace();
    }
    return BitmapMemoryCache.get(key);
}

/**
 * 將圖片放入緩存
 * @param key
 * @param bitmap
 */
private void addBitmapToCache(String key, Bitmap bitmap)
{
    BitmapMemoryCache.put(key, bitmap);
}


/**
 * 圖片異步加載
 * @author Mr.Et
 *
 */
private class ImageLoadTask extends AsyncTask<String, Void, Bitmap>
{
    private String imagePath;
    private ImageView imageView;
    private IMAGE_LOAD_TYPE loadType;

    public ImageLoadTask(IMAGE_LOAD_TYPE loadType , ImageView imageView)
    {
        this.loadType = loadType;
        this.imageView = imageView;
    }

    @Override
    protected Bitmap doInBackground(String...params)
    {
        imagePath = params[0];
        try 
        {
            if(loadType == IMAGE_LOAD_TYPE.FILE_PATH)
            {
                if(new File(imagePath).exists())
                {   //從本地FILE讀取圖片
                    BitmapFactory.Options opts = new BitmapFactory.Options();
                    opts.inSampleSize = 2;
                    Bitmap bitmap = BitmapFactory.decodeFile(imagePath, opts);
                    //將獲取的新圖片放入緩存
                    addBitmapToCache(imagePath, bitmap);
                    return bitmap;
                }
                return null;
            }
            else if(loadType == IMAGE_LOAD_TYPE.FILE_URL)
            {   //從網絡下載圖片
                byte[] datas = getBytesOfBitMap(imagePath);
                if(datas != null)
                {

// BitmapFactory.Options opts = new BitmapFactory.Options(); // opts.inSampleSize = 2; // Bitmap bitmap = BitmapFactory.decodeByteArray(datas, 0, datas.length, opts); Bitmap bitmap = BitmapFactory.decodeByteArray(datas, 0, datas.length); addBitmapToCache(imagePath, bitmap); return bitmap; } return null; }

        } catch (Exception e) {
            e.printStackTrace();
            FileUtils.saveExceptionLog(e);
            //可自定義其他操作
        }
        return null;
    }

    @Override
    protected void onPostExecute(Bitmap bitmap)
    {
        try 
        {
            if(imageView != null)
            {
                if(bitmap != null)
                {
                    imageView.setImageBitmap(bitmap);
                }
                else
                {
                    Log.e(TAG, "The bitmap result is null...");
                }
            }
            else
            {
                Log.e(TAG, "The imageView is null...");
                //獲取圖片失敗時顯示默認圖片
                imageView.setImageResource(R.drawable.pic_default);
            }

        } catch (Exception e) {
            e.printStackTrace();
            FileUtils.saveExceptionLog(e);
        }
    }


}


/**
 * InputStream轉byte[]
 * @param inStream
 * @return
 * @throws Exception
 */
private byte[] readStream(InputStream inStream) throws Exception{  
    ByteArrayOutputStream outStream = new ByteArrayOutputStream();  
    byte[] buffer = new byte[2048];  
    int len = 0;  
    while( (len=inStream.read(buffer)) != -1){  
        outStream.write(buffer, 0, len);  
    }  
    outStream.close();  
    inStream.close();  
    return outStream.toByteArray();  
}

/**
 * 獲取下載圖片并轉為byte[]
 * @param urlStr
 * @return
 */
private byte[] getBytesOfBitMap(String imgUrl){
    try {
        URL url = new URL(imgUrl);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setConnectTimeout(10 * 1000);  //10s
        conn.setReadTimeout(20 * 1000);
        conn.setRequestMethod("GET");  
        conn.connect();
        InputStream in = conn.getInputStream();
        return readStream(in);
    } catch (IOException e) {
        e.printStackTrace();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

/**
 * 該資源ID是否有效
 * @param resourceId 資源ID
 * @return
 */
private boolean ifResourceIdExist(int resourceId)
{
    try 
    {
        Field field = R.drawable.class.getField(String.valueOf(resourceId));
        Integer.parseInt(field.get(null).toString());
        return true;

    } catch (Exception e) {
        e.printStackTrace();
    } 
    return false;
}

/**
 * 取消所有任務
 */
public void cancelAllTask()
{
    if(taskCollection != null){
        for(ImageLoadTask task : taskCollection)
        {
            task.cancel(false);
        }
    }
}


}</pre>

In addition, 如果需要更加完美的體驗,還可以加入第三級的緩存機制, 比如將圖片緩存到本地的磁盤存儲空間中.但是又不想這些緩存在本地的圖片被其他應用掃描到或者被用戶看到怎么辦? 這里有幾個思路, 比如將圖片用加密算法轉為字符串存儲,或者將圖片轉為自定義格式的未知文件去放在隱蔽的地方(很多應用都采取了這種方式). 這個不妨自己去嘗試實現哦~

作者:stzy00 發表于2015/3/11 0:32:42 原文鏈接

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!