7

首先是问题:

  • 我正在开发FragmentLists 在自定义FragmentStatePagerAdapter. 可能存在大量此类片段,例如 20 到 40 个。
  • 每个片段都是一个列表,其中每个项目都可以包含文本或图像。
  • 图像需要从网络异步上传并缓存到临时内存缓存,如果可用,还可以缓存到 SD
  • 当 Fragment 离开屏幕时,应取消任何上传和当前活动(不暂停)

我的第一个实现遵循谷歌著名的图像加载器代码。我对该代码的问题是它基本上为AsyncTask每个图像创建一个实例。在我的情况下,这会很快杀死应用程序。

由于我使用的是 v4 兼容包,我认为使用扩展的自定义加载器AsyncTaskLoader会对我有所帮助,因为它在内部实现了一个线程池。但是令我不快的是,如果我多次执行此代码,则每次后续调用都会中断前一次。假设我的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。

问题

  • 什么方法可以修复 Loader 做我想做的事情(有没有办法?)
  • 如果不是什么是创建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;
        }

    }

}
4

1 回答 1

11

好的,所以首先要做的事情。android 附带的 AsyncTask 不应淹没您的应用程序或导致其崩溃。AsyncTask 运行在一个线程池中,其中最多有 5 个线程同时实际执行。虽然您可以将许多任务排队等待执行,但一次只有 5 个任务在执行。通过在后台线程池中执行这些,它们根本不应该对您的应用程序产生任何影响,它们应该可以平稳运行。

如果您对 AsyncTask 加载程序的性能不满意,使用 AsyncTaskLoader 将无法解决您的问题。AsyncTaskLoader 只是获取加载器接口并将其与 AsyncTask 结合。所以它本质上是映射 onLoadFinished -> onPostExecute, onStart -> onLoadInBackground。所以这是完全相同的事情。

我们为我们的应用程序使用相同的图像加载器代码,每次尝试加载图像时都会将异步任务放入线程池队列中。在 google 的示例中,他们将 imageview 与其异步任务相关联,以便在他们尝试在某种适配器中重用 imageview 时取消异步任务。你应该在这里采取类似的策略。您应该将您的图像视图与异步任务相关联,即在后台加载图像。当您有一个未显示的片段时,您可以循环浏览与该片段关联的图像视图并取消加载任务。简单地使用 AsyncTask.cancel() 应该可以很好地工作。

您还应该尝试实现异步图像视图示例说明的简单图像缓存机制。我们只需创建一个从 url -> weakreference 开始的静态 hashmap。这样,图像可以在需要时被回收,因为它们仅在弱引用的情况下被保留。

这是我们所做的图像加载的概述

public class LazyLoadImageView extends ImageView {
        public WeakReference<ImageFetchTask> getTask() {
        return task;
    }

    public void setTask(ImageFetchTask task) {
        this.task = new WeakReference<ImageFetchTask>(task);
    }

    private WeakReference<ImageFetchTask> task;

        public void loadImage(String url, boolean useCache, Drawable loadingDrawable){

        BitmapDrawable cachedDrawable = ThumbnailImageCache.getCachedImage(url);
        if(cachedDrawable != null){
            setImageDrawable(cachedDrawable);
            cancelDownload(url);
            return;
        }

        setImageDrawable(loadingDrawable);

        if(url == null){
            makeDownloadStop();
            return;
        }

        if(cancelDownload(url)){
            ImageFetchTask task = new ImageFetchTask(this,useCache);
            this.task = new WeakReference<ImageFetchTask>(task);
            task.setUrl(url);
            task.execute();
        }


        ......

        public boolean cancelDownload(String url){

        if(task != null && task.get() != null){

            ImageFetchTask fetchTask = task.get();
            String downloadUrl = fetchTask.getUrl();

            if((downloadUrl == null) || !downloadUrl.equals(url)){
                fetchTask.cancel(true);
                return true;
            } else
                return false;
        }

        return true;

          }
    }

因此,只需旋转片段中的图像视图,然后在片段隐藏时取消它们,并在片段可见时显示它们。

于 2011-11-26T03:52:52.783 回答