在另一个线程上在ListView中加载图像的安全,标准方法?(Safe, standard way to load images in ListView on a different thread?)

在提出这个问题之前,我已经搜索并阅读了这些问题: 在ListView Android中延迟加载图像 - 延迟加载图像到ListView中的问题

我的问题是我有一个ListView ,其中:

每行包含一个ImageView ,其内容将从Internet加载 每行的视图都在ApiDemo的List14中被回收

我最终想要的是:

只有当用户滚动到图像时,才会懒洋洋地加载图像 在不同的线程上加载图像以保持响应性

我目前的做法:

在适配器的getView()方法中,除了设置其他子视图外,我还启动了一个从Internet加载Bitmap的新线程。 当该加载线程完成时,它返回要在ImageView上设置的Bitmap (我使用AsyncTask或Handler执行此操作)。 因为我循环使用ImageView ,所以我可能首先想要使用Bitmap#1设置视图,然后在用户向下滚动时将其设置为Bitmap#2 。 Bitmap#1可能需要比Bitmap#2加载更长的时间,因此最终可能会覆盖视图上的Bitmap#2 。 我通过维护一个WeakHashMap来解决这个问题,该WeakHashMap会记住我想为该视图设置的最后一个Bitmap 。

以下是我当前方法的伪代码。 我已经省略了缓存之类的其他细节,只是为了保持清晰。

public class ImageLoader { // keeps track of the last Bitmap we want to set for this ImageView private static final WeakHashMap<ImageView, AsyncTask> assignments = new WeakHashMap<ImageView, AsyncTask>(); /** Asynchronously sets an ImageView to some Bitmap loaded from the internet */ public static void setImageAsync(final ImageView imageView, final String imageUrl) { // cancel whatever previous task AsyncTask oldTask = assignments.get(imageView); if (oldTask != null) { oldTask.cancel(true); } // prepare to launch a new task to load this new image AsyncTask<String, Integer, Bitmap> newTask = new AsyncTask<String, Integer, Bitmap>() { protected void onPreExecute() { // set ImageView to some "loading..." image } protected Bitmap doInBackground(String... urls) { return loadFromInternet(imageUrl); } protected void onPostExecute(Bitmap bitmap) { // set Bitmap if successfully loaded, or an "error" image if (bitmap != null) { imageView.setImageBitmap(bitmap); } else { imageView.setImageResource(R.drawable.error); } } }; newTask.execute(); // mark this as the latest Bitmap we want to set for this ImageView assignments.put(imageView, newTask); } /** returns (Bitmap on success | null on error) */ private Bitmap loadFromInternet(String imageUrl) {} }

问题我还有:如果在一些图像仍在加载时Activity被破坏怎么办?

当Activity已经被销毁时,加载线程何时回调ImageView会有任何风险吗? 而且, AsyncTask有一些全局线程池,所以如果长时间的任务在不再需要时不被取消,我可能会浪费时间加载用户 别看 我目前在全球范围内保存这个东西的设计太难看了,最终可能会导致一些超出我理解范围的漏洞。 我没有让ImageLoader成为像这样的单例,而是考虑为不同的Activity创建单独的ImageLoader对象,然后当一个Activity被销毁时,它的所有AsyncTask都将被取消。 这太尴尬了吗?

无论如何,我想知道在Android中是否有安全和标准的方法。 另外,我不知道iPhone,但是那里有类似的问题,他们是否有标准的方法来完成这种任务?

非常感谢。

Before making this question, I have searched and read these ones: Lazy load of images in ListView Android - Issue with lazy loading images into a ListView

My problem is I have a ListView, where:

Each row contains an ImageView, whose content is to be loaded from the internet Each row's view is recycled as in ApiDemo's List14

What I want ultimately:

Load images lazily, only when the user scrolls to them Load images on different thread(s) to maintain responsiveness

My current approach:

In the adapter's getView() method, apart from setting up other child views, I launch a new thread that loads the Bitmap from the internet. When that loading thread finishes, it returns the Bitmap to be set on the ImageView (I do this using AsyncTask or Handler). Because I recycle ImageViews, it may be the case that I first want to set a view with Bitmap#1, then later want to set it to Bitmap#2 when the user scrolls down. Bitmap#1 may happen to take longer than Bitmap#2 to load, so it may end up overwriting Bitmap#2 on the view. I solve this by maintaining a WeakHashMap that remembers the last Bitmap I want to set for that view.

Below is somewhat a pseudocode for my current approach. I've ommitted other details like caching, just to keep the thing clear.

public class ImageLoader { // keeps track of the last Bitmap we want to set for this ImageView private static final WeakHashMap<ImageView, AsyncTask> assignments = new WeakHashMap<ImageView, AsyncTask>(); /** Asynchronously sets an ImageView to some Bitmap loaded from the internet */ public static void setImageAsync(final ImageView imageView, final String imageUrl) { // cancel whatever previous task AsyncTask oldTask = assignments.get(imageView); if (oldTask != null) { oldTask.cancel(true); } // prepare to launch a new task to load this new image AsyncTask<String, Integer, Bitmap> newTask = new AsyncTask<String, Integer, Bitmap>() { protected void onPreExecute() { // set ImageView to some "loading..." image } protected Bitmap doInBackground(String... urls) { return loadFromInternet(imageUrl); } protected void onPostExecute(Bitmap bitmap) { // set Bitmap if successfully loaded, or an "error" image if (bitmap != null) { imageView.setImageBitmap(bitmap); } else { imageView.setImageResource(R.drawable.error); } } }; newTask.execute(); // mark this as the latest Bitmap we want to set for this ImageView assignments.put(imageView, newTask); } /** returns (Bitmap on success | null on error) */ private Bitmap loadFromInternet(String imageUrl) {} }

Problem I still have: what if the Activity gets destroyed while some images are still loading?

Is there any risk when the loading thread calls back to the ImageView later, when the Activity is already destroyed? Moreover, AsyncTask has some global thread-pool underneath, so if lengthy tasks are not canceled when they're not needed anymore, I may end up wasting time loading things users don't see. My current design of keeping this thing globally is too ugly, and may eventually cause some leaks that are beyond my understanding. Instead of making ImageLoader a singleton like this, I'm thinking of actually creating separate ImageLoader objects for different Activities, then when an Activity gets destroyed, all its AsyncTask will be canceled. Is this too awkward?

Anyway, I wonder if there is a safe and standard way of doing this in Android. In addition, I don't know iPhone but is there a similar problem there and do they have a standard way to do this kind of task?

Many thanks.

最满意答案

我通过维护一个WeakHashMap来解决这个问题,该WeakHashMap会记住我想为该视图设置的最后一个Bitmap。

我通过setTag()将所需图像的URL附加到ImageView上。 然后,当我下载图像时,我仔细检查ImageView URL - 如果它与我刚下载的不同,我不会更新ImageView ,因为它已被回收。 我只是缓存它。

当Activity已经被销毁时,加载线程何时回调ImageView会有任何风险吗?

除了浪费的CPU时间和带宽(以及电池)之外,我不知道有任何风险。

我没有让ImageLoader成为像这样的单例,而是考虑为不同的Activity创建单独的ImageLoader对象,然后当一个Activity被销毁时,它的所有AsyncTask都将被取消。 这太尴尬了吗?

如果已经运行,取消AsyncTask并不是非常容易。 我只是让它运行完成。

理想情况下,避免单身人士。 使用Service ,或通过onRetainNonConfigurationInstance()将ImageLoader传递给活动的下一个实例(例如, onRetainNonConfigurationInstance() isFinishing()为false ,因此这是一个旋转)。

I solve this by maintaining a WeakHashMap that remembers the last Bitmap I want to set for that view.

I took the approach attaching the the URL of the desired image onto the ImageView via setTag(). Then, when I have the image downloaded, I double-check the ImageView URL -- if it is different than the one I just downloaded, I don't update the ImageView, because it got recycled. I just cache it.

Is there any risk when the loading thread calls back to the ImageView later, when the Activity is already destroyed?

I am not aware of any risk, other than a bit of wasted CPU time and bandwidth (and, hence, battery).

Instead of making ImageLoader a singleton like this, I'm thinking of actually creating separate ImageLoader objects for different Activities, then when an Activity gets destroyed, all its AsyncTask will be canceled. Is this too awkward?

Canceling an AsyncTask is not terribly easy, if it is already running. I'd just let it run to completion.

Ideally, avoid singletons. Either use a Service, or pass your ImageLoader to the next instance of your activity via onRetainNonConfigurationInstance() (e.g., isFinishing() is false in onDestroy(), so this is a rotation).

在另一个线程上在ListView中加载图像的安全,标准方法?(Safe, standard way to load images in ListView on a different thread?)

在提出这个问题之前,我已经搜索并阅读了这些问题: 在ListView Android中延迟加载图像 - 延迟加载图像到ListView中的问题

我的问题是我有一个ListView ,其中:

每行包含一个ImageView ,其内容将从Internet加载 每行的视图都在ApiDemo的List14中被回收

我最终想要的是:

只有当用户滚动到图像时,才会懒洋洋地加载图像 在不同的线程上加载图像以保持响应性

我目前的做法:

在适配器的getView()方法中,除了设置其他子视图外,我还启动了一个从Internet加载Bitmap的新线程。 当该加载线程完成时,它返回要在ImageView上设置的Bitmap (我使用AsyncTask或Handler执行此操作)。 因为我循环使用ImageView ,所以我可能首先想要使用Bitmap#1设置视图,然后在用户向下滚动时将其设置为Bitmap#2 。 Bitmap#1可能需要比Bitmap#2加载更长的时间,因此最终可能会覆盖视图上的Bitmap#2 。 我通过维护一个WeakHashMap来解决这个问题,该WeakHashMap会记住我想为该视图设置的最后一个Bitmap 。

以下是我当前方法的伪代码。 我已经省略了缓存之类的其他细节,只是为了保持清晰。

public class ImageLoader { // keeps track of the last Bitmap we want to set for this ImageView private static final WeakHashMap<ImageView, AsyncTask> assignments = new WeakHashMap<ImageView, AsyncTask>(); /** Asynchronously sets an ImageView to some Bitmap loaded from the internet */ public static void setImageAsync(final ImageView imageView, final String imageUrl) { // cancel whatever previous task AsyncTask oldTask = assignments.get(imageView); if (oldTask != null) { oldTask.cancel(true); } // prepare to launch a new task to load this new image AsyncTask<String, Integer, Bitmap> newTask = new AsyncTask<String, Integer, Bitmap>() { protected void onPreExecute() { // set ImageView to some "loading..." image } protected Bitmap doInBackground(String... urls) { return loadFromInternet(imageUrl); } protected void onPostExecute(Bitmap bitmap) { // set Bitmap if successfully loaded, or an "error" image if (bitmap != null) { imageView.setImageBitmap(bitmap); } else { imageView.setImageResource(R.drawable.error); } } }; newTask.execute(); // mark this as the latest Bitmap we want to set for this ImageView assignments.put(imageView, newTask); } /** returns (Bitmap on success | null on error) */ private Bitmap loadFromInternet(String imageUrl) {} }

问题我还有:如果在一些图像仍在加载时Activity被破坏怎么办?

当Activity已经被销毁时,加载线程何时回调ImageView会有任何风险吗? 而且, AsyncTask有一些全局线程池,所以如果长时间的任务在不再需要时不被取消,我可能会浪费时间加载用户 别看 我目前在全球范围内保存这个东西的设计太难看了,最终可能会导致一些超出我理解范围的漏洞。 我没有让ImageLoader成为像这样的单例,而是考虑为不同的Activity创建单独的ImageLoader对象,然后当一个Activity被销毁时,它的所有AsyncTask都将被取消。 这太尴尬了吗?

无论如何,我想知道在Android中是否有安全和标准的方法。 另外,我不知道iPhone,但是那里有类似的问题,他们是否有标准的方法来完成这种任务?

非常感谢。

Before making this question, I have searched and read these ones: Lazy load of images in ListView Android - Issue with lazy loading images into a ListView

My problem is I have a ListView, where:

Each row contains an ImageView, whose content is to be loaded from the internet Each row's view is recycled as in ApiDemo's List14

What I want ultimately:

Load images lazily, only when the user scrolls to them Load images on different thread(s) to maintain responsiveness

My current approach:

In the adapter's getView() method, apart from setting up other child views, I launch a new thread that loads the Bitmap from the internet. When that loading thread finishes, it returns the Bitmap to be set on the ImageView (I do this using AsyncTask or Handler). Because I recycle ImageViews, it may be the case that I first want to set a view with Bitmap#1, then later want to set it to Bitmap#2 when the user scrolls down. Bitmap#1 may happen to take longer than Bitmap#2 to load, so it may end up overwriting Bitmap#2 on the view. I solve this by maintaining a WeakHashMap that remembers the last Bitmap I want to set for that view.

Below is somewhat a pseudocode for my current approach. I've ommitted other details like caching, just to keep the thing clear.

public class ImageLoader { // keeps track of the last Bitmap we want to set for this ImageView private static final WeakHashMap<ImageView, AsyncTask> assignments = new WeakHashMap<ImageView, AsyncTask>(); /** Asynchronously sets an ImageView to some Bitmap loaded from the internet */ public static void setImageAsync(final ImageView imageView, final String imageUrl) { // cancel whatever previous task AsyncTask oldTask = assignments.get(imageView); if (oldTask != null) { oldTask.cancel(true); } // prepare to launch a new task to load this new image AsyncTask<String, Integer, Bitmap> newTask = new AsyncTask<String, Integer, Bitmap>() { protected void onPreExecute() { // set ImageView to some "loading..." image } protected Bitmap doInBackground(String... urls) { return loadFromInternet(imageUrl); } protected void onPostExecute(Bitmap bitmap) { // set Bitmap if successfully loaded, or an "error" image if (bitmap != null) { imageView.setImageBitmap(bitmap); } else { imageView.setImageResource(R.drawable.error); } } }; newTask.execute(); // mark this as the latest Bitmap we want to set for this ImageView assignments.put(imageView, newTask); } /** returns (Bitmap on success | null on error) */ private Bitmap loadFromInternet(String imageUrl) {} }

Problem I still have: what if the Activity gets destroyed while some images are still loading?

Is there any risk when the loading thread calls back to the ImageView later, when the Activity is already destroyed? Moreover, AsyncTask has some global thread-pool underneath, so if lengthy tasks are not canceled when they're not needed anymore, I may end up wasting time loading things users don't see. My current design of keeping this thing globally is too ugly, and may eventually cause some leaks that are beyond my understanding. Instead of making ImageLoader a singleton like this, I'm thinking of actually creating separate ImageLoader objects for different Activities, then when an Activity gets destroyed, all its AsyncTask will be canceled. Is this too awkward?

Anyway, I wonder if there is a safe and standard way of doing this in Android. In addition, I don't know iPhone but is there a similar problem there and do they have a standard way to do this kind of task?

Many thanks.

最满意答案

我通过维护一个WeakHashMap来解决这个问题,该WeakHashMap会记住我想为该视图设置的最后一个Bitmap。

我通过setTag()将所需图像的URL附加到ImageView上。 然后,当我下载图像时,我仔细检查ImageView URL - 如果它与我刚下载的不同,我不会更新ImageView ,因为它已被回收。 我只是缓存它。

当Activity已经被销毁时,加载线程何时回调ImageView会有任何风险吗?

除了浪费的CPU时间和带宽(以及电池)之外,我不知道有任何风险。

我没有让ImageLoader成为像这样的单例,而是考虑为不同的Activity创建单独的ImageLoader对象,然后当一个Activity被销毁时,它的所有AsyncTask都将被取消。 这太尴尬了吗?

如果已经运行,取消AsyncTask并不是非常容易。 我只是让它运行完成。

理想情况下,避免单身人士。 使用Service ,或通过onRetainNonConfigurationInstance()将ImageLoader传递给活动的下一个实例(例如, onRetainNonConfigurationInstance() isFinishing()为false ,因此这是一个旋转)。

I solve this by maintaining a WeakHashMap that remembers the last Bitmap I want to set for that view.

I took the approach attaching the the URL of the desired image onto the ImageView via setTag(). Then, when I have the image downloaded, I double-check the ImageView URL -- if it is different than the one I just downloaded, I don't update the ImageView, because it got recycled. I just cache it.

Is there any risk when the loading thread calls back to the ImageView later, when the Activity is already destroyed?

I am not aware of any risk, other than a bit of wasted CPU time and bandwidth (and, hence, battery).

Instead of making ImageLoader a singleton like this, I'm thinking of actually creating separate ImageLoader objects for different Activities, then when an Activity gets destroyed, all its AsyncTask will be canceled. Is this too awkward?

Canceling an AsyncTask is not terribly easy, if it is already running. I'd just let it run to completion.

Ideally, avoid singletons. Either use a Service, or pass your ImageLoader to the next instance of your activity via onRetainNonConfigurationInstance() (e.g., isFinishing() is false in onDestroy(), so this is a rotation).