Universal-Image-Loader(UIL)这个开源库,相信大家也都熟悉,应该算是Android图片加载这块的集大成者了吧。基本上该有的功能都有了,关于UIL源码的分析,有兴趣的同学可以点击这里。
UIL将各个功能模块都以接口的形式抽象出来,这样大家可以很方便的进行扩展,同时各个功能模块也都提供了默认的实现,可以满足大部分的需求。优秀的框架的扩展性,真的很赞。
我们项目中一直使用是UIL,最近项目有个需求,对UIL中在Disk中的缓存进行加密,不希望通过文件管理器能查看这些图片。
加解密算法不是重点,大家根据自己的需求,选择合适的算法即可(我在项目中选择了简单的AES加密),重点是怎么在UIL中进行拓展。
采用的方案是:
1.自定义DiskCache,存入Disk时,将图片文件加密,从Disk取出的时候,将加密后的缓存图片文件解密。
2.自定义ImageLoadingListener,当图片显示完成后,删除解密后的文件。
首先是自定义DiskCache
/** * * 实现了加密解密的DiskCache
* 继承自BaseDiscCache,重点实现了get方法和两个重载的save方法 * * @author grumoon * */ public class SecureDiscCache extends BaseDiscCache { private static final String TEMP_IMAGE_POSTFIX = ".tmp"; private static final String TAG = "SecureDiscCache"; private FileSecUtil fileSecUtil = null; public SecureDiscCache(File cacheDir) { super(cacheDir); this.fileSecUtil = new FileSecUtil(); } public SecureDiscCache(File cacheDir, File reserveCacheDir) { super(cacheDir, reserveCacheDir); this.fileSecUtil = new FileSecUtil(); } public SecureDiscCache(File cacheDir, File reserveCacheDir, FileNameGenerator fileNameGenerator) { super(cacheDir, reserveCacheDir, fileNameGenerator); this.fileSecUtil = new FileSecUtil(); } /** * 根据imageUri获取Disk中缓存文件,将其解密并返回。 */ @Override public File get(String imageUri) { Log.v(TAG, "get->" + imageUri); File sourceFile = getFile(imageUri); File imageFile = null; try { // 解密 imageFile = fileSecUtil.decrypt(sourceFile, sourceFile.getAbsolutePath() + FileSecUtil.DECRYPT_TEMP_FILE_TYPE, FileSecUtil.SEC_KEY); } catch (Exception e) { imageFile = null; } return imageFile; } /** * 将图片流imageStream写入到Disk中,并做加密处理。 */ @Override public boolean save(String imageUri, InputStream imageStream, CopyListener listener) throws IOException { Log.v(TAG, "save with stream->" + imageUri); File imageFile = getFile(imageUri); File tmpFile = new File(imageFile.getAbsolutePath() + TEMP_IMAGE_POSTFIX); boolean loaded = false; try { OutputStream os = new BufferedOutputStream(new FileOutputStream(tmpFile), bufferSize); try { loaded = IoUtils.copyStream(imageStream, os, listener, bufferSize); } finally { IoUtils.closeSilently(os); } } finally { IoUtils.closeSilently(imageStream); if (loaded) { try { // 加密 fileSecUtil.encrypt(tmpFile, imageFile.getAbsolutePath(), FileSecUtil.SEC_KEY); } catch (Exception e) { loaded = false; } } tmpFile.delete(); } Log.v(TAG, "save with stream->" + loaded); return loaded; } /** * 将bitmap对象写入到Disk中,并做加密处理。 */ @Override public boolean save(String imageUri, Bitmap bitmap) throws IOException { Log.v(TAG, "save with bitmap->" + imageUri); File imageFile = getFile(imageUri); File tmpFile = new File(imageFile.getAbsolutePath() + TEMP_IMAGE_POSTFIX); OutputStream os = new BufferedOutputStream(new FileOutputStream(tmpFile), bufferSize); boolean savedSuccessfully = false; try { savedSuccessfully = bitmap.compress(compressFormat, compressQuality, os); } finally { IoUtils.closeSilently(os); if (savedSuccessfully) { try { // 加密 fileSecUtil.encrypt(tmpFile, imageFile.getAbsolutePath(), FileSecUtil.SEC_KEY); } catch (Exception e) { savedSuccessfully = false; } } tmpFile.delete(); } bitmap.recycle(); Log.v(TAG, "save with bitmap->" + savedSuccessfully); return savedSuccessfully; } /** * 获取加密的文件绝对地址 * * @param imageUri * @return */ public String getFilePath(String imageUri) { return getFile(imageUri).getAbsolutePath(); } }
继承自BaseDiskCache,BaseDiskCache是个抽象类,已基本实现所有功能
我们这里重写了两个save方法,实现了文件加密
重写get方法,实现了文件的解密
这样在缓存的中文件就是加密状态了。
然后是自定义ImageLoadingListener
/** * 配合SecureDiscCache完成加解密功能这个类的主要目的,实在解密文件后,Disk中存在一个未加密的文件,我们需要在显示出图片或者显示失败后删掉他。
* 用于在显示完图片以后,删除未加密文件 * * @author grumoon * */ public class SecureImageLoadingListener implements ImageLoadingListener { @Override public void onLoadingStarted(String imageUri, View view) { } @Override public void onLoadingFailed(String imageUri, View view, FailReason failReason) { deleteImageFile(imageUri); } @Override public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { deleteImageFile(imageUri); } @Override public void onLoadingCancelled(String imageUri, View view) { deleteImageFile(imageUri); } public void deleteImageFile(String imageUri) { try { ImageLoader imageLoader = ImageLoader.getInstance(); DiskCache diskCache = imageLoader.getDiskCache(); if (diskCache instanceof SecureDiscCache) { SecureDiscCache sdc = (SecureDiscCache) diskCache; new File(sdc.getFilePath(imageUri) + FileSecUtil.DECRYPT_TEMP_FILE_TYPE).delete(); } } catch (Exception e) { } } }
初始化过程:
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context) .diskCache(new SecureDiscCache(cacheDir,cacheDir,new Md5FileNameGenerator())) ...其它配置 .build(); ImageLoader.getInstance().init(config);
ImageLoader.getInstance().displayImage(UrlUtil.preUrl(uri), imageView, ImageLoaderUtil.getDisplayImageOptions(), new SecureImageLoadingListener());
1.在调用SecureDiskCache的get方法(解密文件,产生未加密的文件)到 显示图片完成删除图片之前的时间内(时间很短),Disk中会存在未加密的文件,希望能得到改进。
2.SecureDiskCache中的两个save方法,基本上都是复制了BaseDiskCache中的方法,将流或者bitmap存到disk中,再通过加密工具对文件进行处理。最好是能够直接对流或者bitmap进行处理,直接加密生成文件,免去中间过程和提高安全性。