Android使用缓存机制实现文件下载及异步请求图片加三级缓存
首先给大家介绍android使用缓存机制实现文件下载
在下载文件或者在线浏览文件时,或者为了保证文件下载的正确性,需要使用缓存机制,常使用softreference来实现。
softreference的特点是它的一个实例保存对一个java对象的软引用,该软引用的存在不妨碍垃圾收集线程对该java对象的回收。也就是说,一旦softreference保存了对一个java对象的软引用后,在垃圾线程对这个java对象回收前,softreference类所提供的get()方法返回java对象的强引用。另外,一旦垃圾线程回收该java对象之后,get()方法将返回null。软引用可以和一个引用队列(referencequeue)联合使用,如果软引用所引用的对象被垃圾回收器回收,java虚拟机就会把这个软引用加入到与之关联的引用队列中。
一般的缓存策略是:
一级内存缓存、二级文件缓存(数据库也算作文件缓存)、三级网络数据
一、网络下载的缓存策略
关于网络下载文件(图片、音频、视频)的基本策略:
1.不要直接下载到目标文件,应使用temp文件作中转,以确保文件的正确性与完整性,流程如下:
a)以网络目标文件名 a 生成唯一的本地目标文件名 b
b)以本地目标文件名 b 生成唯一的本地临时文件名 t
c)下载文件到 t 中
d)下载完毕,校验文件 t 的正确性与完整性
e)若不正确或不完整则 delete 文件 t,并返回 false
f)校验完毕后,将文件 t 重命名 或 复制到 b 文件
g)最后的清理现场,删除临时文件 t,成功后,返回 true
2.尽力提供文件正确性与完整性的校验:
a)正确性:比如 md5/hash code 比对、文件格式的比对。
b)完整性:比如 文件大小是否一致、图片的数据是否正确(图片文件头中提供了相关信息)
3.考虑对于下载到本地的文件是否需要再做二次加工,可以思考以下情况:
a)比如网络源始图片的大小为800*600,而我们需要作为缩略图的大小为160*145,所以考虑对下载后的文件进行裁剪,再保存,对于源始文件则直接删除。
二、文件缓存策略:
1.需要唯一的缓存文件的对应i/o key,一般可以使用 hashcode。
2.若是同一个文件,以不同的时间,可以考虑,先清本地缓存,再下载新的缓存到本地。
3.同一文件也可以加上时间戳后,再生成唯一hashcode。
4.生成文件缓时,也许需要作以下全面的考虑:
a)sdcard是否已经没有空间(这个需求是存在的,但几乎没有人会考虑到,一旦发生,必crash)。
b)缓存的清理策略。每日、每周定时清理?到达一个阀值后,自动清理?(若无清理策略,把垃圾数据一直当个宝一相存着,
是很sb的)。
c)缓存真正需要的数据。不要觉外存是无限的,所以就可以什么都存,要知道,多则繁,繁则乱。曾经有一同事,每天存几百mb的用户数据(所有用户的性别、 age、联系方式等等),而pm需要的只是一个每日数户的活跃数据报表,于是最后改为缓存每天的用户分析报表数据即可(才10几kb)。
d)给缓存文件加密。最简单就是去掉文件的扩展名,这也算加密,当然,你可以把服务端文件加密,然后在内存中解密。这就看项目的需求而定,我的经验也不足,一般就是改改扩展名之类的。
下面给大家介绍android 异步请求图片加三级缓存
使用xutils等框架是很方便,但今天要用代码实现bitmaputils 的功能,很简单,
1 asynctask请求一张图片
####asynctask
#####asynctask是线程池+handler的封装 第一个泛型: 传参的参数类型类型(和doinbackground一致) 第二个泛型:
#####更新进度的参数类型(和onprogressupdate一致) 第三个泛型: 返回结果的参数类型(和onpostexecute一致,
#####和doinbackground返回类型一致)
看asynctask源码:
public abstract class asynctask<params, progress, result> { private static final string log_tag = "asynctask"; private static final int core_pool_size = 5; private static final int maximum_pool_size = 128; private static final int keep_alive = 1; private static final threadfactory sthreadfactory = new threadfactory() { private final atomicinteger mcount = new atomicinteger(1); public thread newthread(runnable r) { return new thread(r, "asynctask #" + mcount.getandincrement()); } };
核心线程5 最大线程128 这是asynctask的线程池 然后通过handler发送消息 , 它内部实例化了一个静态的自定义类 internalhandler,这个类是继承自 handler 的,在这个自定义类中绑定了一个叫做 asynctaskresult 的对象,每次子线程需要通知主线程,就调用 sendtotarget 发送消息给 handler自己。然后在 handler 的 handlemessage 中 asynctaskresult 根据消息的类型不同(例如 message_post_progress 会更新进度条,message_post_cancel 取消任务)而做不同的操作,值得一提的是,这些操作都是在ui线程进行的,意味着,从子线程一旦需要和 ui 线程交互,内部自动调用了 handler 对象把消息放在了主线程了。
private static final internalhandler shandler = new internalhandler(); mfuture = new futuretask<result>(mworker) { @override protected void more ...done() { message message; result result = null; try { result = get(); } catch (interruptedexception e) { android.util.log.w(log_tag, e); } catch (executionexception e) { throw new runtimeexception("an error occured while executing doinbackground()", e.getcause()); } catch (cancellationexception e) { message = shandler.obtainmessage(message_post_cancel, new asynctaskresult<result>(asynctask.this, (result[]) null)); message.sendtotarget(); return; } catch (throwable t) { throw new runtimeexception("an error occured while executing " + "doinbackground()", t); } message = shandler.obtainmessage(message_post_result, new asynctaskresult<result>(asynctask.this, result)); message.sendtotarget(); } }; private static class internalhandler extends handler { @suppresswarnings({"unchecked", "rawuseofparameterizedtype"}) @override public void more ...handlemessage(message msg) { asynctaskresult result = (asynctaskresult) msg.obj; switch (msg.what) { case message_post_result: // there is only one result result.mtask.finish(result.mdata[0]); break; case message_post_progress: result.mtask.onprogressupdate(result.mdata); break; case message_post_cancel: result.mtask.oncancelled(); break; } } }
下面看代码 第一步我们先请求一张图片 并解析 注释写的很详细了.
netcacheutils.java
import java.io.inputstream; import java.net.httpurlconnection; import java.net.url; import android.graphics.bitmap; import android.graphics.bitmapfactory; import android.os.asynctask; import android.widget.imageview; /** * 网络缓存 * * @author ace * @date 2016-02-18 */ public class netcacheutils { private localcacheutils mlocalutils; private memorycacheutils mmemoryutils; public netcacheutils(localcacheutils localutils, memorycacheutils memoryutils) { mlocalutils = localutils; mmemoryutils = memoryutils; } public void getbitmapfromnet(imageview imageview, string url) { bitmaptask task = new bitmaptask(); task.execute(imageview, url); } /** * asynctask是线程池+handler的封装 第一个泛型: 传参的参数类型类型(和doinbackground一致) 第二个泛型: * 更新进度的参数类型(和onprogressupdate一致) 第三个泛型: 返回结果的参数类型(和onpostexecute一致, * 和doinbackground返回类型一致) */ class bitmaptask extends asynctask<object, integer, bitmap> { private imageview mimageview; private string url; // 主线程运行, 预加载 @override protected void onpreexecute() { super.onpreexecute(); } // 子线程运行, 异步加载逻辑在此方法中处理 @override protected bitmap doinbackground(object... params) { mimageview = (imageview) params[0]; url = (string) params[1]; mimageview.settag(url);// 将imageview和url绑定在一起 // publishprogress(values)//通知进度 // 下载图片 return download(url); } // 主线程运行, 更新进度 @override protected void onprogressupdate(integer... values) { super.onprogressupdate(values); } // 主线程运行, 更新主界面 @override protected void onpostexecute(bitmap result) { if (result != null) { // 判断当前图片是否就是imageview要的图片, 防止listview重用导致的图片错乱的情况出现 string bindurl = (string) mimageview.gettag(); if (bindurl.equals(url)) { // 给imageview设置图片 mimageview.setimagebitmap(result); // 将图片保存在本地 mlocalutils.setbitmaptolocal(result, url); // 将图片保存在内存 mmemoryutils.setbitmaptomemory(url, result); } } } } /** * 下载图片 * * @param url */ public bitmap download(string url) { httpurlconnection conn = null; try { conn = (httpurlconnection) (new url(url).openconnection()); conn.setconnecttimeout(5000); conn.setreadtimeout(5000); conn.setrequestmethod("get"); conn.connect(); int responsecode = conn.getresponsecode(); if (responsecode == 200) { inputstream in = conn.getinputstream(); // 将流转化为bitmap对象 bitmap bitmap = bitmapfactory.decodestream(in); return bitmap; } } catch (exception e) { e.printstacktrace(); } finally { if (conn != null) { conn.disconnect(); } } return null; } }
memorycacheutils.java 用到了lrucache 很简单我简单翻译下文档:
* a cache that holds strong references to a limited number of values. each time * a value is accessed, it is moved to the head of a queue. when a value is * added to a full cache, the value at the end of that queue is evicted and may * become eligible for garbage collection. * cache保存一个强引用来限制内容数量,每当item被访问的时候,此item就会移动到队列的头部。 * 当cache已满的时候加入新的item时,在队列尾部的item会被回收。 * <p>if your cached values hold resources that need to be explicitly released, * override {@link #entryremoved}. * 如果你cache的某个值需要明确释放,重写entryremoved() * <p>by default, the cache size is measured in the number of entries. override * {@link #sizeof} to size the cache in different units. for example, this cache * is limited to 4mib of bitmaps: 默认cache大小是测量的item的数量,重写sizeof计算不同item的 * 大小。 {@code * int cachesize = 4 * 1024 * 1024; // 4mib * lrucache<string, bitmap> bitmapcache = new lrucache<string, bitmap>(cachesize) { * protected int sizeof(string key, bitmap value) { * return value.getbytecount(); * } * }} ------------------------------------------------------------------- <p>this class is thread-safe. perform multiple cache operations atomically by * synchronizing on the cache: <pre> {@code * synchronized (cache) { * if (cache.get(key) == null) { * cache.put(key, value); * } * }}</pre> * 他是线程安全的,自动地执行多个缓存操作并且加锁 ------------------------- <p>this class does not allow null to be used as a key or value. a return * value of null from {@link #get}, {@link #put} or {@link #remove} is * unambiguous: the key was not in the cache. * 不允许key或者value为null * 当get(),put(),remove()返回值为null时,key相应的项不在cache中
最重要的大概就是以上几点: 使用很简单来看代码
import android.graphics.bitmap; import android.support.v4.util.lrucache; /** * 内存缓存工具类 * * @author ace * @date 2016-02-19 */ public class memorycacheutils { // android 2.3 (api level // 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠,建议用lrucache,它是强引用 private lrucache<string, bitmap> mcache; public memorycacheutils() { int maxmemory = (int) runtime.getruntime().maxmemory();// 获取虚拟机分配的最大内存 // 16m // lru 最近最少使用, 通过控制内存不要超过最大值(由开发者指定), 来解决内存溢出,就像上面翻译的所说 如果cache满了会清理最近最少使用的缓存对象 mcache = new lrucache<string, bitmap>(maxmemory / 8) { @override protected int sizeof(string key, bitmap value) { // 计算一个bitmap的大小 int size = value.getrowbytes() * value.getheight();// 每一行的字节数乘以高度 return size; } }; } public bitmap getbitmapfrommemory(string url) { return mcache.get(url); } public void setbitmaptomemory(string url, bitmap bitmap) { mcache.put(url, bitmap); } }
最后一级缓存 本地缓存 把网络下载的图片 文件名以md5的形式保存到内存卡的制定目录
/** * 本地缓存工具类 * * @author ace * @date 2016-02-19 */ public class localcacheutils { // 图片缓存的文件夹 public static final string dir_path = environment .getexternalstoragedirectory().getabsolutepath() + "/ace_bitmap_cache"; public bitmap getbitmapfromlocal(string url) { try { file file = new file(dir_path, md5encoder.encode(url)); if (file.exists()) { bitmap bitmap = bitmapfactory.decodestream(new fileinputstream( file)); return bitmap; } } catch (exception e) { e.printstacktrace(); } return null; } public void setbitmaptolocal(bitmap bitmap, string url) { file dirfile = new file(dir_path); // 创建文件夹 文件夹不存在或者它不是文件夹 则创建一个文件夹.mkdirs,mkdir的区别在于假如文件夹有好几层路径的话,前者会创建缺失的父目录 后者不会创建这些父目录 if (!dirfile.exists() || !dirfile.isdirectory()) { dirfile.mkdirs(); } try { file file = new file(dir_path, md5encoder.encode(url)); // 将图片压缩保存在本地,参1:压缩格式;参2:压缩质量(0-100);参3:输出流 bitmap.compress(compressformat.jpeg, 100, new fileoutputstream(file)); } catch (exception e) { e.printstacktrace(); } } }
md5encoder
import java.security.messagedigest; public class md5encoder { public static string encode(string string) throws exception { byte[] hash = messagedigest.getinstance("md5").digest(string.getbytes("utf-8")); stringbuilder hex = new stringbuilder(hash.length * 2); for (byte b : hash) { if ((b & 0xff) < 0x10) { hex.append("0"); } hex.append(integer.tohexstring(b & 0xff)); } return hex.tostring(); } }
最后新建一个工具类来使用我们上面的三个缓存工具类
/** * 三级缓存工具类 * * @author ace * @date 2016-02-19 */ public class mybitmaputils { // 网络缓存工具类 private netcacheutils mnetutils; // 本地缓存工具类 private localcacheutils mlocalutils; // 内存缓存工具类 private memorycacheutils mmemoryutils; public mybitmaputils() { mmemoryutils = new memorycacheutils(); mlocalutils = new localcacheutils(); mnetutils = new netcacheutils(mlocalutils, mmemoryutils); } public void display(imageview imageview, string url) { // 设置默认加载图片 imageview.setimageresource(r.drawable.news_pic_default); // 先从内存缓存加载 bitmap bitmap = mmemoryutils.getbitmapfrommemory(url); if (bitmap != null) { imageview.setimagebitmap(bitmap); system.out.println("从内存读取图片啦..."); return; } // 再从本地缓存加载 bitmap = mlocalutils.getbitmapfromlocal(url); if (bitmap != null) { imageview.setimagebitmap(bitmap); system.out.println("从本地读取图片啦..."); // 给内存设置图片 mmemoryutils.setbitmaptomemory(url, bitmap); return; } // 从网络缓存加载 mnetutils.getbitmapfromnet(imageview, url); } }
以上所述给大家介绍了android 异步请求图片加三级缓存的相关知识,希望对大家有所帮助。