以下我从四个个方面来讨论:
安卓的内存机制(为什么会产生内存溢出?)
安卓内存机制:
安卓中应用程序在运行时,系统会给该应用分配一定的内存,系统不同大小可能存在出入,一般为16M,所以如果加载大量图片,特别是高清图片,内存就有点吃紧。而我们知道java虚拟机的内存回收机制,不会去回收强引用的对象,况且垃圾回收机制本身就不及时。(下文会谈到java的内存回收机制)。
图片所占内存较大,容易导致OOM(内存溢出)
概念:引用
public Bitmap download(String url) {
HttpURLConnection conn;
try {
URL murl = new URL(url);
conn = (HttpURLConnection) murl.openConnection();
conn.setRequestMethod("GET");// 请求方式,必须大写
conn.setConnectTimeout(5000);// 连接超时时间
conn.setReadTimeout(5000);// 读取超时时间
conn.connect();// 开始连接
int code = conn.getResponseCode();// 获取返回码
if (code == 200) {
// 成功后获取流,进行处理
InputStream is = conn.getInputStream();
// 根据流来获取对应的数据,这里是图片,所以直接根据流得到bitmap对象
Bitmap bitmap = BitmapFactory.decodeStream(is);
return bitmap;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//代码说明:这是一个ListView的适配器,每一个item中展示的图片都来自网络,每次加载图片时,调用了BitmapUtils 的display方法,就会下载对应的图片并显示在ImageView中
class PicListAdapter extends BaseAdapter {
public BitmapUtils bitmapUtils; //xUtils的图片加载工具类
// public MyBitmapUtils bitmapUtils;
public PicListAdapter() {
bitmapUtils = new BitmapUtils(mActivity);
//设置默认加载的图片,当网络请求失败,默认显示的图片
bitmapUtils.configDefaultLoadFailedImage(R.drawable.pic_item_list_default);
bitmapUtils = new MyBitmapUtils();
}
@Override
public int getCount() {
return photosDataList.size();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder ;
if(convertView==null){
holder = new ViewHolder();
convertView = View.inflate(mActivity, R.layout.item_list_pic, null);
holder.ivNews = (ImageView) convertView.findViewById(R.id.iv_pic);
holder.tvTitle = (TextView) convertView.findViewById(R.id.tv_title);
convertView.setTag(holder);
}else{
holder = (ViewHolder) convertView.getTag();
}
PhotosData item = photosDataList.get(position);
bitmapUtils.display(holder.ivNews, item.listimage);
// bitmapUtils.display(holder.ivNews, item.listimage, R.drawable.pic_item_list_default);
holder.tvTitle.setText(item.title);
return convertView;
}
@Override
public PhotosData getItem(int position) {
return photosDataList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
}
MyBitmapUtils工具类(自己实现的加载图片的工具类)使用时,只需要调用display方法,传入相应的ImageView 图片的url和默认显示的图片资源ID, 其中用到的工具类代码附在后面。
package com.cattsoft.zhhxa.utils;
import android.graphics.Bitmap;
import android.widget.ImageView;
/**
* 利用三级缓存加载图片的工具类
*
* @author ZXJM
* @date 2016年8月23日
*
*/
public class MyBitmapUtils {
private NetCacheUtils mNetCacheUtil;//网络缓存工具类
private LocalCacheUtils mLocalCacheUtil;//本地缓存工具类
private MemoryCacheUtils mMemoryCacheUtil;//内存缓存工具类
public MyBitmapUtils() {
mMemoryCacheUtil = new MemoryCacheUtils();
mLocalCacheUtil = new LocalCacheUtils();
mNetCacheUtil = new NetCacheUtils(mLocalCacheUtil, mMemoryCacheUtil);
}
/**
*
* @param imageView
* 要展示加载图片的ImageView
* @param url
* 加载图片的链接
* @param resId
* 默认图片的资源id
*/
public void display(ImageView imageView, String url, int resId) {
// 设置默认图片
imageView.setImageResource(resId);
Bitmap bitmap = null;
// 0.先从内存加载,如果内存中有值
bitmap = mMemoryCacheUtil.getMemoryCache(url);
if (bitmap != null) {
System.out.println("从内存中加载");
imageView.setImageBitmap(bitmap);
return;
}
// 1.先从本地加载,判断是否有本地缓存
bitmap = mLocalCacheUtil.getLocalCache(url);
if (bitmap != null) {
System.out.println("从本地加载");
imageView.setImageBitmap(bitmap);
//写内存缓存
mMemoryCacheUtil.setMemoryCache(url, bitmap);
return;
}
// 2.从网络加载
mNetCacheUtil.getBitmapFromNet(imageView, url);
System.out.println("从网络加载");
}
}
NetCacheUtils 网络缓存工具类
package com.cattsoft.zhhxa.utils;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.widget.ImageView;
/**
* 网络缓存工具
*
* @author ZXJM
* @date 2016年8月23日
*
*/
public class NetCacheUtils {
private LocalCacheUtils mLocalCacheUtil;
private MemoryCacheUtils mMemoryCacheUtil;
public NetCacheUtils(LocalCacheUtils localCacheUtil,
MemoryCacheUtils memoryCacheUtil) {
super();
this.mLocalCacheUtil = localCacheUtil;
this.mMemoryCacheUtil = memoryCacheUtil;
}
// 从网络加载图片
public void getBitmapFromNet(ImageView imageView, String url) {
// imageView,url 两个参数会封装为数组传给doInBackground
new BitmapTask().execute(imageView, url);
}
/**
* AsyncTask的用法
*
* @author ZXJM
* @date 2016年8月23日 三个泛型的含义:参1:doInBackground的参数类型 参2:onProgressUpdate的参数类型
* 参3:doInBackground 加载完的返回类型,和onPostExecute 加载结束后处理的入参类型
*/
class BitmapTask extends AsyncTask
LocalCacheUtils 本地缓存工具类
package com.cattsoft.zhhxa.utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.os.Environment;
/**
* 本地缓存工具类
*
* @author ZXJM
* @date 2016年8月23日
*
*/
public class LocalCacheUtils {
private static final String LOCAL_CACHE_PATH = Environment
.getExternalStorageDirectory().getAbsolutePath()+"/zhxa_cache";
//写本地缓存
public void setLocalCache(String url,Bitmap bitmap) {
File dir = new File(LOCAL_CACHE_PATH);
if(!dir.exists() || !dir.isDirectory()){
dir.mkdirs();//创建文件夹
}
try {
String fileName = MD5Encoder.encode(url);//采用MD5加密文件名
File cacheFile = new File(dir, fileName);
// 参1:图片格式;参2:压缩比例0-100; 参3:输出流
bitmap.compress(CompressFormat.JPEG, 100, new FileOutputStream(cacheFile));
} catch (Exception e) {
e.printStackTrace();
}
}
//读本地缓存
public Bitmap getLocalCache(String url) {
try {
File cacheFile = new File(LOCAL_CACHE_PATH,MD5Encoder.encode(url));
if(cacheFile.exists()){
Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(cacheFile));
return bitmap;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
MemoryCacheUtils 内存缓存工具类
package com.cattsoft.zhhxa.utils;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import android.graphics.Bitmap;
/**
* 内存缓存工具类
* 1.第一次优化,利用软引用解决可能的OOM异常
*
* @author ZXJM
* @date 2016年8月23日
*
*/
public class MemoryCacheUtils {
// private HashMap hash;
private HashMap> hash;
// 写内存缓存
public void setMemoryCache(String url, Bitmap bitmap) {
// if (hash == null) {
// hash = new HashMap();
// }
// hash.put(url, bitmap);
if(hash == null){
hash = new HashMap>();
}
//使用软引用把Bitmap包装起来
SoftReference soft = new SoftReference(bitmap);
hash.put(url, soft);
}
// 读内存缓存
public Bitmap getMemoryCache(String url) {
// if (hash != null && hash.containsKey(url)) {
// Bitmap bitmap = hash.get(url);
// return bitmap;
// }
if(hash!=null && hash.containsKey(url)){
SoftReference soft = hash.get(url);
Bitmap bitmap = soft.get();
return bitmap;
}
return null;
}
}
上面我们给出了一套自己实现的利用三级缓存加载图片的方案,这很好解决了加载速度,节省用户流量等体验问题,但随着图片数量的增多,内存缓存这一环节就会暴露内存溢出的问题。
优化1.利用软引用解决内存缓存时可能出现的内存溢出
优化2.利用LruCache解决内存缓存
以上分析,我们知道,在通过网络加载图片时,为了更好的体验,我们使用了三级缓存的一个机制,并且在内存缓存中利用Lrucache进行处理,防止OOM异常。但是,我们上面自己实现的方案最多算做一个Demo,可以加深我们对网络加载图片内存溢出机制有更深入的理解。开发中,我们还是使用xUtils,因为xUtils已经对这些问题做了很好的规避,并且非常稳定,出现异常的概率远比我们自己写的Demo低。