标签:
一、简介
现在的Android应用程序中,不可避免的都会使用到图片,如果每次加载图片的时候都要从网络重新拉取,这样不但很耗费用户的流量,而且图片加载的也会很慢,用户体验很不好。所以一个应用的图片缓存策略是很重要的。通常情况下,Android应用程序中图片的缓存策略采用“内存-本地-网络”三级缓存策略,首先应用程序访问网络拉取图片,分别将加载的图片保存在本地SD卡中和内存中,当程序再一次需要加载图片的时候,先判断内存中是否有缓存,有则直接从内存中拉取,否则查看本地SD卡中是否有缓存,SD卡中如果存在缓存,则图片从SD卡中拉取,否则从网络加载图片。依据这三级缓存机制,可以让我们的应用程序在加载图片的时候做到游刃有余,有效的避免内存溢出。
PS:当然现在处理网络图片的时候,一般人都会选择XUtils中的BitmapUtil,它已经将网络缓存处理的相当好了,使用起来非常方便--本人就一直在用。仿照BitMapUtil的实现思路,定制一个自己的图片加载工具,来理解一下三级缓存的策略,希望对自己会有一个提升。
二、网络缓存
网络拉取图片严格来讲不能称之为缓存,实质上就是下载url对应的图片,我们这里姑且把它看作是缓存的一种。仿照BitmapUtil中的display方法,我自己定制的CustomBitmapUtils也定义这个方法,根据传入的url,将图片设置到ivPic控件上。
-
public void display(ImageView ivPic, String url) {
-
-
}
定义网络缓存的工具类,在访问网络的时候,我使用了AsyncTask来实现,在AsyncTask的doInBackGround方法里下载图片,然后将 图片设置给ivPic控件,AsyncTask有三个泛型,其中第一个泛型是执行异步任务的时候,通过execute传过来的参数,第二个泛型是更新的进度,第三个泛型是异步任务执行完成之后,返回来的结果,我们这里返回一个Bitmap。具体的下载实现代码如下:
三、本地缓存
从网络加载完图片之后,将图片保存到本地SD卡中。在加载图片的时候,判断一下SD卡中是否有图片缓存,如果有,就直接从SD卡加载图片。本地缓存的工具类中有两个公共的方法,分别是向本地SD卡设置网络图片,获取SD卡中的图片。设置图片的时候采用键值对的形式进行存储,将图片的url作为键,作为文件的名字,图片的Bitmap作位值来保存。由于url含有特殊字符,不能直接作为图片的名字来存储,故采用url的MD5值作为文件的名字。
-
-
-
-
-
-
-
public class LocalCacheUtils {
-
-
-
-
public static final String FILE_PATH = Environment
-
.getExternalStorageDirectory().getAbsolutePath() + "/cache/pics";
-
-
-
-
-
-
-
-
public Bitmap getBitmapFromLocal(String url) {
-
try {
-
String fileName = MD5Encoder.encode(url);
-
File file = new File(FILE_PATH, fileName);
-
if (file.exists()) {
-
Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(
-
file));
-
return bitmap;
-
}
-
-
} catch (Exception e) {
-
e.printStackTrace();
-
}
-
-
return null;
-
-
}
-
-
-
-
-
-
-
-
public void setBitmap2Local(String url, Bitmap bitmap) {
-
try {
-
-
String fileName = MD5Encoder.encode(url);
-
-
File file = new File(FILE_PATH, fileName);
-
-
File fileParent = file.getParentFile();
-
if (!fileParent.exists()) {
-
-
fileParent.mkdirs();
-
}
-
-
bitmap.compress(CompressFormat.JPEG, 100,
-
new FileOutputStream(file));
-
-
} catch (Exception e) {
-
e.printStackTrace();
-
}
-
}
-
-
}
四、内存缓存
内存缓存说白了就是在内存中保存一份图片集合,首先会想到HashMap这种键值对的形式来进行保存,以url作为key,bitmap作为value。但是在Java中这种默认的new对象的方式是强引用,JVM在进行垃圾回收的时候是不会回收强引用的,所以如果加载的图片过多的话,map会越来越大,很容易出现OOM异常。在Android2.3之前,还可以通过软引用或者弱引用来解决,但是Android2.3之后,Google官方便不再推荐软引用了,Google推荐我们使用LruCache。
在过去,我们经常会使用一种非常流行的内存缓存技术的实现,即软引用或弱引用 (SoftReference or WeakReference)。但是现在已经不再推荐使用这种方式了,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃。
为了能够选择一个合适的缓存大小给LruCache, 有以下多个因素应该放入考虑范围内,例如:
- 你的设备可以为每个应用程序分配多大的内存?Android默认是16M。
- 设备屏幕上一次最多能显示多少张图片?有多少图片需要进行预加载,因为有可能很快也会显示在屏幕上?
- 你的设备的屏幕大小和分辨率分别是多少?一个超高分辨率的设备(例如 Galaxy Nexus) 比起一个较低分辨率的设备(例如 Nexus S),在持有相同数量图片的时候,需要更大的缓存空间。
- 图片的尺寸和大小,还有每张图片会占据多少内存空间。
- 图片被访问的频率有多高?会不会有一些图片的访问频率比其它图片要高?如果有的话,你也许应该让一些图片常驻在内存当中,或者使用多个LruCache 对象来区分不同组的图片。
- 你能维持好数量和质量之间的平衡吗?有些时候,存储多个低像素的图片,而在后台去开线程加载高像素的图片会更加的有效。
以上是Google对LruCache的描述,其实LruCache的使用非常简单,跟Map非常相近,只是在创建LruCache对象的时候需要指定它的最大允许内存,一般设置为当前应用程序的最大运行内存的八分之一即可。
-
-
-
-
-
-
-
public class MemoryCacheUtils {
-
-
-
-
-
-
-
-
-
private LruCache<String, Bitmap> lruCache;
-
-
public MemoryCacheUtils() {
-
-
-
long mCurrentMemory = Runtime.getRuntime().maxMemory();
-
int maxSize = (int) (mCurrentMemory / 8);
-
-
lruCache = new LruCache<String, Bitmap>(maxSize) {
-
@Override
-
protected int sizeOf(String key, Bitmap value) {
-
-
-
int byteCount = value.getRowBytes() * value.getHeight();
-
return byteCount;
-
}
-
};
-
}
-
-
-
-
-
-
-
-
public Bitmap getBitmapFromMemory(String url) {
-
-
-
-
-
-
-
-
Bitmap bitmap = lruCache.get(url);
-
return bitmap;
-
-
}
-
-
-
-
-
-
-
-
public void setBitmap2Memory(String url, Bitmap bitmap) {
-
-
-
-
-
-
-
lruCache.put(url, bitmap);
-
}
-
-
}
好了。现在三级缓存策略封装完毕,接下来定制我们自己的BitmapUtils
-
-
-
-
-
-
-
-
public class CustomBitmapUtils {
-
-
private Bitmap bitmap;
-
-
private NetCacheUtils netCacheUtils;
-
private LocalCacheUtils localCacheUtils;
-
private MemoryCacheUtils memoryCacheUtils;
-
-
public CustomBitmapUtils() {
-
netCacheUtils = new NetCacheUtils();
-
localCacheUtils = new LocalCacheUtils();
-
memoryCacheUtils = new MemoryCacheUtils();
-
}
-
-
-
-
-
-
-
-
-
-
public void display(ImageView ivPic, String url) {
-
-
ivPic.setImageResource(R.drawable.ic_launcher);
-
-
-
bitmap = memoryCacheUtils.getBitmapFromMemory(url);
-
if (bitmap != null) {
-
ivPic.setImageBitmap(bitmap);
-
System.out.println("从内存缓存中加载图片");
-
return;
-
}
-
-
bitmap = localCacheUtils.getBitmapFromLocal(url);
-
if (bitmap != null) {
-
ivPic.setImageBitmap(bitmap);
-
System.out.println("从本地SD卡加载的图片");
-
memoryCacheUtils.setBitmap2Memory(url, bitmap);
-
return;
-
}
-
-
netCacheUtils.getBitmapFromNet(ivPic, url);
-
-
-
-
-
}
-
-
}
在mainActivity中使用ListView加载网络图片
-
-
-
-
-
-
-
public class MainActivity extends Activity {
-
-
private ListView list;
-
private Button btn;
-
private CustomBitmapUtils utils;
-
-
private static final String BASE_URL = "http://192.168.0.148:8080/pics";
-
-
String[] urls = { BASE_URL + "/1.jpg", BASE_URL + "/2.jpg",
-
BASE_URL + "/3.jpg", BASE_URL + "/4.jpg", BASE_URL + "/5.jpg",
-
BASE_URL + "/6.jpg", BASE_URL + "/7.jpg", BASE_URL + "/8.jpg",
-
BASE_URL + "/9.jpg", BASE_URL + "/10.jpg", BASE_URL + "/11.jpg",
-
BASE_URL + "/12.jpg", BASE_URL + "/13.jpg", BASE_URL + "/14.jpg",
-
BASE_URL + "/15.jpg", BASE_URL + "/16.jpg", BASE_URL + "/17.jpg",
-
BASE_URL + "/18.jpg", BASE_URL + "/19.jpg", BASE_URL + "/20.jpg",
-
BASE_URL + "/21.jpg", BASE_URL + "/22.jpg", BASE_URL + "/23.jpg",
-
BASE_URL + "/24.jpg", BASE_URL + "/25.jpg", BASE_URL + "/26.jpg",
-
BASE_URL + "/27.jpg", BASE_URL + "/28.jpg", BASE_URL + "/29.jpg",
-
BASE_URL + "/30.jpg" };
-
-
@Override
-
protected void onCreate(Bundle savedInstanceState) {
-
super.onCreate(savedInstanceState);
-
setContentView(R.layout.activity_main);
-
list = (ListView) findViewById(R.id.list);
-
btn = (Button) findViewById(R.id.btn_load);
-
utils = new CustomBitmapUtils();
-
-
-
btn.setOnClickListener(new OnClickListener() {
-
@Override
-
public void onClick(View v) {
-
MyAdapter adapter = new MyAdapter();
-
list.setAdapter(adapter);
-
}
-
});
-
}
-
-
class MyAdapter extends BaseAdapter {
-
-
@Override
-
public int getCount() {
-
return urls.length;
-
}
-
-
@Override
-
public String getItem(int position) {
-
return urls[position];
-
}
-
-
@Override
-
public long getItemId(int position) {
-
return position;
-
}
-
-
@Override
-
public View getView(int position, View convertView, ViewGroup parent) {
-
ViewHolder holder;
-
if (convertView == null) {
-
convertView = View.inflate(MainActivity.this,
-
R.layout.item_list, null);
-
holder = new ViewHolder();
-
holder.ivPic = (ImageView) convertView.findViewById(R.id.iv);
-
convertView.setTag(holder);
-
} else {
-
holder = (ViewHolder) convertView.getTag();
-
}
-
utils.display(holder.ivPic, urls[position]);
-
return convertView;
-
}
-
-
class ViewHolder {
-
ImageView ivPic;
-
}
-
}
-
}
运行的结果如下:
程序第一次运行,日志打印如下
之后将图片缓存在SD卡中,从本地加载图片
然后将图片缓存到内存,从内存加载图片
OK,到目前为止,Android中图片的三级缓存原理就都介绍完了,我自己本人受益匪浅,希望能够帮助到需要的朋友。
Android开发中图片的三级缓存策略
标签:
原文地址:http://blog.csdn.net/qq_26420489/article/details/51151938