包含Android中图像的大型ListView

时间:2012-09-13 20:54:54

标签: android memory android-listview android-imageview

对于各种Android应用程序,我需要大ListView个,即包含100-300个条目的此类视图。

启动应用程序时必须批量加载所有条目,因为某些排序和处理是必要的,应用程序无法知道首先显示哪些项目,否则。

到目前为止,我一直在批量加载所有项目的图像,然后将这些图像与每个条目的其余数据一起保存在ArrayList<CustomType>中。

但当然,这不是一个好习惯,因为你很可能会有OutOfMemoryExceptionArrayList中对所有图像的引用会阻止垃圾收集器工作。< / p>

因此,最好的解决方案显然只是批量加载文本数据,然后根据需要加载图像,对吧? Google Play应用程序执行此操作,例如:您可以看到图像在滚动到它们时加载,即它们可能已加载到适配器的getView()方法中。但是对于谷歌播放,这是一个不同的问题,因为图像必须从互联网加载,这对我来说并非如此。我的问题不是加载图像需要太长时间,但存储它们需要太多内存。

那么我该如何处理这些图片呢?在真正需要时加载getView()?会使滚动迟缓。那么调用AsyncTask呢?或者只是正常Thread?参数化吗?

我可以保存已加载到HashMap<String,Bitmap>的图片,这样就不需要在getView()中再次加载它们。但是如果这样做,你又会出现内存问题:HashMap存储对所有图像的引用,所以最后你可以再次拥有OutOfMemoryException

我知道这里已经有很多问题讨论过图像的“延迟加载”。但它们主要涵盖了加载缓慢的问题,而不是太多的内存消耗。

编辑:我现在决定start AsyncTasks in getView() which load the image into the ListView in the background。但这会导致我的应用程序遇到RejectedExecutionException。我现在该怎么办?

7 个答案:

答案 0 :(得分:9)

我采用了使用AsyncTask加载图像的方法,并将任务附加到适配器的getView函数中的视图,以跟踪在哪个视图中加载哪个任务。我在我的应用程序中使用它,没有滚动滞后,所有图像都加载到正确的位置,没有异常被抛出。此外,因为如果任务被取消,任务就无效,你可以在列表上执行任务,它应该会落后。

任务:

public class DecodeTask extends AsyncTask<String, Void, Bitmap> {

private static int MaxTextureSize = 2048; /* True for most devices. */

public ImageView v;

public DecodeTask(ImageView iv) {
    v = iv;
}

protected Bitmap doInBackground(String... params) {
    BitmapFactory.Options opt = new BitmapFactory.Options();
    opt.inPurgeable = true;
    opt.inPreferQualityOverSpeed = false;
    opt.inSampleSize = 0;

    Bitmap bitmap = null;
    if(isCancelled()) {
        return bitmap;
    }

    opt.inJustDecodeBounds = true;
    do {
        opt.inSampleSize++;
        BitmapFactory.decodeFile(params[0], opt);
    } while(opt.outHeight > MaxTextureSize || opt.outWidth > MaxTextureSize)
    opt.inJustDecodeBounds = false;

    bitmap = BitmapFactory.decodeFile(params[0], opt);
    return bitmap;
}

@Override
protected void onPostExecute(Bitmap result) {
    if(v != null) {
        v.setImageBitmap(result);
    }
}

}

适配器存储一个ArrayList,其中包含所有需要加载的图像的文件路径。 getView函数如下所示:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ImageView iv = null;
    if(convertView == null) {
        convertView = getLayoutInflater().inflate(R.id.your_view, null); /* Inflate your view here */
        iv = convertView.findViewById(R.id.your_image_view);
    } else {
        iv = convertView.findViewById(R.id.your_image_view);
        DecodeTask task = (DecodeTask)iv.getTag(R.id.your_image_view);
        if(task != null) {
            task.cancel(true);
        }
    }
    iv.setImageBitmap(null);
    DecodeTask task = new DecodeTask(iv);
    task.execute(getItem(position) /* File path to image */);
    iv.setTag(R.id.your_image_view, task);

    return convertView;
}

注意:这里只是一个警告,这可能仍会在版本1.5 - 2.3上给你内存问题,因为它们使用AsyncTask的线程池。 3.0+默认情况下返回串行模型执行AsyncTasks,使其一次运行一个任务,因此在任何给定时间使用更少的内存。只要你的图像不是太大,你应该没问题。

答案 1 :(得分:2)

1)使用HashMap<String, Bitmap>解决您的内存问题:您是否可以使用WeakHashMap以便在需要时回收图片?如果您的ArrayList对图片的引用较弱,则同样适用于您在开头提到的CustomType

2)如果用户在列表中滚动时不加载图像,而是在用户停止滚动时加载图像,此时加载图像。是的,如果没有图像,列表看起来不会很花哨,但在滚动期间它会非常高效,而在用户滚动时,他无论如何都看不到细节。从技术上来说它应该是这样的:

listView.setOnScrollListener(new OnScrollListener() {
  @Override
  public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
      int totalItemCount) {
    // Don't care.
  }

  @Override
  public void onScrollStateChanged(final AbsListView view, int scrollState) {
    // Load images here. ListView can tell you what rows are visible, you load images for these rows and update corresponding View-s.
  }
})

3)我有一个应用程序,在应用程序启动期间加载大约300张图像,并保持应用程序的生命周期。所以这对我来说也是一个问题,但到目前为止我看到很少有OOM报告,我怀疑它们是因为泄漏不同而发生的。我尝试使用ListView图像的RGB_565配置文件来保存一些内存(为此目的,质量没有显着差异),我使用96x96最大图像大小,这对于标准列表项高度应该足够了。

答案 2 :(得分:1)

不要将所有图像存储在列表中,因为它太重了。您应该在getView中启动AsyncTask,在InBackground中获取,解码您的图像,并在PostExecute中的imageView上绘制图像。为了保持列表的性能,您还可以使用getView方法中的convertView参数,但是AsyncTask开始变得复杂,因为您的视图可以在AsyncTask完成之前被回收,并且您应该处理这个额外的...

您可以使用LruCache,但这仅在从互联网下载图像时才有意义。当它们被存储在localy时,没有必要使用它。

答案 3 :(得分:1)

检查一下:https://github.com/DHuckaby/Prime

我会将此用于ListView中的图像与尝试自己解决它...我实际上将它用于我的应用程序中的所有远程图像..或至少阅读源代码..图像管理是一种痛苦..精益在一个成熟的图书馆,让你去。

答案 4 :(得分:0)

你提供的link有助于理解什么是convertView,asyncTask等。我不认为做View v = super.getView(position, convertView, parent);会有效。如果您想要回收视图,请执行if(convertView != null){ myView = convertView} else{ inflate(myView)};

关于AsyncTask,它在不同的APIS中是不同的,但是当你对旧API使用execute()和在新的时候使用executeOnExecutor时 - 我认为一切都很好。您应该将URI和ImageView传递给AsyncTask。在这里你可能遇到convertView的问题,它出现在一个新的行中,AsyncTask处理它的图像。你可以保存ImageView-AsyncTask的示例hashmap,并取消那些无效的。

PS.Sorry用于创建新评论,但内联答案太长了:)

答案 5 :(得分:-1)

    private class CallService extends AsyncTask<String, Integer, String>
         {   
                protected String doInBackground(String... u)
                {
                    fetchReasons();
                    return null;
                }
                protected void onPreExecute() 
                {
                    //Define the loader here.

                }
                public void onProgressUpdate(Integer... args)
                {           
                }
                protected void onPostExecute(String result) 
                {               
                    //remove loader
                    //Add data to your view.
                }
         }

public void fetchReasons()
    {
         //Call Your Web Service and save the records in the arrayList
    }

调用新的CallService()。execute();在onCreate()方法

答案 6 :(得分:-3)

一小段建议是在发生投掷时禁用图像加载。


脏HACK让一切更快:

在适配器的getItemViewType(int position)中,返回位置:

@Override
public long getItemViewType(int position) {
     return position;
}
@Override
public long getViewTypeCount(int position) {
     return getCount();
}
@Override
public View getView(int position, View convertView, ViewGroup arg2) {
    if (convertView == null) {
        //inflate your convertView and set everything
    }
    //do not do anything just return the convertView
    return convertView;
}

ListView将决定要缓存的图片数量。