Android - 线程池策略以及可以使用Loader来实现吗?
首先是问题:
- 我正在开发使用多个
FragmentLists
的应用程序 在自定义的FragmentStatePagerAdapter
中。可能有, 此类片段的数量可能在 20 到 40 之间。 - 每个片段都是一个列表,其中每个项目都可以包含文本或图像。
- 图像需要从网络异步上传并缓存到临时内存缓存以及 SD(如果可用)
- 当 Fragment 离开屏幕时,任何上传和当前活动都应取消(而不是暂停)
我的第一个实现遵循众所周知的 图像加载器代码。我对该代码的问题是它基本上为每个图像创建一个 AsyncTask 实例。就我而言,这会很快杀死该应用程序。
由于我使用的是 v4 兼容性包,我认为使用扩展 AsyncTaskLoader 的自定义 Loader 会对我有所帮助,因为它在内部实现了线程池。然而,令我不愉快的是,如果我多次执行此代码,则每次后续调用都会中断前一个调用。假设我的 ListView#getView
方法中有这个:
getSupportLoaderManager().restartLoader(0, args, listener);
此方法在每个进入视图的列表项的循环中执行。正如我所说 - 每个后续调用都会终止前一个调用。或者至少基于 LogCat 会发生这种情况
11-03 13:33:34.910: V/LoaderManager(14313): restartLoader in LoaderManager: args=Bundle[{URL=http://blah-blah/pm.png}]
11-03 13:33:34.920: V/LoaderManager(14313): Removing pending loader: LoaderInfo{405d44c0 #2147483647 : ImageLoader{405118a8}}
11-03 13:33:34.920: V/LoaderManager(14313): Destroying: LoaderInfo{405d44c0 #2147483647 : ImageLoader{405118a8}}
11-03 13:33:34.920: V/LoaderManager(14313): Enqueuing as new pending loader
然后我想也许为每个加载器提供唯一的 id 会有所帮助,但它似乎没有任何区别。结果,我最终得到了看似随机的图像,而应用程序甚至从未加载过我需要的 1/4。
问题
- 修复加载器以执行我想要的操作的方法是什么(有没有办法?)
- 如果没有,创建 AsyncTask 池的好方法是什么,是否有可能有效的实现?
为了让您了解这里的代码,这是 Loader 的精简版本,其中实际的下载/保存逻辑位于单独的 ImageManager 类中。
public class ImageLoader extends AsyncTaskLoader<TaggedDrawable> {
private static final String TAG = ImageLoader.class.getName();
/** Wrapper around BitmapDrawable that adds String field to id the drawable */
TaggedDrawable img;
private final String url;
private final File cacheDir;
private final HttpClient client;
/**
* @param context
*/
public ImageLoader(final Context context, final String url, final File cacheDir, final HttpClient client) {
super(context);
this.url = url;
this.cacheDir = cacheDir;
this.client = client;
}
@Override
public TaggedDrawable loadInBackground() {
Bitmap b = null;
// first attempt to load file from SD
final File f = new File(this.cacheDir, ImageManager.getNameFromUrl(url));
if (f.exists()) {
b = BitmapFactory.decodeFile(f.getPath());
} else {
b = ImageManager.downloadBitmap(url, client);
if (b != null) {
ImageManager.saveToSD(url, cacheDir, b);
}
}
return new TaggedDrawable(url, b);
}
@Override
protected void onStartLoading() {
if (this.img != null) {
// If we currently have a result available, deliver it immediately.
deliverResult(this.img);
} else {
forceLoad();
}
}
@Override
public void deliverResult(final TaggedDrawable img) {
this.img = img;
if (isStarted()) {
// If the Loader is currently started, we can immediately deliver its results.
super.deliverResult(img);
}
}
@Override
protected void onStopLoading() {
// Attempt to cancel the current load task if possible.
cancelLoad();
}
@Override
protected void onReset() {
super.onReset();
// Ensure the loader is stopped
onStopLoading();
// At this point we can release the resources associated with 'apps'
// if needed.
if (this.img != null) {
this.img = null;
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
好的,首先要说的是第一件事。 Android 附带的 AsyncTask 不应淹没您的应用程序或导致其崩溃。 AsyncTasks 在线程池中运行,其中最多有 5 个线程同时执行。虽然您可以排队执行许多任务,但一次只能执行其中 5 个任务。通过在后台线程池中执行这些,它们根本不会对您的应用程序产生任何影响,它们应该顺利运行。
如果您对 AsyncTaskLoader 的性能不满意,那么使用 AsyncTaskLoader 并不能解决您的问题。 AsyncTaskLoader 只是采用加载器接口并将其与 AsyncTask 结合起来。所以它本质上是映射 onLoadFinished -> onPostExecute、onStart -> onLoadInBackground。所以这是完全相同的事情。
我们为应用程序使用相同的图像加载器代码,每次尝试加载图像时,都会将异步任务放入线程池队列中。在谷歌的示例中,他们将 imageview 与其异步任务相关联,以便在尝试在某种适配器中重用 imageview 时可以取消异步任务。您应该在这里采取类似的策略。您应该将您的 imageview 与正在后台加载图像的异步任务关联起来。当您有一个未显示的片段时,您可以循环浏览与该片段关联的图像视图并取消加载任务。只需使用 AsyncTask.cancel() 就可以很好地工作。
您还应该尝试实现异步图像视图示例中阐明的简单图像缓存机制。我们简单地创建一个静态哈希图,从 url ->弱引用。这样,图像就可以在需要时被回收,因为它们仅通过弱引用来保存。
这是我们所做的图像加载的概述,
因此只需旋转片段中的图像视图,然后在片段隐藏时取消它们,并在片段可见时显示它们。
Ok, so first things first. The AsyncTask that comes with android shouldn't drown out your app or cause it to crash. AsyncTasks run in a thread pool where there is at most 5 threads actually executing at the same time. While you can queue up many tasks to be executed , only 5 of them are executing at a time. By executing these in the background threadpool they shouldn't have any effect on your app at all, they should just run smoothly.
Using the AsyncTaskLoader would not solve your problem if you are unhappy with the AsyncTask loader performance. The AsyncTaskLoader just takes the loader interface and marries it to an AsyncTask. So it's essentially mapping onLoadFinished -> onPostExecute, onStart -> onLoadInBackground. So it's the same exact thing.
We use the same image loader code for our app that causes an asynctask to be put onto the threadpool queue each time that we try to load an image. In google's example they associate the imageview with its async task so that they can cancel the async task if they try to reuse the imageview in some sort of adapter. You should take a similar strategy here. You should associate your imageview with the async task is loading the image in the background. When you have a fragment that is not showing you can then cycle through your image views associated with that fragment and cancel the loading tasks. Simply using the AsyncTask.cancel() should work well enough.
You should also try to implement the simple image caching mechanism the async image view example spells out. We simply create a static hashmap that goes from url -> weakreference . This way the images can be recycled when they need to be because they are only held on with a weak reference.
Here's an outline of the image loading that we do
So just rotate through your image views that are in your fragment and then cancel them when your fragment hides and show them when your fragment is visible.