我正在后台从我的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;
}
}
}