First the problem:
FragmentLists
within a customized FragmentStatePagerAdapter
. There could be,
potentially substantial number of such fragments say between 20 and 40. 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
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;
}
}
}
Why Would you Utilize Thread Pool Executor in an Android or Java app? It is a strong task execution framework since it allows task queue insertion, task cancellation, and task prioritizing. It lowers the overhead associated with thread creation by managing a predetermined number of threads in its thread pool.
After completion of Thread Processing, ThreadPool can use the same Thread to do another process (so saving the time and resources to create another Thread.) The optimum size of the thread pool depends on the number of processors available and the nature of the tasks.
The ThreadPoolExecutor executes a given task using one of its threads from the thread pool. What are these parameters? corePoolSize: The minimum number of threads to keep in the pool.
To send a task to a thread pool, use the ExecutorService interface. Note that ExecutorService has nothing to do with Services, the Android application component. Creating threads is expensive, so you should create a thread pool only once as your app initializes.
Ok, so first things first. The AsyncTask that comes with android shouldn't drown out your app or cause it to crash. AsyncTasks run in a thread pool where there is at most 5 threads actually executing at the same time. While you can queue up many tasks to be executed , only 5 of them are executing at a time. By executing these in the background threadpool they shouldn't have any effect on your app at all, they should just run smoothly.
Using the AsyncTaskLoader would not solve your problem if you are unhappy with the AsyncTask loader performance. The AsyncTaskLoader just takes the loader interface and marries it to an AsyncTask. So it's essentially mapping onLoadFinished -> onPostExecute, onStart -> onLoadInBackground. So it's the same exact thing.
We use the same image loader code for our app that causes an asynctask to be put onto the threadpool queue each time that we try to load an image. In google's example they associate the imageview with its async task so that they can cancel the async task if they try to reuse the imageview in some sort of adapter. You should take a similar strategy here. You should associate your imageview with the async task is loading the image in the background. When you have a fragment that is not showing you can then cycle through your image views associated with that fragment and cancel the loading tasks. Simply using the AsyncTask.cancel() should work well enough.
You should also try to implement the simple image caching mechanism the async image view example spells out. We simply create a static hashmap that goes from url -> weakreference . This way the images can be recycled when they need to be because they are only held on with a weak reference.
Here's an outline of the image loading that we do
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;
}
}
So just rotate through your image views that are in your fragment and then cancel them when your fragment hides and show them when your fragment is visible.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With