作为一名Android开发人员,相信大家对图片OOM的问题已经耳熟能详了,其中防止多图OOM的核心解决思路就是使用图片的3级缓存,那么就有几个疑问了,
1、图片3级缓存是哪几个?
它们分别是内存、外存、网络。图片的读取首先从内存读取,如果找到就返回一个bitmap,否则到外存读取,如果外存还没有,最后才到网络上去读取。
2、3级缓存技术在android总如何体现和使用?
内存缓存使用LruCache,我们知道,一个app一般分配16M或24M的内存,如果加载的图片很大很多,很容易超过容量上限导致oom,所以我们一般分配固定的内存给图片内存缓存用,具体介绍和使用可以参考
http://blog.csdn.net/guolin_blog/article/details/9526203
LruCache只是管理了内存中图片的存储与释放,如果容量超过上限会把自动把最近最少使用的图片从缓存中移除。如果图片从内存中被移除的话,那么异步开启线程从网络上读取。
privat LruCache<String, Bitmap> mMemoryCache;//这个是初始化LruCache的方法
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 bitmap) {
return bitmap.getByteCount(); //返回图片的字节数组大小
}
};
这个类有2个核心方法
public void addBitmapToMemoryCache(String key, Bitmap bitmap) { //将一张图片存入LruCache中
if (getBitmapFromMemoryCache(key) == null) { //key为图片网络url地址
mMemoryCache.put(key, bitmap); //value为图片的bitmap对象
}
}
public Bitmap getBitmapFromMemoryCache(String key) { 从LruCache中获取一张图片,如果不存在就返回nul
return mMemoryCache.get(key); //返回的是一个bitmap对象
}
一个内存缓存对加速访问最近浏览过的Bitmap非常有帮助,但是你不能局限于内存中的可用图片。GridView这样有着更大的数据集的组件可以很轻易消耗掉内存缓存。你的应用有可能在执行其他任务(如打电话)的时候被打断,并且在后台的任务有可能被杀死或者缓存被释放。一旦用户重新聚焦(resume)到你的应用,你得再次处理每一张图片。所以在这种情况下,硬盘缓存可以用来存储Bitmap并在图片被内存缓存释放后减小图片加载的时间(次数)。当然,从硬盘加载图片比内存要慢,并且应该在后台线程进行,因为硬盘读取的时间是不可预知的。所以这个时候外存缓存使用DiskLruCache,
DiskLruCache mDiskLruCache = null; //DiskLruCache的初始化
try {
File cacheDir = getDiskCacheDir(context, "bitmap");
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(context), 1, 10 * 1024 * 1024);
} catch (IOException e) { //cacheDir是外存缓存的地址 最后的参数是缓存大小为10MB
e.printStackTrace();
}
关于这个的介绍可以参考
http://blog.csdn.net/guolin_blog/article/details/28863651
图片在从网络上获取到之后都会存入到本地缓存中,因此即使手机在没有网络的情况下依然能够加载出以前浏览过图片。
补充:
由于DiskLruCache并不是由Google官方编写的,所以这个类并没有被包含在Android API当中,我们需要将这个类从网上下载下来,然后手动添加到项目当中。
但是,一个优秀的程序必然会将内存缓存和硬盘缓存结合到一起使用,如何才能将LruCache和DiskLruCache完美结合到一起,具体参考http://blog.csdn.net/guolin_blog/article/details/34093441
另外,网络读取使用HttpClient,可以参考我的上篇文章
3、BitmapFun是一个谷歌的开源异步读取图片的框架,使用了3级图片缓存技术,BitmapFun中在下载后将Bitmap缓存起来,缓存做了两份:LruCache和DiskLruCache,分别是内存缓存和硬盘缓存,BitmapFun使用的LruCache是将它将最近被引用到的对象存储在一个强引用的LinkedHashMap中,并且在缓存超过了指定大小之后将最近不常使用的对象释放掉。但是Memory Cache的Size是受限的,因此加入DiskLruCache,虽然在访问速度上逊于Memory Cache,但是速度也是相当可观的。也就是说它自己里面已经封装了LruCache和DiskLruCache和网络异步读取的所有操作,我们只用简单调用loadImage(图片url地址,image控件)方法即可,
所以总结bitmapFun的加载图片流程为
① UI:请求数据,使用唯一的Key值索引Memory Cache中的Bitmap。
② 内存缓存:缓存搜索,如果能找到Key值对应的Bitmap,则返回数据。否则执行第三步。
③ 硬盘存储:使用唯一Key值对应的文件名,检索SDCard上的文件。
④ 如果有对应文件,使用BitmapFactory.decode*方法,解码Bitmap并返回数据,同时将数据写入缓存。如果没有对应文件,执行第五步。
⑤ 下载图片:启动异步线程,从数据源下载数据(Web)。
⑥ 若下载成功,将数据同时写入硬盘和缓存,并将Bitmap显示在UI中。
源码分析参考
http://blog.csdn.net/yuanzeyao/article/details/38355719
注意的是,BitmapFun这样子结合了memory和disk缓存和异步线程,是可以很大程度减少内存开销。但是还是有必要根据图片的大小做图片的压缩。
这里就需要使用BItmapFactory.option里面的inSampleSize来进行图片的压缩了。
=========================我是分割线==================================
接下来我们看一个具体例子:使用了BItMapFun 和GridView 展示图片,之前要记得导入BitMapFun的jar包
1、Image这个类放入一个图片的字符串数组,方便使用
public class Image {
public final static String[] imageThumbUrls = new String[] {
"https://img-my.csdn.net/uploads/201407/26/1406383299_1976.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383291_6518.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383291_8239.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383290_9329.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383290_1042.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383275_3977.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383265_8550.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383264_3954.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383264_4787.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383264_8243.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383248_3693.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383243_5120.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383242_3127.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383242_9576.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383242_1721.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383219_5806.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383214_7794.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383213_4418.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383213_3557.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383210_8779.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383172_4577.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383166_3407.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383166_2224.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383166_7301.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383165_7197.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383150_8410.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383131_3736.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383130_5094.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383130_7393.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383129_8813.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383100_3554.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383093_7894.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383092_2432.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383092_3071.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383091_3119.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383059_6589.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383059_8814.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383059_2237.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383058_4330.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406383038_3602.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382942_3079.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382942_8125.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382942_4881.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382941_4559.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382941_3845.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382924_8955.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382923_2141.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382923_8437.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382922_6166.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382922_4843.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382905_5804.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382904_3362.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382904_2312.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382904_4960.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382900_2418.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382881_4490.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382881_5935.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382880_3865.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382880_4662.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382879_2553.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382862_5375.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382862_1748.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382861_7618.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382861_8606.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382861_8949.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382841_9821.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382840_6603.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382840_2405.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382840_6354.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382839_5779.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382810_7578.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382810_2436.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382809_3883.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382809_6269.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382808_4179.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382790_8326.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382789_7174.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382789_5170.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382789_4118.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382788_9532.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382767_3184.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382767_4772.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382766_4924.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382766_5762.jpg",
"https://img-my.csdn.net/uploads/201407/26/1406382765_7341.jpg"
};
}
2、MainActivity里的具体代码
//使用了gridview和BitMapFun去网络上下载图片
public class MainActivity extends Activity {
private GridView gridViewp;
private ImageView imageView;
private String[] data = Image.imageThumbUrls;
private String url;
private ImageFetcher fetcher;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fetcher = new ImageFetcher(getApplicationContext(), 980, 980);
fetcher.setExitTasksEarly(false);// 打开后台线程
fetcher.setImageCache(new ImageCache(getApplicationContext(), "hemiy"));// 设置图片的磁盘缓存空间
gridViewp = (GridView) findViewById(R.id.photo_wall);
gridViewp.setAdapter(new BaseAdapter() {
class ViewHolder {
public ImageView image;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
url = (String) getItem(position);
ViewHolder holder = null;
if (convertView == null) {
holder = new ViewHolder();
convertView = View.inflate(getApplicationContext(),
R.layout.photo_item, null);
holder.image = (ImageView) convertView
.findViewById(R.id.photo);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
fetcher.loadImage(url, holder.image);
return convertView;
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return data[position];
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return data.length;
}
});
gridViewp.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Toast.makeText(getApplicationContext(),
//这里的++position是正确指示点击的个数
"你点击的是第" + (++position)+ "个", 1).show();
}
});
}
public void clearCache(View v){
//无论怎样,外存里面的http文件夹下一定会有文件,但是里面图片大。我们自己设置的cache图片小
DiskLruCache.clearCache(getApplicationContext(), "http");
DiskLruCache.clearCache(getApplicationContext(), "hemiy");
}
@Override
protected void onStop() {
super.onStop();
fetcher.setExitTasksEarly(true); // 关闭后台线程
}
}
3、MainActivity里面的xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<!--
android:numColumns="auto_fit" ,GridView的列数设置为自动
android:columnWidth="90dp",每列的宽度,也就是Item的宽度
android:stretchMode="columnWidth",缩放与列宽大小同步
android:verticalSpacing="10dp",两行之间的边距,如:行一(NO.0~NO.2)与行二(NO.3~NO.5)间距为10dp
android:horizontalSpacing="10dp",两列之间的边距。
-->
<!-- 以下的例子是完全按照郭林的范例
android:columnWidth="100dp" 实际控制图片的大小
在宽度不一样大小的屏幕上,列数会不一样
-->
<GridView
android:id="@+id/photo_wall"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center"
android:columnWidth="100dp"
android:stretchMode="columnWidth"
android:numColumns="auto_fit"
android:horizontalSpacing="1dp"
android:verticalSpacing="1dp" >
</GridView>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="clearCache"
android:text="清除缓存" />
</LinearLayout></span>
photo_item的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="match_parent"
android:orientation="vertical" >
<ImageView
android:id="@+id/photo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:scaleType="fitXY"
android:src="@drawable/ic_launcher" />
</LinearLayout>
4、最后记得在注册文件中,写上权限
<!-- 联网 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 外存储卡 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<!-- 访问网络状态 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
完成了,可以在手机和平板上测试,手机上显示3个列表,而平板显示多个列表,但是每个列表的宽度是一样的。因为在GridView里面设置了
android:columnWidth="100dp"