Android-网络图片下载工具类-三级缓存

一、前言:

之前写过一个简单的网络图片读取工具,读取网络图片,在Android应用中经常可见,在ListView、GridView这些控件中尤其可见。而为了提高用户体验,这些控件图片的读取,一般都是采用异步加载的方式,而使用缓存则是必不可少的环节。   现在网络上已经出现了很多功能丰富、使用简单的图片下载框架,例如universalimageloader,使用只需要初始化并且传入参数即可。这里我打算自己实现一个功能简单的图片下载工具类,采取了内存缓存+磁盘缓存+网络获取的三级缓存方式。

二、思路:

三级缓存,在网上查阅过很多资料,个人所知,有两种实现方式:

1、软引用+SD卡缓存+网络获取

2、Lrucache+DiskLruCache+网络获取

因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。所以本文的思路是:先从内存缓存中寻找需要的图片,如果内存中不存在该图片,再从磁盘缓存中查找,如果再查找为空,则开启网络获取。

三、相关技术:

这里主要使用第二种方式实现:

1、关于Lrucache:

Lrucache是Android自带的一个内存缓存类,专门用来做图片缓存处理的。它有一个特点,当缓存的图片达到了预先设定的值的时候,那么近期使用次数最少的图片就会被回收掉。

详细使用方法请自行百度。

2、关于DiskLruCache:

DiskLruCache是一个非Google官方编写,但获得官方认证的缓存类。它的作用是把图片(或者其他对象)保存到本地磁盘中,以便下次需要的时候,直接获取。

DiskLruCache并没有限制数据的缓存位置,可以自由地进行设定,但是通常情况下多数应用程序都会将缓存的位置选择为 /sdcard/Android/data/<application package>/cache 这个路径。选择在这个位置有两点好处:第一,这是存储在SD卡上的,因此即使缓存再多的数据也不会对手机的内置存储空间有任何影响,只要SD卡空间足够就行。第二,这个路径被Android系统认定为应用程序的缓存路径,当程序被卸载的时候,这里的数据也会一起被清除掉,这样就不会出现删除程序之后手机上还有很多残留数据的问题。

由于DiskLruCache并不是由Google官方编写的,所以这个类并没有被包含在Android API当中,我们需要将这个类从网上下载下来,然后手动添加到项目当中。DiskLruCache的源码在Google Source上,地址:android.googlesource.com/platform/libcore/+/jb-mr2-release/luni/src/main/java/libcore/io/DiskLruCache.java

详细使用方法自行百度。

四、ImageLoad实现类:

package com.zero.imageload;
 
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;
import android.util.LruCache;
 
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashSet;
import java.util.Set;
 
/**
 * @author Zero
 * @version 1.0
 * @date 2015/7/29
 */
public class ImageLoad {
 
 
    private static ImageLoad mImageLoad = null;
 
    private static String CacheName = "image";    //图片缓存文件名
    
    
    /**
     * 获取唯一实例
     * @return
     */
    public static ImageLoad getInstance(Context context) {
        if (mImageLoad == null) {
            mImageLoad = new ImageLoad(context.getApplicationContext());
        }
        return mImageLoad;
    }
 
    
    /**
     * ImageLoad停止所有的任务
     * 通常在退出活动时,用于取消下载任务
     */
    public void ImageLoadCancel(){
        cancelAllTasks();
    }
 
    /**
     * ImageLoad输出到日志
     * 通常到活动暂停时,用于刷新cache
     */
    public void ImageLoadPause(){
        fluchCache();
    }
 
 
    /**
     * ImageLoad获取磁盘缓存的大小
     */
    public long ImageLoadCacheSize(){
        return mDiskLruCache.size();
    }
 
    /**
     * ImageLoad清理内存缓存
     */
    public void ImageLoadCacheClean(){
        //清理内存缓存
        clearCache();
    }
 
    /**
     * ImageLoad磁盘缓存清理
     */
    public void ImageLoadDiskCacheClean(){
        try {
            mDiskLruCache.delete();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.gc();
    }
    
    /**
     * ImageLoad清理所有的数据
     */
    public void ImageLoadCacheAllClean(){
        //清理内存缓存
        clearCache();
        //清理磁盘缓存
        try {
            mDiskLruCache.delete();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.gc();
    }
    
    
    /**
     * 加载Bitmap对象。此方法会在LruCache中检查所有屏幕中可见的ImageView的Bitmap对象,
     * 如果发现任何一个ImageView的Bitmap对象不在缓存中,就会开启异步线程去下载图片。
     * @param imageUrl
     * @param bitmapListener
     */
    public void loadBitmap(String imageUrl,BitmapListener bitmapListener) {
 
        //开启一个新的异步任务  
        BitmapWorkerTask task = new BitmapWorkerTask(bitmapListener);
        //把任务加进任务集合
        taskCollection.add(task);
        //任务开始
        task.execute(imageUrl);
    }
 
    /**
     * 加载Bitmap对象
     * @param imageUrl
     * @param width
     * @param height
     * @param bitmapListener
     */
    public void loadBitmap(String imageUrl,int width,int height,BitmapListener bitmapListener){
        
    }
 
    /**
     * 加载Bitmap对象,由于图片占用内存过大,在这里直接跳过内存缓存,存到磁盘缓存
     * @param imageUrl
     * @param bitmapListener
     */
    public void loadBigBitmap(String imageUrl,BitmapListener bitmapListener){
        
    }
    
 
    /**
     * 记录所有正在下载或等待下载的任务。
     */
    private Set<BitmapWorkerTask> taskCollection;
    
    /**
     * 为每个任务定义一个接口
     */
    public interface BitmapListener{
        public void onFailure();
        public void onSuccess(Bitmap bitmap);
    }
 
    
    /**
     * 异步下载图片的任务。
     *
     * @author zero
     */
    class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {
        
        /**
         * 图片的URL地址
         */
        private String imageUrl;
 
        /**
         * 绑定监听器
         */
        private BitmapListener mBitmapListener;
 
        /**
         * 构造方法
         * @param bitmapListener
         */
        public BitmapWorkerTask(BitmapListener bitmapListener){
            this.mBitmapListener = bitmapListener;
        }
        
        /**
         * 异步类执行
         * @param params
         * @return
         */
        @Override
        protected Bitmap doInBackground(String... params) {
            //获取URL
            imageUrl = params[0];
            Bitmap bitmap;
            
            //先从缓存中获取
            bitmap = mMemoryCache.get(imageUrl);
            if(bitmap != null)
                return bitmap;
            
            //再从磁盘缓存中获取
            FileDescriptor fileDescriptor = null;
            FileInputStream fileInputStream = null;
            DiskLruCache.Snapshot snapShot = null;
            try {
                // 生成图片URL对应的key
                final String key = hashKeyForDisk(imageUrl);
                // 查找key对应的缓存
                snapShot = mDiskLruCache.get(key);
                if (snapShot == null) {
                    // 如果没有找到对应的缓存,则准备从网络上请求数据,并写入缓存
                    DiskLruCache.Editor editor = mDiskLruCache.edit(key);
                    if (editor != null) {
                        OutputStream outputStream = editor.newOutputStream(0);
                        if (downloadUrlToStream(imageUrl, outputStream)) {
                            editor.commit();
                        } else {
                            editor.abort();
                        }
                    }
                    // 缓存被写入后,再次查找key对应的缓存
                    snapShot = mDiskLruCache.get(key);
                }
                if (snapShot != null) {
                    fileInputStream = (FileInputStream) snapShot.getInputStream(0);
                    fileDescriptor = fileInputStream.getFD();
                }
                if (fileDescriptor != null) {
                    bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
                }
                if (bitmap != null) {
                    // 将Bitmap对象添加到内存缓存当中
                    addBitmapToMemoryCache(params[0], bitmap);
                }
                return bitmap;
                
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (fileDescriptor == null && fileInputStream != null) {
                    try {
                        fileInputStream.close();
                    } catch (IOException e) {
                    }
                }
            }
            return null;
        }
 
        /**
         * 异步类执行返回
         * @param bitmap
         */
        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            
            if(bitmap == null)
                mBitmapListener.onFailure();
            else mBitmapListener.onSuccess(bitmap);
            
            //移除任务
            taskCollection.remove(this);
        }
    }
 
    /**
     * 取消所有正在下载或等待下载的任务。
     */
    public void cancelAllTasks() {
        if (taskCollection != null) {
            for (BitmapWorkerTask task : taskCollection) {
                task.cancel(false);
            }
        }
    }
 
    /**
     * 构造方法,由于是单例,因此构造方法为私有
     */
    private ImageLoad(Context context) {
 
        // 初始化任务列表
    	taskCollection = new HashSet<BitmapWorkerTask>();
    	
        // 初始化内存缓存类
        // 获取应用程序最大可用内存
        int maxMemory = (int) Runtime.getRuntime().maxMemory();
        int cacheSize = maxMemory / 8;
        // 设置图片缓存大小为程序最大可用内存的1/8
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getByteCount();
            }
        };
        // 初始化磁盘缓存类
        try {
            // 获取图片缓存路径
            File cacheDir = getDiskCacheDir(context, CacheName);
            if (!cacheDir.exists()) {
                cacheDir.mkdirs();
            }
            // 创建DiskLruCache实例,初始化缓存数据
            mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
 
    /**
     * 图片缓存技术的核心类,用于缓存所有下载好的图片,在程序内存达到设定值时会将最少最近使用的图片移除掉。
     */
    private LruCache<String, Bitmap> mMemoryCache;
 
    /**
     * 将一张图片存储到LruCache中。
     *
     * @param key    LruCache的键,这里传入图片的URL地址。
     * @param bitmap LruCache的键,这里传入从网络上下载的Bitmap对象。
     */
    public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
        if (getBitmapFromMemoryCache(key) == null) {
            mMemoryCache.put(key, bitmap);
        }
    }
 
    /**
     * 从LruCache中获取一张图片,如果不存在就返回null。
     *
     * @param key LruCache的键,这里传入图片的URL地址。
     * @return 对应传入键的Bitmap对象,或者null。
     */
    public Bitmap getBitmapFromMemoryCache(String key) {
        return mMemoryCache.get(key);
    }
 
 
    /**
     * 删除bitmap
     *
     * @param key
     */
    public void deleteBitmapFromMemoryCache(String key) {
        mMemoryCache.remove(key);
    }
 
 
    /**
     * 清空内存缓存
     *
     * @return void
     */
    public void clearCache() {
        if (mMemoryCache != null) {
            if (mMemoryCache.size() > 0) {
                Log.d("CacheUtils", "mMemoryCache.size() " + mMemoryCache.size());
                mMemoryCache.evictAll();
                Log.d("CacheUtils", "mMemoryCache.size()" + mMemoryCache.size());
            }
            // 初始化内存缓存类
            // 获取应用程序最大可用内存
            int maxMemory = (int) Runtime.getRuntime().maxMemory();
            int cacheSize = maxMemory / 8;
            // 设置图片缓存大小为程序最大可用内存的1/8
            mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
                @Override
                protected int sizeOf(String key, Bitmap bitmap) {
                    return bitmap.getByteCount();
                }
            };
        }
    }
 
    /**
     * 图片硬盘缓存核心类, 用于缓存所有下载好的图片到本地
     */
    private DiskLruCache mDiskLruCache;
 
    /**
     * 获取当前应用程序的版本号。
     */
    public int getAppVersion(Context context) {
        try {
            PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
            return info.versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return 1;
    }
 
    /**
     * 根据传入的uniqueName获取硬盘缓存的路径地址。
     */
    public File getDiskCacheDir(Context context, String uniqueName) {
        String cachePath;
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) {
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            cachePath = context.getCacheDir().getPath();
        }
        return new File(cachePath + File.separator + uniqueName);
    }
 
    /**
     * 使用MD5算法对传入的key进行加密并返回。
     */
    public String hashKeyForDisk(String key) {
        String cacheKey;
        try {
            final MessageDigest mDigest = MessageDigest.getInstance("MD5");
            mDigest.update(key.getBytes());
            cacheKey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            cacheKey = String.valueOf(key.hashCode());
        }
        return cacheKey;
    }
 
    private String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(0xFF & bytes[i]);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();
    }
 
    /**
     * 将缓存记录同步到journal文件中。
     */
    public void fluchCache() {
        if (mDiskLruCache != null) {
            try {
                mDiskLruCache.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
 
    /**
     * 建立HTTP请求,并获取Bitmap对象。
     *
     * @param urlString 图片的URL地址
     * @return 解析后的Bitmap对象
     */
    private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
        HttpURLConnection urlConnection = null;
        BufferedOutputStream out = null;
        BufferedInputStream in = null;
        try {
            final URL url = new URL(urlString);
            urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);
            out = new BufferedOutputStream(outputStream, 8 * 1024);
            int b;
            while ((b = in.read()) != -1) {
                out.write(b);
            }
            return true;
        } catch (final IOException e) {
            e.printStackTrace();
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (final IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }
}

五、使用方法:

ImageLoad.getInstance(context).loadBitmap(imageurl, new BitmapListener() {
				
				@Override
				public void onSuccess(Bitmap bitmap) {
					// TODO 自动生成的方法存根
					ImageView image = (ImageView) v.findViewById(R.id.image);
					image.setImageBitmap(bitmap);
				}
				
				@Override
				public void onFailure() {
					// TODO 自动生成的方法存根
					
				}
			});

六、不足之处

这个工具类目前只提供了一个图片下载方法,而且尚未做一些防止OOM发生的操作,

0 条评论
发表一条评论