在学习了郭霖大神的Android高效加载大图、多图解决方案,有效避免程序OOM 之后,又跟着郭大神学习了照片墙的实现
Android照片墙应用实现,再多的图片也不怕崩溃 使用缓存技术使得加载图片避免了OOM(Out Of Memory),下面是做此次照片墙的思
路和记录
第一步:定义图片数据源
第二步:编写主界面布局,使用GridView
第三步:编写每一个item的布局,使用ImageView
第四步:有数据和GridView,因此也要编写一个适配器类
public class PhotoWallApdater extends ArrayAdapter<String> implements OnScrollListener {
//记录所有正在下载或等待下载的任务,便于一次性中断任务
private Set<BitMapWorkTask> taskCollection;
//图片缓存
private LruCache<String, Bitmap> mMemoryCache;
//获得布局实例
private GridView mPhotoWall;
//第一个可见的图片下标
private int mFirstVisibleItem;
//一屏可见多少图片
private int mVisibleItemCount;
//记录是否第一次进入程序
private boolean mIsFirstEnter = true;
public PhotoWallApdater(Context context, int textViewResourceId, String[] objects, GridView photoWall) {
super(context, textViewResourceId, objects);
mPhotoWall = photoWall;
taskCollection = new HashSet<BitMapWorkTask>();
//设置缓冲区大小
int maxMemory = (int) Runtime.getRuntime().maxMemory();
int maxCache = maxMemory / 10;
mMemoryCache = new LruCache<String, Bitmap>(maxCache){
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount();//返回图片个数
}
};
mPhotoWall.setOnScrollListener(this);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
String imageUrl = getItem(position);
View view;
if(convertView == null){
view = LayoutInflater.from(getContext()).inflate(R.layout.photo_layout, null);
}else {
view = convertView;
}
ImageView photo = (ImageView) view.findViewById(R.id.photo);
// 给ImageView设置一个Tag,保证异步加载图片时不会乱序
photo.setTag(imageUrl);
setImageView(imageUrl, photo);
return view;
}
/**
* 给ImageView设置图片。首先从LruCache中取出图片的缓存,设置到ImageView上。如果LruCache中没有该图片的缓存,
* 就给ImageView设置一张默认图片。
*
* @param imageUrl
* 图片的URL地址,用于作为LruCache的键。
* @param imageView
* 用于显示图片的控件。
*/
private void setImageView(String imageUrl, ImageView photo) {
Bitmap bitmap = getBitmapFromMemoryCache(imageUrl);
if(bitmap != null){
photo.setImageBitmap(bitmap);
}else{
}
}
/**
* 将一张图片存储到LruCache中。
*
* @param key
* LruCache的键,这里传入图片的URL地址。
* @param bitmap
* LruCache的键,这里传入从网络上下载的Bitmap对象。
*/
private void addBitmapFromMemoryCache(String key, Bitmap bitmap) {
if(getBitmapFromMemoryCache(key) == null){
mMemoryCache.put(key, bitmap);
}
}
/**
* 从LruCache中获取一张图片,如果不存在就返回null。
*
* @param key
* LruCache的键,这里传入图片的URL地址。
* @return 对应传入键的Bitmap对象,或者null。
*/
private Bitmap getBitmapFromMemoryCache(String key) {
Bitmap bitmap = mMemoryCache.get(key);
return bitmap;
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// 仅当GridView静止时才去下载图片,GridView滑动时取消所有正在下载的任务
if (scrollState == SCROLL_STATE_IDLE) {
loadBitmaps(mFirstVisibleItem, mVisibleItemCount);
} else {
cancleAllTasks();
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
mFirstVisibleItem = firstVisibleItem;
mVisibleItemCount = visibleItemCount;
// 下载的任务应该由onScrollStateChanged里调用,但首次进入程序时onScrollStateChanged并不会调用,
// 因此在这里为首次进入程序开启下载任务。
if (mIsFirstEnter && (visibleItemCount > 0)) {
loadBitmaps(firstVisibleItem, visibleItemCount);
mIsFirstEnter = false;
}
}
/**
* 加载Bitmap对象。此方法会在LruCache中检查所有屏幕中可见的ImageView的Bitmap对象,
* 如果发现任何一个ImageView的Bitmap对象不在缓存中,就会开启异步线程去下载图片。
*
* @param firstVisibleItem
* 第一个可见的ImageView的下标
* @param visibleItemCount
* 屏幕中总共可见的元素数
*/
private void loadBitmaps(int firstVisibleItem, int visibleItemCount) {
try {
for (int i = firstVisibleItem; i < firstVisibleItem + visibleItemCount; i++) {
String imageUrl = Images.imageUrls[i];
Bitmap bitmap = getBitmapFromMemoryCache(imageUrl);
if (bitmap == null) {
BitMapWorkTask task = new BitMapWorkTask();
taskCollection.add(task);
task.execute(imageUrl);
} else {
ImageView imageView = (ImageView) mPhotoWall.findViewWithTag(imageUrl);
if (imageView != null && bitmap != null) {
imageView.setImageBitmap(bitmap);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void cancleAllTasks() {
if(taskCollection != null){
for(BitMapWorkTask task : taskCollection){
task.cancel(false);
}
}
}
class BitMapWorkTask extends AsyncTask<String, Void, Bitmap>{
private String imageUrl;
@Override
protected Bitmap doInBackground(String... params) {
imageUrl = params[0];
Bitmap bitmap = downloadBitmap(imageUrl);
if(bitmap != null){
//如果下载图片成功,就把它放入缓冲区
addBitmapFromMemoryCache(imageUrl, bitmap);
}
//把值返回到onPostExecute,更新主线程UI
return bitmap;
}
@Override
protected void onPostExecute(Bitmap result) {
super.onPostExecute(result);
ImageView imageView = (ImageView) mPhotoWall.findViewWithTag(imageUrl);
if(imageView != null && result != null){
imageView.setImageBitmap(result);
}
taskCollection.remove(this);
}
private Bitmap downloadBitmap(String imageUrl) {
Bitmap bitmap = null;
HttpURLConnection conn = null;//如何判断哪些需要初始化哪些不需要初始化
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
URL url = new URL(imageUrl);
conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(5000);
conn.setConnectTimeout(5000);
is = conn.getInputStream();
/*
* byte方式
*/
// int len = -1;
// byte[] buf = new byte[1024];
// baos = new ByteArrayOutputStream();
//
// while((len = is.read(buf)) != -1){//is.read字节读取,读入缓冲区数组buf中
// baos.write(buf, 0, len);//把数组写入字节数组buf中
// }
// baos.flush();
//
// byte[] data = baos.toByteArray();
//
// bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
/*
* stream方式
*/
bitmap = BitmapFactory.decodeStream(is);
Log.d("TAG", "downloadBitmap调用了");
} catch (Exception e) {
e.printStackTrace();
} finally{
if(conn != null){
conn.disconnect();
}
}
return bitmap;
}
}
}
适配器的思路是这样的:
一:GridView要滑动,所以我们让它实现了OnScrollListener监听器,并实现了监听器的两个方法
1.public void onScrollStateChanged(AbsListView view, int scrollState)
这个方法用于处理屏幕滑动时的事件,在这里我们限定了,当屏幕静止时才开启加载图片的任务,滑动时则停止任务
2.public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
这个方法用于处理刚进入程序时的事件,在这里我们限定了第一次进入程序时要开启加载图片任务,不然会不显示图片
二:进入加图片的任务后,首先从缓存区通过键查找缓存区是否有该键值对应的图片,如果有的话,直接把该Bitmap对象赋给imageView的实例,如果缓存区没有,则把url传入,开启下载任务。
三:下载任务中使用异步任务,在doinBackgroud中去下载图片,如果下载成功,则首先把图片加入缓存区,然后把Bitmap对象返回到onPostExecute()中去更新UI,设置imageView。
具体每一步的逻辑在代码中。