Android图片缓存处理

Lrucache类 内存缓存

LruCache<Key,Values>以键-值对的形式存储(内部定义了一个LinkedHashMap<Key,Values>)数据,通过new LruCache(int size)实例化,参数使指定分配给LruCache的缓存大小。

LruCache缓存大小设置

对于分配给LruCache的缓存大小,可以直接指定固定的数值,但是更好的做法应该是通过获取最大内存(int)Runtime.getRuntime.maxMemory,然后通过返回的最大内存/int n的大小动态分配给LruCache。

LruCache的存储和读取

LruCache是以为键值对形式存储数据,所以它的读写方法都和HashMap<Key,Values>一样,都可以通过key操作。

存储

LruCache.put(Key,Values)

读取

LruCache.get(Key)

通过LruCache缓存从网络读取的图片资源

在开发中,经常会遇到请求网络,从网络读取图片资源的时候,如果每次都去请求读取,很浪费资源(都会开辟子线程进行耗时操作),这样也会影响用户的体验。那么我们应该想到的是,启动app后,只从网络中读取一次资源,之后如果还需要同样的操作,就直接通过缓存读取,这样就可以通过LruCache类来进行操作。

构造一个工具类,用来存储图片到缓存和从缓存中读取图片
public class CustomLruCache {
    private LruCache<String, Bitmap> stringBitmapLruCache;
    int maxMemory = (int) Runtime.getRuntime().maxMemory();//获取最大内存
    int cacheSize = maxMemory / 16;//大小为最大内存的1/16
    private static CustomLruCache customLruCache;

    /**
     * 私有化构造方法
     */
    private CustomLruCache() {
        stringBitmapLruCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount();
            }
        };
    }

    /**
     * 单例模式获取实例,保证只有一个CustomLruCache对象,同时保证只有一个CustomLruCache.stringBitmapLruCache
     *
     * @return
     */
    public static CustomLruCache getInstance() {
        if (customLruCache == null) {
            customLruCache = new CustomLruCache();
        }
        return customLruCache;
    }

    public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
        if (getBitmapFromMemoryCache(key) != bitmap)//如果缓存中不存在bitmap,就存入缓存
            stringBitmapLruCache.put(key, bitmap);
    }

    public Bitmap getBitmapFromMemoryCache(String key) {
        return stringBitmapLruCache.get(key);
    }
}
从网络读取图片,存储到缓存用昨天学过的AsyncTask异步任务类来处理逻辑
AsyncTask<String, Void, Bitmap> stringVoidBitmapAsyncTask = new AsyncTask<String, Void, Bitmap>() {
            @Override
            protected Bitmap doInBackground(String... params) {
                Bitmap bitmap = null;
                try {
                    CustomLruCache customLruCache = CustomLruCache.getInstance();
                    bitmap = customLruCache.getBitmapFromMemoryCache(params[0]);
                    //先从缓存中读取图片,如果缓存中不存在,再请求网络,从网络读取图片添加至LruCache中
                    //启动app后第一次bitmap为null,会先从网络中读取添加至LruCache,如果app没销毁,再执行读取图片操作时
                    //就会优先从缓存中读取
                    if (bitmap == null) {
                        //从网络中读取图片数据
                        URL url = new URL(params[0]);
                        bitmap = BitmapFactory.decodeStream(url.openStream());
                        //添加图片数据至LruCache
                        customLruCache.addBitmapToMemoryCache(params[0], bitmap);
                    }
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return bitmap;
            }

            @Override
            protected void onPostExecute(Bitmap bitmap) {
                super.onPostExecute(bitmap);
                imageView.setImageBitmap(bitmap);
            }
        };
        stringVoidBitmapAsyncTask.execute(imageURL);


文件缓存-第三方类DiskLruCache

利用DiskLruCache从网络上获取到之后都会存入到本地缓存中,因此即使手机在没有网络的情况下依然能够加载显示图片数据。DiskLruCache存储的位置没有限制,但是一般选择存储在context.ExternolStorageCacheDir(),即这个手机的外部存储这个app的私有区域,即/sdcard/Android/data/应用包名/cache,因为是存储在外部存储私有区域,当app被卸载时,这部分的内容会被一起清除。


1、使用DiskLruCache

实例化DiskLruCache是通过 DiskLruCache.open(File directory, int appVersion, int valueCount, long maxSize),四个参数分别:为directory缓存的路径;appVersion 应用版本;alueCount 指定同一个key可以对应多少个缓存文件,一般指定为1;maxSize 指定可以缓存多少字节的数据。

(1)directory 缓存路径

public File getDiskCacheDir(Context context, String uniqueName) {
        String cachePath;
        if (isExternalStorageWritable()) {
            cachePath = context.getExternalCacheDir().getPath();//如果挂载了sdcard,获取外部存储私有区域路径
        } else {
            cachePath = context.getCacheDir().getPath();//如果没有挂载sdcard,则获取内部存储缓存区域
        }
        return new File(cachePath + File.separator + uniqueName);
    }

其中isExternalStorageWritable()是检测手机是否挂在sdcard。

private boolean isExternalStorageWritable() {
        String state = Environment.getExternalStorageState();
        if (state.equals(Environment.MEDIA_MOUNTED)) {
            return true;//挂载了sdcard,返回真
        } else {
            return false;//否则返回假
        }
    }

因为要对外部存储区域进行读写操作,所以要在androidManifest中添加相应的权限

 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

(2)appVersion 应用版本

 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;
    }

当版本号变更时,缓存路径下所有数据会被清除,因为DiskLruCache认为当版本更新时,所有数据应从网络重新获取

(3)alueCount Key对应的缓存文件个数

DiskLruCache从缓存读取文件和写入文件到缓存时是通过 key来识别的,所以一般是指定alueCountKey为1。

(4)maxSize 缓存存储内容的大小

可以自己指定


通过上面的方法可以,这下可以完整的实例化一个DiskLruCache

 private DiskLruCacheHelper(Context context) {
        try {
            File cacheDir = getDiskCacheDir(context, "bitmap");
            //如果文件不存在,则创建
            if (!cacheDir.exists()) {
                cacheDir.mkdirs();
            }
            mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

2、通过DiskLruCache存储和读取图片


为了方便操作,我们可以封装一个类来进行操作

public class DiskLruCacheHelper {
    DiskLruCache mDiskLruCache = null;
    static DiskLruCacheHelper diskLruCacheHelper;

    private DiskLruCacheHelper(Context context) {
        try {
            File cacheDir = getDiskCacheDir(context, "bitmap");
            //如果文件不存在,则创建
            if (!cacheDir.exists()) {
                cacheDir.mkdirs();
            }
            mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static DiskLruCacheHelper getInstance(Context context) {
        if (diskLruCacheHelper == null)
            diskLruCacheHelper = new DiskLruCacheHelper(context);
        return diskLruCacheHelper;
    }

    public File getDiskCacheDir(Context context, String uniqueName) {
        String cachePath;
        if (isExternalStorageWritable()) {
            cachePath = context.getExternalCacheDir().getPath();//如果挂载了sdcard,获取外部存储私有区域路径
        } else {
            cachePath = context.getCacheDir().getPath();//如果没有挂载sdcard,则获取内部存储缓存区域
        }
        return new File(cachePath + File.separator + uniqueName);
    }

    /**
     * 检查外部存储是否可用
     *
     * @return
     */
    private boolean isExternalStorageWritable() {
        String state = Environment.getExternalStorageState();
        if (state.equals(Environment.MEDIA_MOUNTED)) {
            return true;//挂载了sdcard,返回真
        } else {
            return false;//否则返回假
        }
    }

    /**
     * 获取应用版本号
     * 当版本号改变,缓存路径下存储的所有数据都会被清除掉,因为DiskLruCache认为
     * 当应用程序有版本更新的时候,所有的数据都应该从网上重新获取。
     *
     * @param context
     * @return
     */
    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;
    }

    /**
     * 写入图片数据到文件缓存
     *
     * @param imageUrl
     * @param bitmap
     */
    public void writeToCache(String imageUrl, Bitmap bitmap) {
        try {
            String key = hashKeyForDisk(imageUrl);
            DiskLruCache.Editor editor = mDiskLruCache.edit(key);
            if (editor != null) {
                OutputStream outputStream = editor.newOutputStream(0);
                if (bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)) {
                    editor.commit();
                } else {
                    editor.abort();
                }

            }
            mDiskLruCache.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 从缓存读取数据
     *
     * @param imageUrl
     * @return
     */

    public Bitmap readFromCache(String imageUrl) {
        Bitmap bitmap = null;
        try {
            String key = hashKeyForDisk(imageUrl);
            DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
            if (snapShot != null) {//如果文件存在,读取数据转换为Bitmap对象
                InputStream is = snapShot.getInputStream(0);
                bitmap = BitmapFactory.decodeStream(is);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bitmap;
    }

    /**
     * 将文件名转换成"MD5"编码
     *
     * @param key
     * @return
     */
    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();
    }

}

下面是从网络获取一张图片显示到ImageView上,第一次进入app时从网络获取,并存储至外部存储私有区域,以后(即使没有网络的情况)进入若相同的缓存文件存在,则直接从缓存读取。

注:涉及到网络操作,需要添加网络操作的相关权限

 <uses-permission android:name="android.permission.INTERNET" />

 public class MainActivity extends AppCompatActivity {
        ImageView mImageView;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mImageView = (ImageView) findViewById(R.id.get_image_from_network_img);
            new AsyncTask<String, Void, Bitmap>() {
                @Override
                protected Bitmap doInBackground(String... strings) {
                    Bitmap bitmap = DiskLruCacheHelper.getInstance(MainActivity.this).readFromCache(strings[0]);
                    if (bitmap == null) {
                        try {
                            URL url = new URL(strings[0]);
                            bitmap = BitmapFactory.decodeStream(url.openStream());
                            DiskLruCacheHelper.getInstance(MainActivity.this).writeToCache(strings[0], bitmap);
                            return bitmap;
                        } catch (MalformedURLException e) {
                            e.printStackTrace();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    return bitmap;
                }

                @Override
                protected void onPostExecute(Bitmap bitmap) {
                    super.onPostExecute(bitmap);
                    mImageView.setImageBitmap(bitmap);
                }
            }.execute("http://file3.u148.net/2011/4/images/1302139153715.jpg");
        }
    }


运行过后,我们可以到相应的缓存区域查看是否有文件生成


多级缓存处理

上面提到了内存缓存和文件缓存,可以将这两者一起使用,形成二级缓存,第一层是内存缓存,如果内存缓存中没有,则从文件缓存中读取,之后如果本地也没有相关缓存文件,再从网络获取。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值