2

Quick question: I have been using frameworks that spawn worker threads to perform asynchronous tasks, a good example is Retrofit. Within the success/failure sections, I may pop up a Dialog box which would need to be on the UI thread. I have been accessing the underlying Activity/UI thread in this fashion within the success/failure sections of Retrofit:

Dialog dialog = new Dialog(LoginActivity.this, R.style.ThemeDialogCustom);

This works well 99.9% of the time but every once in a while, I receive the following error when creating a Dialog box:

android.view.WindowManager$BadTokenException
LoginActivity.java line 343 in LoginActivity$6.success()
Unable to add window -- token android.os.BinderProxy@41662138 is not valid;
is your activity running?

So, is my approach the most stable way to access the Activity context/UI thread from a worker thread or do I need a different approach?

Perry Hoekstra
  • 2,687
  • 3
  • 33
  • 52

2 Answers2

2

If you work with threads and not using Asynctasks, always run everything that changes UI in runOnUIThread like this

activity.runOnUiThread(new Runnable() {
     @Override
     public void run() {
         //change UI
     }
});

The more generic way to do it is this, which is pretty much the same

new Handler(Looper.getMainLooper()).post(new Runnable() {
         @Override
         public void run() {
             //change UI
         }
    })

See here the minimal difference between runOnUIThread and MainLooper

If you want to check if you are on the main/ui thread

if(Thread.currentThread() == Looper.getMainLooper().getThread()) {
   //you are on the main thread
}
Community
  • 1
  • 1
Patrick
  • 33,984
  • 10
  • 106
  • 126
  • That does not make much sense as I would then have to code it like this: LoginActivity.this.runOnUiThread(new Runnable() { @Override public void run() { Dialog dialog = new Dialog(LoginActivity.this, R.style.ThemeDialogCustom);}}); – Perry Hoekstra Mar 29 '15 at 22:38
  • And why does this not make sense? Thats pretty much what onPostExecute() in asynctask does – Patrick Mar 29 '15 at 23:11
  • My question then would be: What is the difference between LoginActivity.this.runOnUiThread(new Runnable ....) and just calling Dialog dialog = new Dialog(LoginActivity.this, R.style.ThemeDialogCustom)? – Perry Hoekstra Mar 30 '15 at 11:14
  • My concern was the appropriateness of calling LoginActivity.this from a worker thread and in the example of LoginActivity.this.ruOnUiThread(new Runnable ...), I am still stuck with calling LoginActivity.this – Perry Hoekstra Mar 30 '15 at 11:30
  • Then just use new Handler(Looper.getMainLooper()) which ommits dependencies to any activity – Patrick Mar 30 '15 at 11:31
0

AFAIK, there is nothing wrong with the approach you are using. The problem is occurring because the by the time the worker thread finishes and you are trying to show the dialog, the instance of the Activity has finished. So, the crash is totally dependent on the amount of time it takes for the thread to finish. And it seems that in your case, the thread mostly finishes when the Activity is still active; hence you don't get the error is most cases.

What you need to do is to check if the Activity is still running before trying to show the Dialog. One of the simplest ways would be to

if(!((Activity) LoginActivity.this).isFinishing())
{
    //safe to show your dialog
}
Swayam
  • 16,294
  • 14
  • 64
  • 102
  • Notice that this is the exact same problem that we used to have with Android AsyncTasks. There are two issues: 1) the LoginActivity may be in a bad state (destroyed) by the time you get back to it, and 2) you have leaked the reference to the LoginActivity: it cannot be GCed until the task finishes. – G. Blake Meike Mar 29 '15 at 18:45
  • That is just it, in this case, the Activity is not finishing. I was just popping up a Dialog box informing the user of something when this particular error occurred. – Perry Hoekstra Mar 29 '15 at 22:35