Usually, AsyncTask
s are meant to be one-shot, i.e. you start it, it fetches some data, displays the result and dies. However, I'm now using a long-running AsyncTask
to load images as the need arises. So far it's working great, but I'd just like to know if this is good practice & if there's any threading detail that I missed. Here's my slimmed down implementation:
public class ImageLoader extends AsyncTask<Void, Object, Void> {
private final List<String> pendingURLs = new Vector<String>();
private Thread backgroundThread;
@Override
protected Void doInBackground(Void... params) {
this.backgroundThread = Thread.currentThread();
while (!backgroundThread.isInterrupted()) {
String url = null;
synchronized(this) {
if (pendingURLs.size() > 0) {
url = pendingURLs.remove(0);
}
}
if (url != null) {
// fetch image...
publishProgress(url, fetchedBitmap); // in onProgressUpdate I call a listener that can be implemented by any Fragment or other component
}
try {
synchronized(this) {
if (pendingURLs.size() == 0) {
wait();
}
}
} catch (InterruptedException e) {
break;
}
}
}
public syncrhonized void addUrl(String url) {
pendingURLs.add(url);
notify();
}
public Thread getBackgroundThread() {
return backgroundThread;
}
}
Then, in my Fragment#onCreate()
I create a new instance and execute it like this:
mLoader = new ImageLoader();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
mLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
mLoader.execute();
}
Finally, in my Fragment#onDestroy()
I do:
if (mLoader != null && mLoader.getBackgroundThread() != null) {
mLoader.getBackgroundThread().interrupt(); // this will cause it to break out of the while loop and finish the thread
}
1 Answer 1
For starters, you should replace your Vector with an ArrayBlockingQueue . Then you can drop the wait and the null check. Can also then remove synchronization on the add method since its handled by the data structure.
private final BlockingQueue<String> pendingURLs = new ArrayBlockingQueue<String>(64);
protected void doInBackground(Void... params) {
this.backgroundThread = Thread.currentThread();
while (!backgroundThread.isInterrupted()) {
String url = pendingURLs.take();
// fetch image...
publishProgress(url, fetchedBitmap); // in onProgressUpdate I call a listener that can be implemented by any Fragment or other component
}
public void addUrl(String url) {
pendingURLs.put(url);
}
You should probably replace it with just a new thread instead of using asynctask at all.
public class ImageLoader implements Runnable {
private final BlockingQueue<String> pendingURLs = new ArrayBlockingQueue<String>();
public void run() {
while (true) {
String url = pendingURLs.take();
// fetch image...
publishProgress(url, fetchedBitmap); // in onProgressUpdate I call a listener that can be implemented by any Fragment or other component
}
}
public void addUrl(String url) {
pendingURLs.put(url);
}
}
Then you can create it in your initialization somewhere, ie
ImageLoader loader = new ImageLoader();
new Thread(loader, "BackgroundLoader").start();
-
\$\begingroup\$ +1 Nice improvement to use the blocking queue. However, I wouldn't stray from
AsyncTask
since it handles callingonProgressUpdate
on the UI thread whenever I callpublishProgress
from the background thread. \$\endgroup\$Felix– Felix2013年10月28日 11:38:56 +00:00Commented Oct 28, 2013 at 11:38 -
\$\begingroup\$ which one is better, Android loader or AsyncTask ???? \$\endgroup\$Namrata– Namrata2014年07月16日 10:09:22 +00:00Commented Jul 16, 2014 at 10:09