Android - thread pool strategy and can Loader be used to implement it?

First problem:

  • I am working on an application that uses multiple FragmentLists in a custom FragmentStatePagerAdapter . Perhaps a potentially significant number of such fragments say from 20 to 40.
  • Each fragment is a list in which each element can contain text or an image.
  • Images must be downloaded asynchronously from the Internet and cached in temporary memory, as well as in SD, if available
  • When Fragment is turned off, any downloads and current activity should be canceled (not paused).

My first implementation went through the famous image downloader code from Google. My problem with this code is that it basically creates one AsyncTask instance for each image. Which in my case quickly kills the application.

Since I am using the v4 compatibility pack, I thought that using a custom Loader that extends AsyncTaskLoader would help me as it internally implements a thread pool. However, to my unpleasant surprise, if I execute this code several times, each subsequent call will interrupt the previous one. Say I have this in my ListView#getView :

 getSupportLoaderManager().restartLoader(0, args, listener); 

This method runs in a loop for each list item that appears in the view. And, as I said, each subsequent call will be interrupted by the previous one. Or at least what happens on the basis of 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 perhaps providing a unique identifier to each bootloader would help in resolving issues, but it does not seem to make any difference. As a result, I end up with seemingly random images, and the application never downloads even 1/4 of what I need.

Question

  • What would be the way to get the bootloader to do what I want (and is there a way?)
  • If not what is a good way to create an AsyncTask pool and is it possible to execute it?

To give you an idea of ​​this code, a stripped-down version of Loader, where the actual load / save logic is in a 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; } } } 
+7
source share
1 answer

OK, first first. AsyncTask, which comes with Android, should not mute your application or cause it to crash. AsyncTasks runs in a thread pool, in which no more than 5 threads are actually running at a time. Although you can queue up many tasks that will be performed, only 5 of them are performed at a time. Running them in the background thread, they should not affect your application at all, they should just work smoothly.

Using AsyncTaskLoader will not solve your problem if you are unsatisfied with the performance of the AsyncTask loader. AsyncTaskLoader simply takes the bootloader interface and marries AsyncTask. Therefore, it essentially displays onLoadFinished -> onPostExecute, onStart -> onLoadInBackground. So this is the same.

We use the same image downloader code for our application, which forces the async to queue threadpool every time we try to load an image. In the Google example, they map the image to its async task so that they can cancel the async task if they try to reuse the image in some adapter. You should go for a similar strategy. You must link your image to the async task by loading the image in the background. When you have a fragment that is not displayed, you can cycle through the images associated with this fragment and cancel the download task. Just using AsyncTask.cancel () should work pretty well.

You should also try to implement a simple image caching mechanism, which will show an example of an asynchronous image representation. We just create a static hash file that comes from url -> weakreference. Thus, images can be recycled when they should be, because they are only held when there is a weak link.

Here is the image loading scheme we make

 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 the images that are in your fragment, and then undo them when your fragment is hiding and show them when your fragment is visible.

+11
source

All Articles