• Android GridView异步加载图片和加载大量图片时出现Out Of Memory问题


    我们在使用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=0;i<1000;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(0,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 = 120;//每个Item的宽度,可以根据实际情况修改
            private int height = 150;//每个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 0;
            }
    
    
            @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+1)+"");
                    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(0, 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[0]);                        
                            bitmap = getBitmapFromUrl(url);
                            MainActivity.gridviewBitmapCaches.put(mList.get(params[0]), 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 != 0 && height !=0){
                                    Matrix matrix = new Matrix();
                                    float scaleWidth = ((float) width / bmpWidth);
                                    float scaleHeight = ((float) height / bmpHeight);
                                    matrix.postScale(scaleWidth, scaleHeight);
                                    bitmap = Bitmap.createBitmap(bmp, 0, 0, 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="4"
                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>
  • 相关阅读:
    触动心灵的话语
    join和os.path.join 的用法
    os.path.join 的用法
    Python中函数的定义必须在调用的前面
    矩阵和数组的区别
    Object detection with deep learning and OpenCV
    YOLO Object Detection with OpenCV
    Python-OpenCV中VideoCapture类的使用
    Object Detection: Face Detection using Haar Cascades
    有关目标检测的文章
  • 原文地址:https://www.cnblogs.com/zhujiabin/p/4208456.html
Copyright © 2020-2023  润新知