Android – Displaying Dialogs From Background Threads

Having threads to do some heavy lifting and long processing in the background is pretty standard stuff. Very often you would want to notify or prompt the user after the background task has finished by displaying a Dialog.

The displaying of the Dialog has to happen on the UI thread, so you would do that either in the Handler object for the thread or in the onPostExecute method of an AsyncTask (which is a thread as well, just an easier way of implementing it). That is a textbook way of doing this and you would think that pretty much nothing wrong could go with this.

Surprisingly I found out that something CAN actually go wrong with this. After Google updated the Android Market and started giving crash reports to the developers I received the following exception:

android.view.WindowManager$BadTokenException: Unable to add window — token android.os.BinderProxy@447a6748 is not valid; is your activity running?
at android.view.ViewRoot.setView(ViewRoot.java:468)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:177)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:91)
at android.view.Window$LocalWindowManager.addView(Window.java:424)
at android.app.Dialog.show(Dialog.java:239)
at android.app.Activity.showDialog(Activity.java:2488)

at android.os.Handler.dispatchMessage(Handler.java:99)

I only got a couple of these exceptions from thousands of installs, so I knew that was not anything that happens regularly or that it was easy to replicate.

Looking at the stack trace above it gives us a pretty good idea why it failed. It started in the Handler object, which naturally was called by a background thread after it finished its processing. The Handler instance tried to show a Dialog and before it could show it, it tried to set the View for it and then it failed with:

android.view.WindowManager$BadTokenException: Unable to add window — token android.os.BinderProxy@447a6748 is not valid; is your activity running?

The 447a6748 number is just a memory address of an object that no longer exists.

Note- do not get hung up on the exact number. It would be different with every execution.

Now we know why the application crashed, the only thing left is to figure out what caused it?

We know that background threads execute independently of the main UI thread. That means that the user could be interacting with the application during the time that the thread is doing its work under the covers. Well, what happens if the user hits the “Back” button on the device while the background thread is running and what happens to the Dialog that this thread is supposed to show? Well, if the timing is right the application will most likely crash with the above described error.

In other words what happens is that the Activity will be going through its destruction when the background thread finishes its work and tries to show a Dialog.

In this case it is almost certain that this should have been handled by the Virtual Machine. It should have recognized the fact that the Activity is in the process of finishing and not even attempted to show the Dialog. This is an oversight of the Google developers and it will probably be fixed some time in the future, but in the meantime the burden is on us to take care of this.

The fix to this is pretty simple. Just test if the Activity is going through its finishing phase before displaying the Dialog:

private Handler myHandler = new Handler() {
  @Override
  public void handleMessage(Message msg) {
    switch (msg.what) {
      case DISPLAY_DLG:
        if (!isFinishing()) {
        showDialog(MY_DIALOG);
        }
      break;
    }
  }
};
Friday, August 20th, 2010 Android, Java, Programming

26 Comments to Android – Displaying Dialogs From Background Threads

  1. Hey Thanks….
    I was finishing activity just after the call to AsyncTask thats why exception was getting fired.
    coz there was no activity to show dialog.
    anyways thanks for guiding…

  2. shashank degloorkar on March 10th, 2011
  3. great!

  4. wang on May 24th, 2011
  5. Thanks! Mine is 4 out of 1000 installs to be exact.

  6. kn on May 24th, 2011
  7. I’ve found another one. The same message (during testing of course) is reasoned by the fact that I’m running the activity which should start the dialog in a tabbed Host.
    In that case you’ve to write
    xyz = new Dialog (getParent()) instead of
    new Dialog (this)

    regards

    Michael

  8. Michael on July 11th, 2011
  9. @Michael, bingo – just started having the same issue after switching to tabs.

  10. Artem Russakovskii on August 9th, 2011
  11. Many thanks!

  12. Diana on August 19th, 2011
  13. Thanks man!!

  14. Demc on September 29th, 2011
  15. Thank you for this information. I put it into work just now. I hope this helps the few users that are reporting the crash. How annoying!

    Thank you very much,

    Joshua

  16. Joshua on October 20th, 2011
  17. Thank you Man

  18. iiizio on November 6th, 2011
  19. Thanks a lot man. Works for me =D

  20. Jonas on November 20th, 2011
  21. You could put an extra check, making the activity a WeakReference or your handler a WeakReference. For example, say you have an AsyncTask and you send the handler as parameter:

    public class SomeThread extends AsyncTask<Void, Void, HashMap> {
    private WeakReference weakHandler;

    public SomeThread(WeakReference weakProgressDialog, WeakReference weakHandler) {
    this.weakProgressDialog = weakProgressDialog;
    this.weakHandler = weakHandler;
    }

    @Override
    protected HashMap doInBackground(Void… params) {
    ErrorObject errorObject = null;
    HashMap result = null;
    String webResponse = DoSomeServerRequest();
    if (webResponse != null) {
    errorObject = parseError(webResponse);
    if (errorObject != null) {
    result.put(errorObject, null);
    } else {
    MyObject myObject = parseMyObject(webResponse);
    if (myObject != null) {
    result.put(null, myObject);
    }
    }

    return result;
    }

    errorObject = new ErrorObject();
    errorObject.setCode(0);
    errorObject.setMessage(“An error has occured”);

    result.put(errorObject, null);

    return result;
    }

    @Override
    protected void onPostExecute(HashMap result) {
    ProgressDialog progressDialog = weakProgressDialog.get();
    if (progressDialog != null) {
    progressDialog.dismiss();
    }

    if (result != null) {
    for (Map.Entry resultEntries : result.entrySet()) {
    ErrorObject errorObject = resultEntries.getKey();
    MyObject myObject = resultEntries.getValue();

    Message msg = new Message();
    Bundle b = new Bundle();
    if (errorObject != null) {
    b.putSerializable(“error”, errorObject);
    } else if (domainsList != null) {
    b.putSerializable(“myObject”, myObject);
    }

    msg.setData(b);

    Handler myHandler = weakHandler.get();
    if (myHandler != null) {
    myHandler.sendMessage(msg);
    }
    }
    }
    }
    }

  22. Bogdan on November 22nd, 2011
  23. Sorry, I forgot to mention the usage:

    Handler myHandler = new Handler() {
    // Handler implementation
    };

    ProgressDialog progressDialog = ProgressDialog.show(myContext, “”, “Loading. Please wait…”, true);

    WeakReference weakProgressDialog = new WeakReference(progressDialog);
    WeakReference weakHandler = new WeakReference(myHandler);

    SomeThread someThread = new SomeThread(weakProgressDialog, weakHandler)
    someThread.execute();

  24. Bogdan on November 22nd, 2011
  25. Perfect! Thanks.

  26. Hagar on January 2nd, 2012
  27. In my case error occurred because the dialog show was invoked during screen rotation. I will put your check.
    Thanks

  28. Ivano on January 14th, 2012
  29. Hi. Intesting post. But can u help me? I have two classes: 1 is activity and 2 is non-activity class where i have create different Dialogs. Then i test connection from activity class – all works fine, but when i try to check connection on button back from 3 activity (in theory ot might be bring me back to 1 activity) my dialog dont appear and all app is crashed! Can i use urs code in this case and how? Thanks.
    PS Sorry for my bad English :)

  30. Salmpy on January 30th, 2012
  31. @Salmpy,

    I am not sure I understand what you are trying to do.
    But if you are trying to display dialogs from classes that do not inherit from Activity, you need to pass in an Activity context.

    So basically you can either have a member variable in the non Activity class to hold a reference to your Activity context, or pass the activity context in a method call (as a parameter). If that is what you are looking for and need more explanation, let me know.

  32. dimitar on January 31st, 2012
  33. Thanks! :)

  34. Botond on February 9th, 2012
  35. Hi,

    Thanks for that. I am a little worried about the lack of synchronized() here though. In the following case it can still go wrong (assume background thread running):
    - ifFinishing() == False
    - Thread switch to UI thread
    - call to finish()
    - Thread switch back to background thread
    - dialog.show()

    Therefore, both the ifFinishing() and finish() should be surrounded by a synchronized() clause. This thread switch is unlikely to happen right at that point, but it’s nonetheless undesirable.

  36. Aert on March 2nd, 2012
  37. @Aert — synchronized() is not necessary here. All of this code runs on the main UI thread: ifFinishing() and dialog.show() are called in a Handler’s message processing code, so are executed from the UI thread’s event loop.

    @Dimitar — your site design is a little non-intuitive. You should move the captcha above the submit button. :)

  38. Jules on March 30th, 2012
  39. hi,

    I am creating a dialog from Non-activity class having a reference to activity. I have following code …

    MyNonActivityClass{

    //other code here

    public static AlertDialog dialog;
    private void showErrorDialog(String msg) {
    final AlertDialog.Builder builder = new AlertDialog.Builder(context);
    builder.setTitle(“Error”).setMessage(msg).setCancelable(false).setPositiveButton(“Ok”, listener);

    // Log.i(tag, “dialog created”);
    context.runOnUiThread(new Runnable() {

    @Override
    public void run() {
    UIManager.errorDialog = builder.create();
    if (!context.isFinishing(){
    context.showDialog(UIManager.ERROR_DIALOG_ID/* , null */);
    }else{
    Log.i(tag, “can’t display dialog”);
    }
    }
    });
    }
    }

    I was getting this error before I put this check (!isFinishing()) in second app launch on OK button. Error is now gone but so do the Dialog as it always goes in else part (“cant’t display dialog”).

    I am not sure why my activity is being destroyed/paused as I am not pressing back button.

    thanks.

  40. leo on April 2nd, 2012
  41. hi,

    I have just notices, when I finish my activity by calling finish(), it doesn’t gets destroyed properly I believe as when press and hold Home key I can see my activity in list.

    What could be wrong with finish()??

  42. leo on April 2nd, 2012
  43. @Leo,

    Pressing and holding the home button gives you the recently run applications not the currently running applications. Your app is in the list because you had recently started it, but it is not necessarily running. So this behavior is normal.

    About your other question, please take a look at the Activity lifecycle:

    http://developer.android.com/reference/android/app/Activity.html

    There is no guarantee that the Activity will always be available to you even if you did not call “finish()” on it.

    It is hard to say what is going wrong with your application from this small snippet. It looks like your background task takes too long and the Activity is being destroyed before the thread finishes its job.

  44. dimitar on April 2nd, 2012
  45. Thank you ~ !

  46. sliper on April 12th, 2012
  47. Thank you ~!

  48. sliper on April 12th, 2012
  49. tnks a lot

  50. jornando junior on April 25th, 2012
  51. thank you very much

  52. letroll on April 30th, 2012

Leave a comment

*

Search

 

Archive

May 2012
M T W T F S S
« Mar    
 123456
78910111213
14151617181920
21222324252627
28293031  

Other