基于主管建议我看下缓存这方面的资料,所以找到了郭神的《Android照片墙完整版,完美结合LruCache和DiskLruCache》
和《Android DiskLruCache完全解析,硬盘缓存的最佳方案》
很荣幸能了解到大神对缓存的理解。所以特地把我对前篇代码阅读的心得体会记录下来,若有哪里说得不好、说错的,欢迎指正,至于后一篇大神已经说得非常非常好了,我也没啥说的。
public class PhotoWallAdapter extends ArrayAdapter<String>
public PhotoWallAdapter(Context context, int resource, String[] objects, GridView photoWall) {
super(context, resource, objects);
taskCollection = new HashSet<BitmapWorkerTask>();
mOkHttpClient = new OkHttpClient();
mPhotoWall = photoWall;
//获取应用程序最大的可用内存
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 value) {
return value.getByteCount();
}
};
//获取图片缓存路径
File cacheDir = getDiskCacheDir(context, "pzh");
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
try {
//设置10M的磁盘缓存
mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);
} catch (IOException e) {
e.printStackTrace();
}
}
可以看到郭神,是自己定义了一个适配器,这个适配器中,需要传入上下文、Url和GridView。原文中是用HttpUrlConnection,与时俱进,我就用OkHttp来代替。因为这是结合内存缓存和磁盘缓存来使用,所以思路是:先从内存里根据Key获取,若没有则从磁盘缓存里获取,还没有就下载,然后放进内存缓存(若有空间,没有就放磁盘缓存),接着再呈现出来。所以构造方法里步骤如下:
1、先获取获取程序最大运行内存Runtime.getRuntime().maxMemory(),再分配其中的八分之一给图片缓存new LruCache
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//这里的数据是之前构造方法String[] objects里得到的
String url = getItem(position);
View view;
if (convertView != null) {
view = convertView;
} else {
view = View.inflate(getContext(), R.layout.photo_layout, null);
}
ImageView imageView = (ImageView) view.findViewById(R.id.photo);
//设置item的高度
if (imageView.getLayoutParams().height != mItemHeight) {
imageView.getLayoutParams().height = mItemHeight;
}
//给Imageview设置,避免异步加载时图片不会乱序
imageView.setTag(url);
imageView.setImageResource(R.mipmap.ic_launcher);
loadBitmaps(imageView, url);
return view;
}
这里主要是想看loadBitmaps(imageView, url);
private void loadBitmaps(ImageView imageView, String url) {
Bitmap bitmap = getBitmapFromMemoryCache(url);
if (bitmap == null) {
BitmapWorkerTask task = new BitmapWorkerTask();
taskCollection.add(task);
task.execute(url);
} else {
if (imageView != null && bitmap != null) {
imageView.setImageBitmap(bitmap);
}
}
}
1、首先getBitmapFromMemoryCache(url)从缓存中得到图片
2、若图片为空,则new BitmapWorkerTask()新建一个异步加载任务, taskCollection.add(task);同时把这个任务记录在HashSet中,以便可以取消这个加载任务。
3、若缓存中有图片,就直接显示出来
接下来看看如何从缓存中的到图片
private Bitmap getBitmapFromMemoryCache(String url) {
return mMemoryCache.get(url);
}
很简单,根据key来可以获取。当然第一次加载为空,没什么可说的,那么就需要看看如何异步加载图片,放进缓存的操作
class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {
private String imageUrl;
@Override
protected Bitmap doInBackground(String... params) {
//这个url是task.execute(url);这里传进来的,只传进来一个数,所以是params数组里的第0位
imageUrl = params[0];
FileDescriptor fileDescriptor = null;
FileInputStream fileInputStream = null;
DiskLruCache.Snapshot snapshot = null;
//生成Url对应Md5加密的key
String key = hashKeyForDisk(imageUrl);
try {
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();
}
}
snapshot = mDiskLruCache.get(key);
}
if(snapshot != null){
fileInputStream = (FileInputStream) snapshot.getInputStream(0);
fileDescriptor = fileInputStream.getFD();
}
//将数据解析成bitmap对象
Bitmap bitmap = null;
if(fileDescriptor != null){
bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
}
if(bitmap != null){
addBitmapToMemoryCache(params[0],bitmap);
}
return bitmap;
} catch (IOException e) {
e.printStackTrace();
}finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
ImageView imageView = (ImageView) mPhotoWall.findViewWithTag(imageUrl);
if(imageView != null && bitmap != null){
imageView.setImageBitmap(bitmap);
}
taskCollection.remove(this);
}
}
1、内存缓存没有图片,下载前先读取磁盘,就用Snapshot 这个类来读取磁盘缓存,若不为空,那么snapshot.getInputStream(0)可以读取到输入流,接着转成Bitmap,addBitmapToMemoryCache(params[0],bitmap)把url作为key和Bitmap放进缓存里面,当然key是url有些不规范,可以md5加密
2、若磁盘没有图片,就只能下载了,downLoadUrlToStream(imageUrl, outputStream)
private boolean downLoadUrlToStream(String imageUrl, final OutputStream outputStream) {
Request request = new Request.Builder()
.url(imageUrl)
.build();
Call call = mOkHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
}
@Override
public void onResponse(Response response) throws IOException {
InputStream is = null;
BufferedOutputStream out = null;
BufferedInputStream in = null;
try {
is = response.body().byteStream();
in = new BufferedInputStream(is, 8 * 1024);
out = new BufferedOutputStream(outputStream, 8 * 1024);
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
isSuccess = true;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (is != null) {
is.close();
}
if (in != null) {
in.close();
}
if(out != null){
out.close();
}
}
}
});
return isSuccess;
}
使用OkHttp,需要定义Request和Call参数,response.body().byteStream()得到下载的输入流,把它写进输出流即可
这样就可以 editor.commit();就可以生成磁盘的缓存文件了, editor.abort();若下载不成功就废弃掉
最后加上两个方法
public void fluchCache(){
if(mDiskLruCache != null){
try {
mDiskLruCache.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
将缓存记录同步到journal文件中。
public void cancelAllTasks(){
if(taskCollection != null){
for(BitmapWorkerTask task : taskCollection){
task.cancel(false);
}
}
}
取消所有下载任务