当前位置: 移动技术网 > IT编程>移动开发>Android > Android使用缓存机制实现文件下载及异步请求图片加三级缓存

Android使用缓存机制实现文件下载及异步请求图片加三级缓存

2019年07月24日  | 移动技术网IT编程  | 我要评论

非主流美女 图片,加油稿,小昭养成计划txt

首先给大家介绍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 异步请求图片加三级缓存的相关知识,希望对大家有所帮助。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网