在学习了郭霖大神的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。
具体每一步的逻辑在代码中。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/3172.html