5

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

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

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

但当然,这不是一个好的做法,因为您很可能会遇到OutOfMemoryException这样的情况:对所有图像的引用会ArrayList阻止垃圾收集器工作。

因此,显然,最好的解决方案是仅批量加载文本数据,然后根据需要加载图像,对吗?例如,Google Play 应用程序会这样做: 您可以看到图像在您滚动到它们时被加载,即它们可能是在适配器的getView()方法中加载的。但是对于 Google Play,这是一个不同的问题,无论如何,因为图像必须从 Internet 加载,这对我来说不是这种情况。我的问题不是加载图像需要太长时间,而是存储它们需要太多内存。

那么我应该如何处理图像呢?加载getView(),什么时候真正需要它们?会使滚动变慢。所以打电话给AsyncTask然后?还是只是一个正常的Thread?参数化它?

我可以将已经加载到的图像保存HashMap<String,Bitmap>getView(). 但是如果这样做了,你又会遇到内存问题:HashMap存储对所有图像的引用,所以最后你可能会OutOfMemoryException再次遇到。

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

编辑:我现在决定在 getView() 中启动 AsyncTasks,它将图像加载到后台的 ListView 中。但这会导致我的应用程序遇到 RejectedExecutionException。我现在该怎么办?

4

7 回答 7

9

I took the approach of loading the images with an AsyncTask and attaching the task to the view in the adapter's getView function to keep track of which task is loading in which view. I use this in an app of mine and there's no scroll lag and all images are loaded in the proper position with no exceptions being thrown. Also, because the task does no work if it's canceled, you can perform a fling on your list and it should lag up at all.

The task:

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);
    }
}

}

The adapter stores an ArrayList that contains the file paths of all the images that need loaded. The getView function looks like this:

@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;
}

NOTE: Just a caveat here, this might still give you memory problems on versions 1.5 - 2.3 since they use a thread pool for AsyncTask. 3.0+ go back to the serial model by default for executing AsyncTasks which keeps it to one task running at a time, thus using less memory at any given time. So long as your images aren't too big though, you should be fine.

UPDATE: While this solution will still work, there have been great additions to the open source community for solving this problem in a cleaner way. Libraries like Glide or Picasso both handle loading items in a list quite well and I'd recommend you look into one of those solutions if possible.

于 2012-09-21T14:19:59.360 回答
2

1) To solve your memory problem with HashMap<String, Bitmap>: Can you use WeakHashMap so that images are recycled when needed? The same should work for the ArrayList you mentioned in the beginning, if your CustomType has weak references to images.

2) What about NOT loading images while user scrolls through the list, but instead when user stops scrolling, load images at that moment. Yes, the list will not look fancy without images, but it will be very efficient during scroll, and while user scrolls he does not see details anyway. Techinally it should work like this:

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) I have an app that loads about 300 images during app start and keeps them for app lifetime. So that's an issue for me too, but so far I have seen very few OOM reports and I suspect they happened because of a different leak. I try to save some memory by using RGB_565 profile for ListView images (there's no significant difference in quality for this purpose) and I use 96x96 max image size, that should be enough for standard list item height.

于 2012-09-17T23:44:20.717 回答
1

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

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

于 2012-09-13T22:34:21.047 回答
1

check this out: https://github.com/DHuckaby/Prime

I would use this for images in a ListView vs trying to solve it yourself.. I actually use it for all remote images in my apps.. or at least read the source code.. image management is a pain.. lean on a mature library to get you going.

于 2012-09-21T03:56:43.430 回答
0

您提供的链接有助于理解什么是 convertView、asyncTask 等。我认为这样做View v = super.getView(position, convertView, parent);行不通。如果你想回收视图,你应该这样做if(convertView != null){ myView = convertView} else{ inflate(myView)};

About AsyncTask that's right its different in different APIS but when you use execute() for old API and executeOnExecutor on the new one - I think everything is fine. You should pass URI and ImageView to your AsyncTask. Here you could have problem with convertView that it appears in a new row with AsyncTask working on it's image. You can hold for example hashmap of ImageView-AsyncTask and cancel these which are not valid any more.

PS.Sorry for creating a new comment but it was too long for inline answer :)

于 2012-09-14T06:51:46.723 回答
-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
    }

Call new CallService().execute(); in onCreate() method

于 2012-09-20T10:16:41.517 回答
-3

A small piece of advice would be to disable loading of images when a fling occurs.


dirty HACK to make everything faster:

In your Adapter's getItemViewType(int position), return the 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 will decide the amount of images to cache.


于 2012-09-17T00:21:58.380 回答