图片的三级缓存

一、概述

一開始在学习Android的时候。处理图片的时候,每次获取图片都是直接从网络上面载入图片。

可是在开发项目的过程中,每次点击进入app里面,图片都要慢慢的再一次从网络上面载入。

给用户的体验很不好,第一个等待的时间很令人dan 疼

第二个给用户的流量造成了不必要的浪费

因此提出图片的三级缓存策略,

所谓的三级缓存:就是在手机载入图片的时候,

1、首先从内存中载入,

2、假设内存中没有的话,从sd卡上获取。读取到之后将图片写入到内存中

3、假设sd卡上没有的话,从网络上获取,从网络上获取之后。写入到sd卡上,还有内存中

整体效果图例如以下:(比較模糊了点将就看吧)

想要写一个网络载入的框架,首先来写一个内存缓存的类:

package com.jishihuitong.bitmaputil;

import android.graphics.Bitmap;
import android.util.LruCache; /**
* Created by hss on 2016/8/4.
* <p>图片内存缓存工具类
*/
public class MemoryCache { /**
* 内存缓存对象
*/
private LruCache<String, Bitmap> mLruCache;
private static final String TAG = "MemoryCache"; /**
* 构造方法,初始化LruCache
*/
public MemoryCache() {
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int cacheMemory = maxMemory/8;
mLruCache = new LruCache<String,Bitmap>(cacheMemory){
@Override
protected int sizeOf(String key, Bitmap value) {
LogUtil.d(TAG,"创建了lruCache实例");
return value.getByteCount();
}
}; } /**
* 从内存中获取Bitmap对象
* @param url key
* @return bitmap对象
*/
public Bitmap getBitmap(String url){
LogUtil.d(TAG,"从内存中获取图片"+url);
return mLruCache.get(url);
} /**
* 将图片对象依照键值对保存在内存中
* @param url key
* @param bitmap value
*/
public void putBitmap(String url,Bitmap bitmap){
LogUtil.d(TAG,"将图片保存到内存中"+url);
mLruCache.put(url,bitmap);
}
}

然后来写 本地缓存 实例,本地通常是保存在了sd卡上面,

可是有时候须要推断一下手机有没有sd卡。要是没有的话我们就把他存在缓存文件夹里面,

将文件写入到本地的话。为了安全操作。我们须要对文件名称进行一次MD5加密,

这里是MD5的代码

package com.jishihuitong.bitmaputil;

import java.security.MessageDigest;

/**
* Created by hss on 2016/8/4.
*/
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();
}
}

将图片保存到本地的代码

package com.jishihuitong.bitmaputil;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment; import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream; /**
* Created by hss on 2016/8/4.
*/
public class SDCardCache {
private static final String TAG = "SDCardCache";
private static String sdcardCache; /**
* 从sd卡缓存中获取图片对象
*
* @param url 图片URL
* @return bitmap
*/
public Bitmap getSDCardBitmap(Context context,String url) {
Bitmap bitmap;
String filename = null;
try {
filename = MD5Encoder.encode(url);
LogUtil.d(TAG,"获取到文件的filename = "+filename);
bitmap = BitmapFactory.decodeStream(new FileInputStream(new File(getSDCache(context), filename)));
if (bitmap != null) {
LogUtil.d(TAG,"从SD卡中获取到图片"+url);
return bitmap;
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return null;
} /**
* 将图片缓存到本sd卡
* @param context 上下文对象
* @param url 图片URL
* @param bitmap 图片对象
*/
public void saveBitmapToSDCard(Context context,String url, Bitmap bitmap) {
String filename = null;
try {
filename = MD5Encoder.encode(url);
LogUtil.d(TAG,"保存的图片的filename = "+filename);
} catch (Exception e) {
e.printStackTrace();
}
File file = new File(getSDCache(context), filename);
File parentFile = file.getParentFile();
if(!parentFile.exists()){
LogUtil.d(TAG,"父文件夹不存在,创建");
parentFile.mkdirs();
}
//将图片保存到本地
try {
LogUtil.d(TAG,"文件保存在了本地");
//将图片保存到本地的方法,第一个參数 图片的格式,第二个參数图片的质量100表示最高质量
//第三个參数 图片的流
bitmap.compress(Bitmap.CompressFormat.PNG, 100,new FileOutputStream(file));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
} /**
* 获取到图片在本地缓存的文件夹
* @param context 上下文对象
* @return 缓存文件夹的字符串
*/
public String getSDCache(Context context){
String state = Environment.getExternalStorageState();
if(state==Environment.MEDIA_MOUNTED){
LogUtil.d(TAG,"SD卡挂载,获取到sd卡文件夹");
sdcardCache = Environment.getExternalStorageDirectory() + "cache/";
}else{
LogUtil.d(TAG,"SD卡不在,获取到cache文件夹");
sdcardCache = context.getCacheDir().getAbsolutePath();
LogUtil.d(TAG,"sdcardCache = "+sdcardCache);
}
return sdcardCache;
}
}

然后说从网络上获取图片的操作,这个操作涉及到訪问网络,这个样例里面使用的是asyncTask

在asyncTask里面,须要有在UI线程的回调,将图片对象设置到控件中,所以也须要一个ImageView对象的引用

把图片下载下来之后,还要将图片保存到本地 还有内存中,

所以该网络缓存的工具类中,须要有ImageView的引用,还有本地和内存的缓存对象

我们将他们在构造方法中进行实现

訪问网络的工具类:

package com.jishihuitong.bitmaputil;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Handler;
import android.util.Log;
import android.widget.ImageView; import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL; /**
* Created by hss on 2016/8/4.
*/
public class NetCacheUtil {
/**
* 内存缓存对象
*/
private MemoryCache mMemoryCache;
/**
* 本地缓存对象
*/
private SDCardCache mSDCardCache;
/**
* ImageView对象的引用
*/
public ImageView mImageView;
/**
* 全局使用的URL
*/
public String mUrl;
/**
* 上下文对象
*/
public Context context;
public Handler handler = new Handler();
public static final String TAG = "NetCacheUtil"; public NetCacheUtil(Context context,MemoryCache memoryCache, SDCardCache sdcardCache) {
this.mMemoryCache = memoryCache;
this.mSDCardCache = sdcardCache;
this.context = context;
}
public void getBitmapFromNet(ImageView view,String url){
//开启一个任务去下载图片,并设置到view上
new BitmapAsyncTask().execute(view,url);
} /**
* 下载图片的多线程任务
* <p/>
* 第一个泛型 : 參数类型 相应doInBackground()
* 第二个泛型 : 更新进度 相应onProgressUpdate()
* 第三个泛型 : 返回结果result 相应onPostExecute
*/
class BitmapAsyncTask extends AsyncTask<Object,Void,Bitmap>{ @Override
protected Bitmap doInBackground(Object... params) {
//获取到參数
mImageView = (ImageView) params[0];
mUrl = (String) params[1];
//在主线程运行,让ImageView和URL进行绑定,防止图片错乱
handler.post(new Runnable() {
@Override
public void run() {
mImageView.setTag(mUrl);
}
});
//运行下载图片的任务
Bitmap bitmap = downloadBitmap(mUrl);
return bitmap;
} @Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
} @Override
protected void onPostExecute(Bitmap bitmap) {
//该方法将在DoInBackground之后运行,
if(bitmap!=null){
String url = (String) mImageView.getTag();
//防止图片错乱。对刚才的图片URL和如今获取到的URL进行比对
if(url.equals(mUrl)){
mImageView.setImageBitmap(bitmap);
LogUtil.d(TAG,"将图片保存在本地和内存中" +context +" "+mUrl+" "+bitmap );
mSDCardCache.saveBitmapToSDCard(context, mUrl, bitmap);
mMemoryCache.putBitmap(mUrl, bitmap);
}
}
}
} /**
* 链接网络下载图片,下载下来之后将图片保存在本地和内存中
* @param mUrl
* @return
*/
private Bitmap downloadBitmap(String mUrl) {
HttpURLConnection conn =null;
try {
conn = (HttpURLConnection) new URL(mUrl).openConnection();
conn.setReadTimeout(5 * 1000);
conn.setRequestMethod("GET");
conn.setConnectTimeout(5 * 1000);
conn.connect();
int code = conn.getResponseCode();
if(code == 200){
InputStream inputStream = conn.getInputStream();
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
return bitmap;
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(conn!=null){
conn.disconnect();
conn = null;
}
}
return null;
}
}

三个阶段的缓存工具类都准备好了,接下来定义一个类。一个方法,能够让他们一次性运行,那么这种方法就是display方法

package com.jishihuitong.bitmaputil;

import android.content.Context;
import android.graphics.Bitmap;
import android.widget.ImageView; /**
* Created by hss on 2016/8/4.
* <p/>
* 综合使用三级缓存的工具类
*/
public class BitmapUtil { /**
* 内存缓存类
*/
private final MemoryCache memoryCache;
/**
* SD卡缓存类
*/
private final SDCardCache sdCardCache;
/**
* 网络缓存类
*/
private final NetCacheUtil netCacheUtil;
/**
* 上下文对象
*/
private Context context; public BitmapUtil(Context context) {
memoryCache = new MemoryCache();
sdCardCache = new SDCardCache();
netCacheUtil = new NetCacheUtil(context, memoryCache, sdCardCache);
this.context = context;
} /**
* 调用该方法能够进入图片三级缓存流程。将图片展示在UI界面上
* @param view ImageView对象
* @param url 图片訪问路径
*/
public void display(ImageView view,String url){
//根URL从内存中获取。
Bitmap memoryCacheBitmap = memoryCache.getBitmap(url);
//假设获取到的不为空,将图片设置到控件上。返回
if(memoryCacheBitmap!=null){
view.setImageBitmap(memoryCacheBitmap);
return;
}
//假设内存获取为空的话,再依据URL从内存中获取,
Bitmap sdCardBitmap = sdCardCache.getSDCardBitmap(context, url);
//假设不为空,将图片设置给控件。然后将图片保存在内存中 返回
if(sdCardBitmap!=null){
view.setImageBitmap(sdCardBitmap);
memoryCache.putBitmap(url,sdCardBitmap);
return;
}
//本地也没有的话就仅仅能到网络去找了,该方法里有直接将图片设置给控件的方法
netCacheUtil.getBitmapFromNet(view,url);
}
}

工具类都写好之后,我们在一个activity里面简单的调用一下,

首先整个布局就是一个ListView。然后里面的item就是一个ImageView

布局代码例如以下

main_activity.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/lv"> </ListView>
</LinearLayout>

view_listview.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="180dp"
android:orientation="vertical"
>
<ImageView
android:layout_width="match_parent"
android:layout_height="180dp"
android:id="@+id/iv"
android:src="@mipmap/empty_photo"
/> </LinearLayout>

在MainActivity里面调用方法 让图片填充ListView

package com.jishihuitong.bitmaputil;

import android.content.Context;
import android.graphics.BitmapFactory;
import android.media.Image;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView; import java.util.ArrayList;
import java.util.List; public class MainActivity extends AppCompatActivity { private ArrayList<String> list;
private BitmapUtil bitmapUtil;
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//准备数据
list = new ArrayList<String>();
list.add("http://192.168.0.11:8080/images/10001.png");
list.add("http://192.168.0.11:8080/images/10002.png");
list.add("http://192.168.0.11:8080/images/10003.png");
list.add("http://192.168.0.11:8080/images/10004.png");
list.add("http://192.168.0.11:8080/images/10005.png");
list.add("http://192.168.0.11:8080/images/10006.png");
//初始化图片缓存工具类
bitmapUtil = new BitmapUtil(this);
ListView lv = (ListView) findViewById(R.id.lv);
lv.setAdapter(new MyAdapter(getApplication(),0,list));
} class MyAdapter extends ArrayAdapter { public MyAdapter(Context context, int resource, List<String> list) {
super(context, resource, list);
} @Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
ImageView iv;
if (convertView == null) {
view = View.inflate(getApplicationContext(), R.layout.view_listview, null);
} else {
view = convertView;
}
iv = (ImageView) view.findViewById(R.id.iv);
iv.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.empty_photo));
//为了防止图片乱跳。给imageView设置标记
iv.setTag(list.get(position));
LogUtil.d(TAG, "訪问的URL为 " + list.get(position));
//载入图片
bitmapUtil.display(iv, list.get(position));
return view;
} }
}

部署在tomcat上面的图片路径如图所看到的:

点击下载代码

Android进阶图片处理之三级缓存方案的更多相关文章

  1. Android 平滑图片加载和缓存库 Glide 使用详解

    在图片加载库烂大街的今天,选择一个适合自己使用的图片加载库已经成为了每一个Android开发者的必经之路.现在市面上知名的图片加载库有UIL,Picasso,Volley ImageLoader,Fr ...

  2. Android远程图片获取和本地缓存

    对于客户端——服务器端应用,从远程获取图片算是经常要用的一个功能,而图片资源往往会消耗比较大的流量,对 应用来说,如果处理不好这个问题,那会让用户很崩溃,不知不觉手机流量就用完了,等用户发现是你的应用 ...

  3. 关于Android圆形图片的一种优化方案(可以显示网络图片)

    在Android App中,我们经常看到圆形头像图片,然后网上也有很多开源的控件.刚好这个项目用到了,也去找了一些开源的,发现并不完美,所以只好自己优化了,废话不多说,先上效果图: 下面是源码:本人能 ...

  4. 简单地Android中图片的三级缓存机制

    我们不能每次加载图片的时候都让用户从网络上下载,这样不仅浪费流量又会影响用户体验,所以Android中引入了图片的缓存这一操作机制. 原理: 首先根据图片的网络地址在网络上下载图片,将图片先缓存到内存 ...

  5. 【Android - 进阶】之图片三级缓存的原理及实现

    在Android开发中,如果图片过多,而我们又没有对图片进行有效的缓存,就很容易导致OOM(Out Of Memory)错误.因此,图片的缓存是非常重要的,尤其是对图片非常多的应用.现在很多框架都做了 ...

  6. Android 的图片异步请求加三级缓存 ACE

    使用xUtils等框架是很方便,但今天要用代码实现bitmapUtils 的功能,很简单, 1 AsyncTask请求一张图片 ####AsyncTask #####AsyncTask是线程池+han ...

  7. Android 图片三级缓存之内存缓存(告别软引用(SoftRefrerence)和弱引用(WeakReference))

    因为之前项目同事使用了图片三级缓存,今天整理项目的时候发现同事还是使用了软引用(SoftRefrerence)和弱引用(WeakReference),来管理在内存中的缓存.看到这个我就感觉不对了.脑海 ...

  8. Android中图片的三级缓存策略

    在开发过程中,经常会碰到进行请求大量的网络图片的样例.假设处理的不好.非常easy造成oom.对于避免oom的方法,无非就是进行图片的压缩.及时的回收不用的图片.这些看似简单可是处理起来事实上涉及的知 ...

  9. Android中图片的三级缓存

    为什么要使用三级缓存 如今的 Android App 经常会需要网络交互,通过网络获取图片是再正常不过的事了 假如每次启动的时候都从网络拉取图片的话,势必会消耗很多流量.在当前的状况下,对于非wifi ...

随机推荐

  1. python-水仙花数

    >>> for a in range(1,10):... for b in range(0,10):... for c in range(0,10):... x=100*a+10*b ...

  2. react笔记汇总

    1.什么是React? a.React 是一个用于构建用户界面的 JAVASCRIPT 库. b.React主要用于构建UI,很多人认为 React 是 MVC 中的 V. c.React 起源于 F ...

  3. Socket通信时服务端无响应,客户端超时设置

    背景:在写一个客户端的socket程序,服务端没有返回消息,客户端一直在等待. 目标:我需要设置一个时间,如果超过这个时间客户端自动断开连接.最好是在服务端实现,客户端对我来说不可控.

  4. [LUOGU] P3004 [USACO10DEC]宝箱Treasure Chest

    第一眼:区间DP,可以瞎搞 f[i][j]=max(sum(i,j)-f[i+1][j],sum(i,j)-f[i][j-1]) 提出来就是f[i][j]=sum(i,j)-min(f[i+1][j] ...

  5. php 时间 数组替换

    //随机数和时间//随机数生产器//echo rand();//echo "<br>";//生成某个范围的随机数//echo rand(0,10);//echo &qu ...

  6. CodeForces 22、23部分题解

    CodeForces 22A 找严格第二小的...注意只有一种情况,可以sort排序然后unique输出. int a[N]; int main() { int n; while(~scanf(&qu ...

  7. HDU1686 计算模式串匹配的次数

    题目大意: 输入一个T,表示有T组测试数据: 每组测试数据包括一个字符串W,T,T长度大于W小于1000000,w长度小于10000,计算W匹配到T中成功的次数: 这题很明显要用KMP算法,不然很容易 ...

  8. 解决Genymotion运行Android 5.0一直卡在开机界面

    在一些机器,启动genymotion 的android5.0版模拟器时,会卡在启动界面,一直启动不了. 这是因为要求的开启虚拟选项没有打开,在第一次启动时,会有提示,但可能大家没有注意(我也没注意到, ...

  9. 【ZJOI2017 Round1练习】D2T3 counter(线段树)

    题意: 思路: 预处理出b[i]代表i位置之前比a[i]小的数的个数 以每个数为结尾的组数是线段树中(1,a[i]-1) 对于a[i]换到最后,相当于线段树中(a[i]+1,n)-- 交换后b[i]又 ...

  10. CF613A:Peter and Snow Blower

    用一个圆心在(x,y)的圆环覆盖一个n边形,顺或逆时针给出n边形所有顶点,求圆环最小面积. 卡了好久,各种傻逼错误.. 题目就是让我们固定一大一小两个边界圆,我们来看看这两个圆满足什么条件. 首先外面 ...