12

The application I'm working on is using a background thread to download a list of images through a API, then display the images in a slideshow.

There is a background task (currently AsyncTask) to periodically fetch the new images.

I'm not getting any error messages about the wrong Thread etc, it's just that the AsyncTasks's second instance will not run the doInBackground method.

Here is some code from the Activity:

private DownloadTask mDownloadTask = null;
private Handler mHandler;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if(mDownloadTask != null) {
                mDownloadTask.cancel(true);
            }
            mDownloadTask = new DownloadTask();
            mDownloadTask.execute((Void[]) null);
        }
    };

    mDownloadTask = new DownloadTask();
    mDownloadTask.execute((Void[]) null);
}

The DownloadTask looks like this:

@Override
protected List<String> doInBackground(Void... voids) {
     // Download list of URLs from server, etc.
}

@Override
protected void onPostExecute(List<String> urls) {
    mHandler.sendEmptyMessageDelayed(111, 5000);
}

The handler will be called, the onPreExecute (in the AsyncTask) will be called and the initial run of DownloadTask (right in onCreate) also works.

According to this question: Android SDK AsyncTask doInBackground not running (subclass) , it might be SDK15 related.

Thanks for any hints.


Update As I received comments that the Handler might not be in the UI thread (which is weird, as Thread.currentThread is the same both in onCreate and the Handlers handleMessage method, I revised the handleMessage method to:

mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if(mDownloadTask != null) {
                    mDownloadTask.cancel(true);
                }
                mDownloadTask = new DownloadTask();
                mDownloadTask.execute((Void[]) null);
            }
        });
    }
};

Still without success.


Update the full DownloadTask class

class DownloadTask extends AsyncTask<Void, Void, List<String>> {

    @Override
    protected void onPreExecute() {
        // Cancel the animation.
        if (mSlideshowAnimation != null) {
            mSlideshowAnimation.cancel(true);
        }

        mImageView1.setVisibility(View.GONE);
        mImageView2.setVisibility(View.GONE);
        animate(mProgressBar).alpha(1.0f).setDuration(500).start();

        Log.d(TAG, "Download preparation done.");
    }

    @Override
    protected List<String> doInBackground(Void... voids) {
        Log.d(TAG, "Download");
        SharedPreferences s = getSharedPreferences("access", Context.MODE_PRIVATE);
        String token = s.getString("token", null);

        Log.d(TAG, "Downloading slideshows.");

        List<String> urls = new ArrayList<String>();
        Slideshow[] slideshows = new Api(SlideshowActivity.this).getSlideshows(token);
        for (Slideshow slideshow : slideshows) {
            urls.addAll(slideshow.getAllPhotoUrls());
        }

        Log.d(TAG, "Downloading slideshows: " + slideshows.length);

        for (String url : urls) {
            try {
                url = Api.HOST + url;

                if (!Cache.fileExists(Cache.getCacheFilenameForUrl(SlideshowActivity.this, url))) {
                    Cache.cacheStream(SlideshowActivity.this, HttpHelper.download(SlideshowActivity.this, url), url);
                } else {
                    Log.d(TAG, "Cached: " + url);
                }
            } catch (IOException e) {
                Log.e(TAG, "Error while downloading.", e);
            }
        }

        Log.d(TAG, "Downloading slideshows finished.");

        return urls;
    }

    @Override
    protected void onPostExecute(List<String> urls) {
        Log.d(TAG, "download successful");
        animate(mProgressBar).alpha(0.0f).setDuration(500).start();

        mCurrentImageIndex = -1;
        mImageUrls = urls;

        mSlideshowAnimation = new SlideshowAnimation();
        mSlideshowAnimation.execute((Void[]) null);

        mHandler.sendEmptyMessageDelayed(111, 5000);
    }
}
Community
  • 1
  • 1
Sebastian Roth
  • 11,344
  • 14
  • 61
  • 110
  • One AsyncTask object can be executed only once. So try changing your code and instead using object of AsyncTask class directly use it as new DownloadTask().execute(); – MKJParekh Apr 26 '12 at 07:06
  • @Frankenstein that's what I'm doing in `mDownloadTask = new DownloadTask();` both times? – Sebastian Roth Apr 26 '12 at 07:08
  • Do not write `.execute((Void[]) null)`. It can be written `.execute()`. – Hauleth Apr 26 '12 at 07:14
  • @Hauleth can be, but creates compiler warnings for `unchecked varargs` parameter. – Sebastian Roth Apr 26 '12 at 07:27
  • can you post your full DownloadTask class ? – waqaslam Apr 26 '12 at 07:39
  • @Waqas, added and contains unrelated code as well, sorry for that. :-) – Sebastian Roth Apr 26 '12 at 07:44
  • I just tested your code-pattern in my test project and its working perfectly fine. handler loads new AsyncTask after every 5 secs. – waqaslam Apr 26 '12 at 08:19
  • What SDK / phone OS are you using? I ended up switching all AsyncTasks to a manual `Thread` and `Handler` model now and it works at least. The key problem I observed is the inability to recreate AsyncTasks with 'new'. – Sebastian Roth Apr 26 '12 at 08:21
  • is it this operation `mSlideshowAnimation = new SlideshowAnimation(); mSlideshowAnimation.execute((Void[]) null); ` which doesnt get executed? – waqaslam Apr 26 '12 at 08:21
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/10531/discussion-between-sebastian-roth-and-waqas) – Sebastian Roth Apr 26 '12 at 08:23
  • possible duplicate of [Android SDK AsyncTask doInBackground not running (subclass)](http://stackoverflow.com/questions/9119627/android-sdk-asynctask-doinbackground-not-running-subclass) – Heath Borders Dec 16 '13 at 21:01
  • Old thread, but I think this may be the solution: http://stackoverflow.com/questions/13647869/second-asynctask-not-executing – Michael Feb 25 '16 at 05:35

2 Answers2

14

Thanks to a helpful discussion with Waqas (thank you!) I finally uncovered the error in my code. In fact all above written is correct and works as is. Issue with my case was that the second task blocked the first one and vice versa.

It was perhaps a coincidence to find this post on Google Groups: http://groups.google.com/group/android-developers/browse_thread/thread/f0cd114c57ceefe3?tvc=2&q=AsyncTask+in+Android+4.0 . Recommend that everyone involved in threading reads this discussion carefully.

AsyncTask switched the Threading model to a serial executor (again), which is not compatible with my approach of having 2 AsyncTasks it seems.

Finally I switched the handling of the "Download" to a classic Thread and used the Handler to post messages to cancel the Slideshow if neccessary. Using the Handlers sendEmptyMessageDelayed I will simple recreate the Download Thread after a while to refresh the data.

Thanks to all comments & answers.

Sebastian Roth
  • 11,344
  • 14
  • 61
  • 110
  • You have to create a new instance of ThreadPoolExecutor with increased pool size and call executeOnExecutor for async task. Now you can run tasks in parallel – Vivek MVK Nov 26 '19 at 16:43
3

Handler is a kind of thread in android, and AsyncTask also runs in different thread. When you use AsyncTask there are few rules..

There are a few threading rules that must be followed for this class to work properly:

The task instance must be created on the UI thread. execute(Params...) must be invoked on the UI thread. Do not call onPreExecute(), onPostExecute(Result), doInBackground(Params...), onProgressUpdate(Progress...) manually. The task can be executed only once (an exception will be thrown if a second execution is attempted.)

So it clearly says AsyncTAsk must be called from UI thread.. where as you call it from Handler which is not an UI thread...

well try this too

mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            if(mDownloadTask != null) {
                mDownloadTask.cancel(true);
            }
           if([isCancelled()][1]){
            mDownloadTask = new DownloadTask();
            mDownloadTask.execute((Void[]) null);
          } // i assume your task is not getting cancelled before starting it again..
        }
    });
  }
};

and also the documentation says this..

There are two main uses for a Handler:

(1) to schedule messages and runnables to be executed as some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.

Ant4res
  • 1,217
  • 1
  • 18
  • 36
ngesh
  • 13,398
  • 4
  • 44
  • 60
  • When I add `Log.d(TAG, "Thread: " + Thread.currentThread());` in both `Handler`.`handleMessage` and the activities `onCreate` function, I will receive `THREAD: Thread[main,5,main]`. – Sebastian Roth Apr 26 '12 at 07:12
  • 1
    What @Sandy said is that current thread you use to execute the asynctask is the wrong thread as it is the thread of the handler. So one solution, not really beautiful but that should work could be to wrap your execute invocation using runOnUiThread...or get rid of the handler to call execute (better). – Snicolas Apr 26 '12 at 07:13
  • Also pls. notice that I'm creating the `mHandler` object in the `onCreate` method of the activity. According to the docs: `Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler, it is bound to the thread / message queue of the thread that is creating it (...) ` – Sebastian Roth Apr 26 '12 at 07:14
  • @SebastianRoth .. yes you are right.. it is a 'new Thread' which remains bound to the thread that created it.. so again its 'another thread not UI thread' – ngesh Apr 26 '12 at 07:17
  • @sandy Just updated the code example. Perhaps thats more close? – Sebastian Roth Apr 26 '12 at 07:20
  • 2
    Handler itself is not a kind of thread. Its a mechanism which allows to provide messages and runnables to a thread it is working for – waqaslam Apr 26 '12 at 07:22
  • @Waqas ..But it never interrupts the UI thread even when you give it some time consuming background task... it does mean that it can create another Thread – ngesh Apr 26 '12 at 07:25
  • 1
    It does interrupt UI thread if its attached to it. Add this code `new Handler().post(new Runnable() { @Override public void run() { try { Thread.sleep(60000); } catch (InterruptedException e) { e.printStackTrace(); } } });` with in your **onCreate** method and it will halt the app for 1 minute. – waqaslam Apr 26 '12 at 07:31
  • @sandy doesn't help. I'm always cancelling the task if it's `!= null` anyway. – Sebastian Roth Apr 26 '12 at 07:32
  • @Waqas .. it does because it puts this task inside message queue of UI thread.. it does't mean that it cannot run in a Background thread... – ngesh Apr 26 '12 at 07:32
  • 2
    You can attach your handler to another Looper - looper which is connected to another thread. In that case it will work aside from UI thread. When you declare `new Handler()` in activity, it is automatically informed to work for UI thread, unless you provide a different Looper – waqaslam Apr 26 '12 at 07:33