android开发步步为营之89:ListView和GridView动态加载图片,保证不导致OOM
2015-12-21 21:54
579 查看
上篇文章写了个网络相册,但是这个相册在内存不足的情况下肯定OOM(Out of memory),虽然使用了LruCache(如果是应用中常用的图片,我们可以下载后保存到用户手机,下次直接从手机读取,节省用户流量,本篇只是假设用户查看他人网络相册,所以就不保存到手机了),但是记住LruCache只是个缓存,方便快速取数据的,仔细想想,我们分配一定的内存空间给LruCache,这样一部分的图片确实只要在LruCache里面取了,但是,如果有10万或者更多的照片需要下载下来显示给用户看呢?开启线程,火力全开,全部加载到ImageView?假设一张图片100K,那么总共需要内存:100000*100/1024/1024=9.53G,性能好一点的手机只有3个G左右,不用说很多手机会导致程序直接崩溃。那么我们采取什么办法呢?方法就是:动态加载,就是只加载用户滑动屏幕看到的这一屏数据。怎么来做呢?对就是使用GridView或者ListView的OnScrollListener事件,在用户滑动的过程中取消下载的任务,滑动结束后再去加载看到的这一屏数据。好,直接贴出代码:
第一步:设计页面
activity_async_task.xml
layout_item_image.xml
第二步:编写Activity
第一步:设计页面
activity_async_task.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="wrap_content" android:orientation="vertical" android:gravity="center"> <TextView android:text="@string/lb_album" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <GridView android:id="@+id/gv_album" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:layout_marginStart="10dp" android:layout_marginRight="10dp" android:layout_marginEnd="10dp" android:horizontalSpacing="10dp" android:numColumns="4" android:scrollbars="none" android:verticalSpacing="10dp"></GridView> </LinearLayout>
layout_item_image.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"> <ImageView android:layout_width="match_parent" android:layout_height="100dp" android:src="@mipmap/ic_launcher" android:id="@+id/img_show" /> </LinearLayout>
第二步:编写Activity
package com.figo.study.activity; import android.app.Activity; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.AsyncTask; import android.os.Bundle; import android.support.v4.util.LruCache; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.BaseAdapter; import android.widget.GridView; import android.widget.ImageView; import android.widget.ListView; import com.figo.study.R; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.concurrent.Executor; import java.util.concurrent.Executors; public class AsyncTaskActivity extends Activity { //单个任务建议用 // Executor mSingeThreadExecutor = Executors.newSingleThreadExecutor(); //不设线程个数,建议还是别这样用,免得占用cpu过长时间 // Executor mCacheExecutor = Executors.newCachedThreadPool(); //开启固定个数线程的线程池 Executor mFixedExecutor = Executors.newFixedThreadPool(6); //内存自动回收 private LruCache<String, Bitmap> mLruCache; private HashMap<String, DownloadPicTask> mTasks = new HashMap<String, DownloadPicTask>(); private GridView mGridView; private ArrayList<String> mAlImgUrls = new ArrayList<String>(); private int mFirstVisibleItem, mVisibleItemCount; private boolean mIsFirstEnter = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_async_task); initView(); } private void initView() { mGridView = (GridView) findViewById(R.id.gv_album); mGridView.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (scrollState == SCROLL_STATE_IDLE) { loadBitmaps(mFirstVisibleItem, mVisibleItemCount); } else { cancelAllTasks(); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { mFirstVisibleItem = firstVisibleItem; mVisibleItemCount = visibleItemCount; // 因此在这里为首次进入程序开启下载任务。 if (mIsFirstEnter && visibleItemCount > 0) { loadBitmaps(firstVisibleItem, visibleItemCount); mIsFirstEnter = false; } } }); /**ListView同样适用 mListView = (ListView) findViewById(R.id.lv_album); mListView.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (scrollState == SCROLL_STATE_IDLE) { loadBitmaps(mFirstVisibleItem, mVisibleItemCount); } else { cancelAllTasks(); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { mFirstVisibleItem = firstVisibleItem; mVisibleItemCount = visibleItemCount; // 因此在这里为首次进入程序开启下载任务。 if (mIsFirstEnter && visibleItemCount > 0) { loadBitmaps(firstVisibleItem, visibleItemCount); mIsFirstEnter = false; } } }); */ int maxMemory = (int) Runtime.getRuntime().maxMemory(); int cacheSize = maxMemory / 10; mLruCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap value) { return value.getByteCount(); } }; for (int a = 0; a < 10000; a++) { mAlImgUrls.add("http://img1.3lian.com/gif/more/11/201209/0031dd714277655526ca4effc190d2c1.jpg"); mAlImgUrls.add("http://img1.3lian.com/gif/more/11/201209/4c150bc639803ff471f156a06df45ef2.jpg"); mAlImgUrls.add("http://img1.3lian.com/gif/more/11/201209/260c730eb8ee9ed34b1d007e27906ae1.jpg"); mAlImgUrls.add("http://img1.3lian.com/gif/more/11/201209/46b6004db53f7d121ba24dd11fcbe2fc.jpg"); mAlImgUrls.add("http://img1.3lian.com/gif/more/11/201209/dd4e588612313a3da7b51f6bd3904c74.jpg"); mAlImgUrls.add("http://img1.3lian.com/gif/more/11/201209/26b50769eab624fade3a2fcfab030f65.jpg"); mAlImgUrls.add("http://img1.3lian.com/gif/more/11/201209/13b8960de9966973fc572aab2cca622b.jpg"); mAlImgUrls.add("http://img1.3lian.com/gif/more/11/201209/fa3bdd2dfe736262d8dc20844d0e8ab0.jpg"); mAlImgUrls.add("http://img1.3lian.com/gif/more/11/201209/14d7bc3a224c5a5b18bec98930094aae.jpg"); mAlImgUrls.add("http://img1.3lian.com/gif/more/11/201209/e4a9726bf6d11817470aeb8b71c82441.jpg"); mAlImgUrls.add("http://img1.3lian.com/gif/more/11/201209/daa16f54a122d15328ce5d9931cf283f.jpg"); mAlImgUrls.add("http://img1.3lian.com/gif/more/11/201209/0e210425982c39d212585696ac31e572.jpg"); mAlImgUrls.add("http://img1.3lian.com/gif/more/11/201209/50650c08fcbe1c7ef053f3d2c8731610.jpg"); mAlImgUrls.add("http://img1.3lian.com/gif/more/11/201209/bfc3a909e0c9863bd13720b051d9a0e6.jpg"); mAlImgUrls.add("http://img1.3lian.com/gif/more/11/201209/87ee21c00737eb4fb74b3d00fd0c08c8.jpg"); mAlImgUrls.add("http://img1.3lian.com/gif/more/11/201209/066c9456758ceb11c926025eb0105ec8.jpg"); mAlImgUrls.add("http://img1.3lian.com/gif/more/11/201209/4451ccb0f6312ae16cce70b3630b04f5.jpg"); } mGridView.setAdapter(new PictureAdapter()); // mListView.setAdapter(new PictureAdapter()); } /** * 取消所有正在下载或等待下载的任务。 */ public void cancelAllTasks() { if (mTasks != null) { for (DownloadPicTask task : mTasks.values()) { task.cancel(false); } } } private void loadBitmaps(int firstVisibleItem, int visibleItemCount) { try { for (int a = firstVisibleItem; a < firstVisibleItem + visibleItemCount; a++) { String imageUrl = mAlImgUrls.get(a); Bitmap bitmap = getBitmapFromLruCache(imageUrl); if (bitmap == null) { //同一个位置的避免开启多个线程去加载 if (!mTasks.containsKey(imageUrl + a)) { DownloadPicTask task = new DownloadPicTask(); mTasks.put(imageUrl + a, task); task.executeOnExecutor(mFixedExecutor, imageUrl, String.valueOf(a)); } } else { ImageView imageView = (ImageView) mGridView.findViewWithTag(imageUrl + a); // ImageView imageView = (ImageView) mListView.findViewWithTag(imageUrl + a); if (imageView != null && bitmap != null) { imageView.setImageBitmap(bitmap); } } } } catch (Exception e) { e.printStackTrace(); } } public Bitmap getBitmapFromLruCache(String key) { return mLruCache.get(key); } class PictureAdapter extends BaseAdapter { @Override public Object getItem(int position) { return null; } @Override public int getCount() { return mAlImgUrls.size(); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if (convertView == null) { holder = new ViewHolder(); //从xml页面实例化视图 convertView = LayoutInflater.from(AsyncTaskActivity.this).inflate(R.layout.layout_item_image, null); //填充我们定义的容器里面的控件 holder.img = (ImageView) convertView.findViewById(R.id.img_show); convertView.setTag(holder); } else { //直接从视图里面获取控件容器 holder = (ViewHolder) convertView.getTag(); } ImageView img = holder.img; img.setTag(mAlImgUrls.get(position) + position);//兼容,图片地址一致的情况 if (getBitmapFromLruCache(mAlImgUrls.get(position)) != null) { img.setImageBitmap(getBitmapFromLruCache(mAlImgUrls.get(position))); } return convertView; } } // 视图容器 public final class ViewHolder { public ImageView img; } //下载图片异步任务 class DownloadPicTask extends AsyncTask<String, Integer, Bitmap> { String imgUrl; String position; public DownloadPicTask() { } @Override protected void onPreExecute() { super.onPreExecute(); } @Override protected void onPostExecute(Bitmap bitmap) { ImageView imageView = (ImageView) mGridView.findViewWithTag(imgUrl + position); // ImageView imageView = (ImageView) mListView.findViewWithTag(imgUrl + position); if (imageView != null && bitmap != null) { imageView.setImageBitmap(bitmap); } } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); } @Override protected Bitmap doInBackground(String... params) { try { imgUrl = params[0].toString(); position = params[1].toString(); return getBitmap(imgUrl); } catch (Exception e) { return null; } } } //获取网络图片 public Bitmap getBitmap(String path) throws Exception { if (mLruCache.get(path) != null) { return mLruCache.get(path); } else { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5 * 1000); conn.setRequestMethod("GET"); InputStream inStream = conn.getInputStream(); if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { Bitmap bitmap = BitmapFactory.decodeStream(inStream); mLruCache.put(path, bitmap); return bitmap; } } return null; } }代码都是测试通过的,大家拿去用即可。运行效果如下:
相关文章推荐
- android shape的使用
- Mars 老师 Android 教程之 Activity 生命周期学习笔记
- 在android studio中导入开源库PullToRefresh
- Android内存泄露
- android实现课程表界面
- 给 Android 开发者的 RxJava 详解
- Mars Android 视频教程之 Activity 生命周期学习笔记
- 通过Wifi调试运行Android应用的IntelliJ/AndroidStudio插件:AndroidWiFiADB
- Android Service使用Messenger通信
- Android back键两次退出
- 给 Android 开发者的 RxJava 详解(作者:扔物线)
- android-USB Accessory
- android单元测试
- Android开发之序列化接口
- Android studio 性能优化
- Android apk 签名原理
- android studio 打包流程
- Android 开发者必备的书单
- Android笔记 - Binder之基本概念
- Android Service两种启动方式