我们在使用GridView或者ListView时,通常会遇到两个棘手的问题:
1.每个Item获取的数据所用的时间太长会导致程序长时间黑屏,更甚会导致程序ANR,也就是Application No Responding
2.当每个Item中有图片存在时,少量图片不会出现问题,当有大量图片存在时,就会出现Out Of Memory的错误,导致这个错误的原因是Android系统中读取位图Bitmap时.默认分给虚拟机中图片的堆栈大小只有8M。

一、解决第一个问题,这里我们采用异步加载图片的方法,也就是先让每个Item加载一张默认的drawable,在后台处理获取图片的任务,等后台处理完以后,提示前台更新图片。这 里我们会遇到一个问题,就是在gridview或则listview等容器中,当用户随意滑动的时候,将会产生N个线程去加载图片,这个是我们不想看到 的。我们希望的是一个图片只有一个线程去下载就行了。为了解决这个问题,我们应该做的是让这个Item中imageview记住它是否正在加载(或者说是 下载)图片资源。如果正在加载,或者加载完成,那么我就不应该再建立一个任务去加载图片了。
   二、第二个问题我们采用图片缓存的方式,将已经加载完成的图片保存到缓存中,然后通过监控gridview的滑动事件去释放图片,即调用bitmap.recycle()方法,从而保证不会出现Out Of Memory错误 。

MainActivity类:

public class MainActivity extends Activity implements OnScrollListener{
private static final String TAG = "MainActivity"; private TextView textview_show_prompt = null;
private GridView gridview_test = null; private List<String> mList = null;
//图片缓存用来保存GridView中每个Item的图片,以便释放
public static Map<String,Bitmap> gridviewBitmapCaches = new HashMap<String,Bitmap>(); private MyGridViewAdapter adapter = null; @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViews();
initData();
setAdapter();
} private void findViews(){
textview_show_prompt = (TextView)findViewById(R.id.textview_show_prompt);
gridview_test = (GridView)findViewById(R.id.gridview_test);
} private void initData(){
mList = new ArrayList<String>();
String url = "/mnt/sdcard/testGridView/jay.png";//为sd卡下面创建testGridView文件夹,将图片放入其中
//为了方便测试,我们这里只存入一张图片,将其路径后面添加数字进行区分,到后面要获取图片时候再处理该路径。
for(int i=;i<;i++){
mList.add(url+"/"+i);//区分路径
}
} private void setAdapter(){
adapter = new MyGridViewAdapter(this, mList);
gridview_test.setAdapter(adapter);
gridview_test.setOnScrollListener(this);
}  //释放图片的函数
private void recycleBitmapCaches(int fromPosition,int toPosition){
Bitmap delBitmap = null;
for(int del=fromPosition;del<toPosition;del++){
delBitmap = gridviewBitmapCaches.get(mList.get(del));
if(delBitmap != null){
//如果非空则表示有缓存的bitmap,需要清理
Log.d(TAG, "release position:"+ del);
//从缓存中移除该del->bitmap的映射
gridviewBitmapCaches.remove(mList.get(del));
delBitmap.recycle();
delBitmap = null;
}
}
}   
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
// TODO Auto-generated method stub
//注释:firstVisibleItem为第一个可见的Item的position,从0开始,随着拖动会改变
//visibleItemCount为当前页面总共可见的Item的项数
//totalItemCount为当前总共已经出现的Item的项数
recycleBitmapCaches(,firstVisibleItem);
recycleBitmapCaches(firstVisibleItem+visibleItemCount, totalItemCount); } @Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
} }

MyGridViewAdapter类:

public class MyGridViewAdapter extends BaseAdapter{

        private Context mContext = null;
private LayoutInflater mLayoutInflater = null;
private List<String> mList = null; private int width = ;//每个Item的宽度,可以根据实际情况修改
private int height = ;//每个Item的高度,可以根据实际情况修改 public static class MyGridViewHolder{
public ImageView imageview_thumbnail;
public TextView textview_test;
} public MyGridViewAdapter(Context context,List<String> list) {
// TODO Auto-generated constructor stub
this.mContext = context;
this.mList = list;
mLayoutInflater = LayoutInflater.from(context);
} @Override
public int getCount() {
// TODO Auto-generated method stub
return mList.size();
} @Override
public Object getItem(int arg0) {
// TODO Auto-generated method stub
return null;
} @Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return ;
} @Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
MyGridViewHolder viewHolder = null;
if(convertView == null){
viewHolder = new MyGridViewHolder();
convertView = mLayoutInflater.inflate(R.layout.layout_my_gridview_item, null);
viewHolder.imageview_thumbnail = (ImageView)convertView.findViewById(R.id.imageview_thumbnail);
viewHolder.textview_test = (TextView)convertView.findViewById(R.id.textview_test);
convertView.setTag(viewHolder);
}else{
viewHolder = (MyGridViewHolder)convertView.getTag();
} String url = mList.get(position);
//首先我们先通过cancelPotentialLoad方法去判断imageview是否有线程正在为它加载图片资源,
//如果有现在正在加载,那么判断加载的这个图片资源(url)是否和现在的图片资源一样,不一样则取消之前的线程(之前的下载线程作废)。
//见下面cancelPotentialLoad方法代码
if (cancelPotentialLoad(url, viewHolder.imageview_thumbnail)) {
AsyncLoadImageTask task = new AsyncLoadImageTask(viewHolder.imageview_thumbnail);
LoadedDrawable loadedDrawable = new LoadedDrawable(task);
viewHolder.imageview_thumbnail.setImageDrawable(loadedDrawable);
task.execute(position);
}
viewHolder.textview_test.setText((position+)+"");
return convertView;
} private Bitmap getBitmapFromUrl(String url){
Bitmap bitmap = null;
bitmap = MainActivity.gridviewBitmapCaches.get(url);
if(bitmap != null){
System.out.println(url);
return bitmap;
}
url = url.substring(, url.lastIndexOf("/"));//处理之前的路径区分,否则路径不对,获取不到图片 //bitmap = BitmapFactory.decodeFile(url);
//这里我们不用BitmapFactory.decodeFile(url)这个方法
//用decodeFileDescriptor()方法来生成bitmap会节省内存
//查看源码对比一下我们会发现decodeFile()方法最终是以流的方式生成bitmap
//而decodeFileDescriptor()方法是通过Native方法decodeFileDescriptor生成bitmap的 try {
FileInputStream is = new FileInputStream(url);
bitmap = BitmapFactory.decodeFileDescriptor(is.getFD());
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} bitmap = BitmapUtilities.getBitmapThumbnail(bitmap,width,height);
return bitmap;
} //加载图片的异步任务
private class AsyncLoadImageTask extends AsyncTask<Integer, Void, Bitmap>{
private String url = null;
private final WeakReference<ImageView> imageViewReference; public AsyncLoadImageTask(ImageView imageview) {
super();
// TODO Auto-generated constructor stub
imageViewReference = new WeakReference<ImageView>(imageview);
} @Override
protected Bitmap doInBackground(Integer... params) {
// TODO Auto-generated method stub
Bitmap bitmap = null;
this.url = mList.get(params[]);
bitmap = getBitmapFromUrl(url);
MainActivity.gridviewBitmapCaches.put(mList.get(params[]), bitmap);
return bitmap;
} @Override
protected void onPostExecute(Bitmap resultBitmap) {
// TODO Auto-generated method stub
if(isCancelled()){
resultBitmap = null;
}
if(imageViewReference != null){
ImageView imageview = imageViewReference.get();
AsyncLoadImageTask loadImageTask = getAsyncLoadImageTask(imageview);
if (this == loadImageTask) {
imageview.setImageBitmap(resultBitmap);
imageview.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
}
}
super.onPostExecute(resultBitmap);
}
} private boolean cancelPotentialLoad(String url,ImageView imageview){
AsyncLoadImageTask loadImageTask = getAsyncLoadImageTask(imageview); if (loadImageTask != null) {
String bitmapUrl = loadImageTask.url;
if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) {
loadImageTask.cancel(true);
} else {
// 相同的url已经在加载中.
return false;
}
}
return true; } //当 loadImageTask.cancel(true)被执行的时候,则AsyncLoadImageTask 就会被取消,
//当AsyncLoadImageTask 任务执行到onPostExecute的时候,如果这个任务加载到了图片,
//它也会把这个bitmap设为null了。
//getAsyncLoadImageTask代码如下:
private AsyncLoadImageTask getAsyncLoadImageTask(ImageView imageview){
if (imageview != null) {
Drawable drawable = imageview.getDrawable();
if (drawable instanceof LoadedDrawable) {
LoadedDrawable loadedDrawable = (LoadedDrawable)drawable;
return loadedDrawable.getLoadImageTask();
}
}
return null;
} //该类功能为:记录imageview加载任务并且为imageview设置默认的drawable
public static class LoadedDrawable extends ColorDrawable{
private final WeakReference<AsyncLoadImageTask> loadImageTaskReference; public LoadedDrawable(AsyncLoadImageTask loadImageTask) {
super(Color.TRANSPARENT);
loadImageTaskReference =
new WeakReference<AsyncLoadImageTask>(loadImageTask);
} public AsyncLoadImageTask getLoadImageTask() {
return loadImageTaskReference.get();
} }
}

BitmapUtilities类处理图片,这里我们在imageview显示图片之前就将图片缩小,更好的节省内存:

public class BitmapUtilities {

        public BitmapUtilities() {
// TODO Auto-generated constructor stub
} public static Bitmap getBitmapThumbnail(String path,int width,int height){
Bitmap bitmap = null;
//这里可以按比例缩小图片:
/*BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = 4;//宽和高都是原来的1/4
bitmap = BitmapFactory.decodeFile(path, opts); */ /*进一步的,
如何设置恰当的inSampleSize是解决该问题的关键之一。BitmapFactory.Options提供了另一个成员inJustDecodeBounds。
设置inJustDecodeBounds为true后,decodeFile并不分配空间,但可计算出原始图片的长度和宽度,即opts.width和opts.height。
有了这两个参数,再通过一定的算法,即可得到一个恰当的inSampleSize。*/
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, opts);
opts.inSampleSize = Math.max((int)(opts.outHeight / (float) height), (int)(opts.outWidth / (float) width));
opts.inJustDecodeBounds = false;
bitmap = BitmapFactory.decodeFile(path, opts);
return bitmap;
} public static Bitmap getBitmapThumbnail(Bitmap bmp,int width,int height){
Bitmap bitmap = null;
if(bmp != null ){
int bmpWidth = bmp.getWidth();
int bmpHeight = bmp.getHeight();
if(width != && height !=){
Matrix matrix = new Matrix();
float scaleWidth = ((float) width / bmpWidth);
float scaleHeight = ((float) height / bmpHeight);
matrix.postScale(scaleWidth, scaleHeight);
bitmap = Bitmap.createBitmap(bmp, , , bmpWidth, bmpHeight, matrix, true);
}else{
bitmap = bmp;
}
}
return bitmap;
} }

activity_main.xml:

<RelativeLayout 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" > <TextView
android:id="@+id/textview_show_prompt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/textview_show_prompt"
android:textSize="24dp"
tools:context=".MainActivity" /> <GridView
android:layout_below="@id/textview_show_prompt"
android:id="@+id/gridview_test"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:horizontalSpacing="10dp"
android:verticalSpacing="20dp"
android:numColumns=""
android:gravity="center"
android:padding="10dp"
android:background="#FFFFFFFF">
</GridView>
</RelativeLayout>

layout_my_gridview_item.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#88000000"
android:gravity="center"
>
<ImageView
android:id="@+id/imageview_thumbnail"
android:layout_width="120dp"
android:layout_height="150dp"
android:padding="5dp"
android:scaleType="centerInside"
/>
<TextView
android:id="@+id/textview_test"
android:layout_width="120dp"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="24dp"
android:textColor="#FFFF0000"
/>
</RelativeLayout>

Android GridView异步加载图片和加载大量图片时出现Out Of Memory问题的更多相关文章

  1. Android 之异步加载LoaderManager

    LoaderManager: Loader出现的背景: Activity是我们的前端页面展现,数据库是我们的数据持久化地址,那么正常的逻辑就是在展示页面的渲染页面的阶段进行数据库查询.拿到数据以后才展 ...

  2. [Android] Android 用于异步加载 ContentProvider 中的内容的机制 -- Loader 机制 (LoaderManager + CursorLoader + LoaderManager.LoaderCallbacks)

    Android 用于异步加载 ContentProvider 中的内容的机制 -- Loader 机制 (LoaderManager + CursorLoader + LoaderManager.Lo ...

  3. android 网络异步加载数据进度条

    ProgressDialog progressDialog = null; public static final int MESSAGETYPE = 0; private void execute( ...

  4. ListView与GridView异步加载图片

    原理很简单,主要是用到了回调方法,下面是异步加载图片的类 <span style="font-size:16px;">package com.xxx.xxx; impo ...

  5. android ListView异步加载图片(双缓存)

    首先声明,参考博客地址:http://www.iteye.com/topic/685986 对于ListView,相信很多人都很熟悉,因为确实太常见了,所以,做的用户体验更好,就成了我们的追求... ...

  6. 又优化了一下 Android ListView 异步加载图片

    写这篇文章并不是教大家怎么样用listview异步加载图片,因为这样的文章在网上已经有很多了,比如这位仁兄写的就很好: http://www.iteye.com/topic/685986 我也是因为看 ...

  7. android实现异步加载图片类

    其中牵涉到的关键知识点 1,回调机制,不过回调接口的实现方式有多种多样,可以是一个类继承该接口,也可以是作为一个方法参数: 可以参照自己的这篇博客: http://www.cnblogs.com/bo ...

  8. android listview 异步加载图片并防止错位

    网上找了一张图, listview 异步加载图片之所以错位的根本原因是重用了 convertView 且有异步操作. 如果不重用 convertView 不会出现错位现象, 重用 convertVie ...

  9. Android 实现异步加载图片

    麦洛开通博客以来,有一段时间没有更新博文了.主要是麦洛这段时间因项目开发实在太忙了.今天周六还在公司加班,苦逼程序猿都是这样生活的. 今天在做项目的时候,有一个实现异步加载图片的功能,虽然比较简单但还 ...

随机推荐

  1. 一个好用的ssh终端:MobaXterm

    WSL由于没有图形界面,所有操作都是在命令行里执行,平时用来编译和跑CFD代码其实还是挺方便.不过有时候要查看WSL里的文件就比较麻烦,这时可以用SFTP这类工具,连接过去后直接操作文件.试过几个这类 ...

  2. stark - 5 ⇲ 其他常用功能

    Ⅰ 排序 当数据量增多,对于数据 我们应该能够指定如何排序的.且此功能应该是可以给用户自定义进行配置的. 这是StarkHandler类的方法1 order_list = [] def get_ord ...

  3. Dijkstra+优先队列 堆优化

    关于堆优化 传统\(Dijkstra\),在选取中转站时,是遍历取当前最小距离节点,但是我们其实可以利用小根堆(STL的priority_queue)优化这个过程,从而大大降低复杂(\(O(V2+E) ...

  4. [Xamarin] 透過StartActivityForResult傳值回來(转贴)

    上一篇文章(開啟另外一個Activity 並且帶資料),提到了開啟一個新的Activity ,我們將值透過intent 帶到下個Activity 但是,如果我們開啟的Actrivity其實是有一個任務 ...

  5. MYSQL 本地无ROOT权限 忘记密码

    打开CMD窗口 net stop mysql //停止MYSQL mysqld  -nt  --skip-grant-tables //跳过密码检测. mysqld.exe在Bin目录下 然后另外新打 ...

  6. ActiveMQ:Exception occurred while processing this request, check the log for more information!

    出现上面错误的原因有以下两种 1 jdk的版本和activemq的版本不符 安装完ActiveMQ之后,通过http://IP:8161登陆到控制台. 通过测试代码给服务端发送队列消息,在控制台点击q ...

  7. python2和python3中列表推导式的变量泄露问题

    Python 2.x 中,在列表推导中 for 关键词之后的赋值操作可能会影响列表推导上下文中的同名变量.像下面这个 Python 2.7 控制台对话: Python 2.7.15 (default, ...

  8. php中continue关键字

    跳出本次循环进行下一次. 在while和do while中也可以用 $num = 0; while($num < 10) { $num ++; if($num === 5) { echo 'eq ...

  9. 将字符串 “ hello word,你 好 世 界 ! ” 两端空格去掉并且将其中的其他所有空格替换成一个空格 输出结果为“hello word,你 好 世界”

    string str = " hello word,你 好 世 界 ! "; string msg = str.Trim(); //去掉首尾空格 //使用split分割字符串,st ...

  10. docker部署ELK日志处理

    docker环境下部署的ELK系统,日志处理,保留最近五个小时的日志 dockerlog.sh #!/bin/bash logs=`find /var/lib/docker/containers/ - ...