2

我正在后台从我的assets/. 我在网上找到了几个解决方案,但他们为每张图片启动了一个新的后台任务。

由于我发现它更易于理解,我将其更改为只有一个具有生产者/消费者模式的加载线程。BlockingQueue我对来自 UI 线程的请求使用s 并publishProgress做出响应。

我的解决方案运行良好,但它让我想知道如果我使用多线程是否会更好。我的代码支持请求失效,但它总是会在处理新请求之前完成当前正在执行的加载。

另一方面:线程不竞争磁盘访问吗?

我的代码:

package org.example;

import android.content.ContextWrapper;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

import android.os.AsyncTask;

import android.util.Pair;
import android.widget.ImageView;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.PriorityBlockingQueue;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class ImageLoader extends AsyncTask<Void, Pair<ImageLoader.SetImageTask, Bitmap>, Void> {

    //~ Static fields/initializers -------------------------------------------------------------------------------------

    private static final Logger L = LoggerFactory.getLogger(ImageLoader.class);

    //~ Instance fields ------------------------------------------------------------------------------------------------

    private final Map<String, Bitmap> cache                 = Maps.newHashMap();
    private final ContextWrapper contextWrapper;
    private final PriorityBlockingQueue<LoadTask> loadTasks =
        new PriorityBlockingQueue<ImageLoader.LoadTask>(1, Ordering.arbitrary());

    /* is changed to invalidate tasks */
    private String uuid = null;

    //~ Constructors ---------------------------------------------------------------------------------------------------

    public ImageLoader(final ContextWrapper contextWrapper) {
        this.contextWrapper     = contextWrapper;
        this.uuid               = UUID.randomUUID().toString();
    }

    //~ Methods --------------------------------------------------------------------------------------------------------

    public void invalidateTasks() {
        L.debug("invalidating tasks");
        this.uuid = UUID.randomUUID().toString();
    }

    public void setImage(final ImageView view, final String asset) {
        this.loadTasks.add(new SetImageTask(this.uuid, view, asset));
    }

    public void loadIntoCache(final ImmutableList<String> assets) {
        for (final String asset : assets) {
            this.loadTasks.add(new LoadIntoCacheTask(asset));
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    protected Void doInBackground(Void... param) {
        try {
            while (true) {

                LoadTask task = this.loadTasks.take();
                if (task instanceof SetImageTask) {

                    SetImageTask setImageTask = (SetImageTask) task;
                    if (setImageTask.uuid.equals(this.uuid)) {

                        Bitmap bitmap = this.loadAsset(setImageTask.asset);
                        if (bitmap != null) {
                            this.publishProgress(Pair.create(setImageTask, bitmap));
                        }
                    } else {
                        L.debug("request for asset '{}' outdated", setImageTask.asset);
                    }
                } else {

                    LoadIntoCacheTask cacheTask = (LoadIntoCacheTask) task;
                    Bitmap bitmap               = this.loadAsset(cacheTask.asset);
                    L.debug("storing in cache: '{}'", cacheTask.asset);
                    cache.put(cacheTask.asset, bitmap);
                }
            }
        } catch (final InterruptedException e) {
            L.debug("interrupted -> exiting");
        }

        return null;
    }

    @Override
    protected void onProgressUpdate(Pair<SetImageTask, Bitmap>... results) {

        Pair<SetImageTask, Bitmap> result = results[0];
        if (result.first.uuid.equals(this.uuid)) {
            result.first.view.setImageBitmap(result.second);
        } else {
            L.debug("request for asset '{}' outdated", result.first.asset);
        }
    }

    private Bitmap loadAsset(final String asset) {
        if (this.cache.containsKey(asset)) {
            L.debug("serving from cache '{}'", asset);
            return this.cache.get(asset);

        } else {
            L.debug("loading from assets: '{}'", asset);

            InputStream in = null;
            Bitmap bitmap  = null;
            try {
                in         = new BufferedInputStream(contextWrapper.getAssets().open(asset));
                bitmap     = BitmapFactory.decodeStream(in);
            } catch (final IOException e) {
                L.error(e.getMessage(), e);
            } finally {
                try {
                    in.close();
                } catch (final IOException e) {}
            }

            return bitmap;
        }
    }

    //~ Inner Interfaces -----------------------------------------------------------------------------------------------

    protected static interface LoadTask {}

    //~ Inner Classes --------------------------------------------------------------------------------------------------

    protected static final class SetImageTask implements LoadTask {

        private final String uuid;
        private final ImageView view;
        private final String asset;

        public SetImageTask(final String uuid, final ImageView view, final String asset) {
            this.uuid      = uuid;
            this.view      = view;
            this.asset     = asset;
        }
    }

    protected static final class LoadIntoCacheTask implements LoadTask {

        private final String asset;

        public LoadIntoCacheTask(final String asset) {
            this.asset = asset;
        }
    }
}
4

0 回答 0