First the problem:
- I'm working on the application that uses multiple
FragmentLists
within a customizedFragmentStatePagerAdapter
. There could be, potentially substantial number of such fragments say between 20 and 40. - Each fragment is a list in which each item could contain text or image.
- The images need to be uploaded asynchronously from the web and cached to temp memory cache and also to SD if available
- When Fragment goes off the screen any uploads and current activity should be cancelled (not paused)
My first implementation followed well known image loader code from Google. My problem with that code is that it basically creates one instance of AsyncTask
per image. Which in my case kills the app real fast.
Since I'm using v4 compatibility package I thought that using custom Loader that extends AsyncTaskLoader
would help me since that internally implements a thread pool. However to my unpleasant surprise if I execute this code multiple times each following invocation will interrupt the previous. Say I have this in my ListView#getView
method:
getSupportLoaderManager().restartLoader(0, args, listener);
This method is executed in the loop for each list item that comes into view. And as I stated - each following invocation will terminate the previous one. Or at least that's what happen based on 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
Then I thought that maybe giving unique id to each loader will help the matters but it doesn't seem to make any difference. As result I end up with seemingly random images and the app never loads even 1/4 of what I need.
The Question
- What would be the way to fix the Loader to do what I want (and is there a way?)
- If not what is a good way to create
AsyncTask
pool and is there perhaps working implementation of it?
To give you idea of the code here's stripped down version of Loader where actual download/save logic is in separate ImageManager class.
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;
}
}
}
AsyncTask
already uses a pool. The pool goes up to 128 threads IIRC, which may be the source of your difficulty. You can always implement your own thread pool usingjava.util.concurrent
classes. – SubstanceScheduledExecutorService
for its thread pool and task queue. Your task is added to theScheduledExecutorService
, which will dole it out to a thread (if there is one available) or put it in aLinkedBlockingQueue
(IIRC) waiting for a thread to clear up. Actually,ModernAsyncTask
from the support package is interesting -- you can use your ownScheduledExecutorService
now with any API level. – SubstanceAsyncTask
objects in rapid succession. – Substance