Android - стратегия пула потоков и можно ли использовать Loader для ее реализации? - PullRequest
7 голосов
/ 04 ноября 2011

Первая проблема:

  • Я работаю над приложением, которое использует несколько FragmentLists в соответствии с индивидуальными FragmentStatePagerAdapter. Там может быть, потенциально значительное количество таких фрагментов, скажем, между 20 и 40.
  • Каждый фрагмент представляет собой список, в котором каждый элемент может содержать текст или изображение.
  • Изображения должны загружаться асинхронно из Интернета и кэшироваться во временную кэш-память, а также на SD, если доступно
  • Когда Fragment исчезает с экрана, любые загрузки и текущая активность должны быть отменены (не приостановлена)

Моя первая реализация была основана на хорошо известном коде загрузчика изображений от Google. Моя проблема с этим кодом заключается в том, что он в основном создает один экземпляр AsyncTask на изображение. Что в моем случае очень быстро убивает приложение.

Поскольку я использую пакет совместимости v4, я подумал, что использование пользовательского Loader, расширяющего 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

Тогда я подумал, что, возможно, предоставление уникального идентификатора каждому загрузчику поможет, но, похоже, это не имеет значения. В результате я получаю, казалось бы, случайные изображения, и приложение никогда не загружает даже 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;
        }

    }

}

1 Ответ

11 голосов
/ 26 ноября 2011

Хорошо, так обо всем по порядку. AsyncTask, который поставляется с Android, не должен заглушать ваше приложение или вызывать его сбой. AsyncTasks запускаются в пуле потоков, где одновременно выполняется не более 5 потоков. Хотя вы можете поставить в очередь много задач для выполнения, только 5 из них выполняются одновременно. Выполняя их в фоновом пуле потоков, они вообще не должны влиять на ваше приложение, они должны просто работать без сбоев.

Использование AsyncTaskLoader не решит вашу проблему, если вы недовольны производительностью загрузчика AsyncTask. AsyncTaskLoader просто берет интерфейс загрузчика и объединяет его с AsyncTask. По сути, это отображение onLoadFinished -> onPostExecute, onStart -> onLoadInBackground. Так что это точно так же.

Мы используем тот же код загрузчика изображений для нашего приложения, который заставляет асинктическую задачу помещаться в очередь пула потоков каждый раз, когда мы пытаемся загрузить изображение. В примере Google они связывают просмотр изображений с его асинхронной задачей, чтобы они могли отменить асинхронную задачу, если они пытаются повторно использовать просмотр изображений в каком-то адаптере. Вы должны принять аналогичную стратегию здесь. Вы должны связать ваше изображение с асинхронной задачей загрузки изображения в фоновом режиме. Если у вас есть фрагмент, который не отображается, вы можете циклически просматривать изображения, связанные с этим фрагментом, и отменять загрузку. Простое использование AsyncTask.cancel () должно работать достаточно хорошо.

Вам также следует попытаться реализовать простой механизм кэширования изображений, описанный в примере с асинхронным представлением изображений. Мы просто создаем статический хэш-файл, который идет от url -> weakreference. Таким образом, изображения могут быть переработаны, когда они должны быть, потому что они удерживаются только со слабой ссылкой.

Вот схема загрузки изображения, которую мы делаем

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;

          }
    }

Так что просто вращайте изображения, которые находятся в вашем фрагменте, а затем отменяйте их, когда ваш фрагмент скрывается, и отображайте их, когда ваш фрагмент видим.

...