大的ListView包含Android中的图像

问题描述 投票:5回答:7

对于各种Android应用程序,我需要较大的ListView,即具有100-300个条目的视图。

启动应用程序时,必须批量加载所有条目,因为必须进行一些排序和处理,否则应用程序将不知道首先显示哪些项目。

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

但是,当然,这不是一个好习惯,因为您很可能会拥有OutOfMemoryException:对ArrayList中所有图像的引用会阻止垃圾收集器正常工作。

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

所以我应该如何处理图像?何时真正需要加载getView()?会使滚动缓慢。那么调用AsyncTask呢?还是普通的Thread?参数化吗?

我可以保存已经加载到HashMap<String,Bitmap>中的图像,这样就无需再次将它们加载到getView()中。但是,如果这样做,您将再次遇到内存问题:HashMap存储对所有图像的引用,因此最后,您可以再次拥有OutOfMemoryException

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

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

android memory android-listview android-imageview
7个回答
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,从而使它一次只能运行一个任务,因此在任何给定时间都使用更少的内存。只要图像不太大,就可以了。

UPDATE:尽管此解决方案仍然有效,但是开源社区中有很多新增功能可以更干净地解决此问题。像Glide或Picasso这样的库都可以很好地处理列表中的加载项,如果可能,我建议您考虑使用其中一种解决方案。


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最大图像尺寸,这对于标准列表项的高度应该足够了。


1
投票

请勿将所有图像都存储在列表中,因为它太重了。您应该在getView中启动AsyncTask,在InBackground中获取,解码图像,并在PostExecute中在imageView上绘制图像。为了保持列表的性能,您还可以使用getView方法中的convertView参数,但是它与AsyncTask一起变得很复杂,因为可以在AsyncTask完成之前回收您的视图,因此您应该处理此额外的工作...

您可以使用LruCache,但这仅在从Internet下载图像时才有意义。当它们存储在本地时,没有必要使用它。


1
投票

检查此:https://github.com/DHuckaby/Prime

我会在ListView中将其用于图像,然后尝试自行解决。.我实际上将其用于我的应用程序中的所有远程图像..或至少阅读了源代码。.图像管理是一种痛苦。.依靠一个成熟的图书馆,助您一臂之力。


0
投票

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

关于AsyncTask,这在不同的API中是正确的,但是当您对旧API使用execute()并在新API上使用executeOnExecutor时-我认为一切都很好。您应该将URI和ImageView传递给AsyncTask。在这里,您可能会遇到convertView的问题,因为它在新行中与AsyncTask一起工作在其图像上。您可以保存例如ImageView-AsyncTask的哈希图,然后取消不再有效的哈希图。

PS。抱歉,无法创建新评论,但是对于内联答案来说太长了:)


-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()方法中


-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将决定要缓存的图像数量。


© www.soinside.com 2019 - 2024. All rights reserved.