1

I'd like to someone explain me why this fails...

My activity has an inner class, AppHelper, that exports a function setThrobber. For simplicity I have omitted all the init code, etc..

This function setThrobber is meant to be called outside of the UI thread, normally during network loads, to report progress... Then, the classes use the inner class AppHelper with the method setThrobber to do so, from the network-loader thread.

To my surprise, the first approach fails (see error at the end) and the second succeeds. Why isn't the first one executed in the UI thread and the second one is??? More strange is, in the error stack trace looks like it IS called from the UI thread, even though Android throws the Called From Wrong Thread exception. Why are not both code chunks equivalent from the thread point of view?

PD- I also tried a handler.post() with the same result! PD2- The AppHelper class is instantiated inside onCreate

Thanks in advance !!

public class MyApplication extends Activity {

    private ProgressDialog progressDialog;

    void setThrobber_internal (String message) {
         progressDialog.setMessage(message);
    }

    public class AppHelper {

        public setThrobber(final String msg) {
             MyApplication.this.runOnUiThread(new Runnable() {
                 @Override 
                 public void run() {
                     setThrobber_internal(msg);
                     // This throws CalledFromWrongThread (!!)
                 }
             }); 
        }
    }
}

VERSUS

public class MyApplication extends Activity {

    private ProgressDialog progressDialog;

    private void setThrobber_internal(final String msg) {

         // runUiThread here instead of in inner class wrapper

         runOnUiThread(new Runnable() {
             @Override 
             public void run() {
                 progressDialog.setMessage(msg);
             }
         }); 
    } 

    public class AppHelper {

        public void setThrobber(final String msg) {
             setThrobber_internal(msg); // this works OK
        }
    }
}

The Stack trace of the first situation:

E/AndroidRuntime(17677): FATAL EXCEPTION: main
E/AndroidRuntime(17677): android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
E/AndroidRuntime(17677):    at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:4039)
E/AndroidRuntime(17677):    at android.view.ViewRootImpl.invalidateChild(ViewRootImpl.java:722)
E/AndroidRuntime(17677):    at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:771)
E/AndroidRuntime(17677):    at android.view.ViewGroup.invalidateChild(ViewGroup.java:4005)
E/AndroidRuntime(17677):    at android.view.View.invalidate(View.java:8576)
E/AndroidRuntime(17677):    at android.view.View.invalidate(View.java:8527)
E/AndroidRuntime(17677):    at android.widget.TextView.checkForRelayout(TextView.java:6760)
E/AndroidRuntime(17677):    at android.widget.TextView.setText(TextView.java:3306)
E/AndroidRuntime(17677):    at android.widget.TextView.setText(TextView.java:3162)
E/AndroidRuntime(17677):    at android.widget.TextView.setText(TextView.java:3137)
E/AndroidRuntime(17677):    at com.android.internal.app.AlertController.setMessage(AlertController.java:261)
E/AndroidRuntime(17677):    at android.app.AlertDialog.setMessage(AlertDialog.java:185)
E/AndroidRuntime(17677):    at android.app.ProgressDialog.setMessage(ProgressDialog.java:314)
----------------------------------
E/AndroidRuntime(17677):    at com.regaliz.libneo.NativeStory.setThrobber_internal(NativeStory.java:269)
E/AndroidRuntime(17677):    at com.regaliz.libneo.NativeStory$AppHelper$8.run(NativeStory.java:865)
----------------------------------
E/AndroidRuntime(17677):    at android.os.Handler.handleCallback(Handler.java:605)
E/AndroidRuntime(17677):    at android.os.Handler.dispatchMessage(Handler.java:92)
E/AndroidRuntime(17677):    at android.os.Looper.loop(Looper.java:137)

Request for additional code:

  • The AppHelper class is instantiated inside the main activity, and is passed to other child classes in the activity, that keep it with a WeakReference (checked this is not the problem)

  • The classes that use the AppHelper that fails do:

public void story_loadfonts(String jsonFonts) {

    final AppHelper appHelper=mWeakAppHelper.get(); // apphelper stored in a weak ref

    try {
        .
        .
        .
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for (int i=0; i<NUMFONTS; i++) {
                        load_font_from_network(i);
                        appHelper.setThrobber("LOADING FONT "+i+"/"+NUMFONTS);
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    } catch (Exception e) {
        return;
    }
}
rupps
  • 9,712
  • 4
  • 55
  • 95
  • code looks good to me. Maybe a bit more of code caould be helpful – Blackbelt May 08 '13 at 15:07
  • Yep it also looks good to me! Thing is, there's not much more related code to the issue. If I just switch runUiThread it works flawlessly, but I don't know the difference!! – rupps May 08 '13 at 15:08
  • Please share the code that instantiates the `AppHelper` and calls the method. – tbkn23 May 08 '13 at 15:21
  • Hi tbkn! I am posting that code, however why would it be related? Isn't the root of the problem starting at RunOnUiThread? – rupps May 08 '13 at 15:24
  • Out of interest, which is line 269 of NativeStory.java? – Neil Townsend May 08 '13 at 15:45
  • It's the one that touches progressdialog ! (you can see it in the first code chunk, the only line in setThrobber_internal) : progressDialog.setMessage(newMessage) – rupps May 08 '13 at 15:46

1 Answers1

1

Looking at the ticked answer from Android: Accessing UI Element from timer thread, I wonder if the issue is to do with where the runOnUiThread call is created.

According to the android developer pages, runOnUiThread:

Runs the specified action on the UI thread. If the current thread is the UI thread, then the action is executed immediately. If the current thread is not the UI thread, the action is posted to the event queue of the UI thread.

Note from Android Java runOnUiThread() that calling post on the activity's handler is essentially the same as calling runOnUithread.

So, the question is how the Handler associate with the first case is different from the Handler in the second case.

In the second case, I suspect that you are guaranteed to post to the handler associated with the main activity.

In the second case, it is, I infer, the handler associated with the thread that created the AppHelper, which should be the same, from what you say.

EDIT: Based on further interaction with the question author, the key is as follows:

It would seems that onPause effectively stops the activities UI thread dead, and onResume creates a fresh one. So the handler by which we can access it is also renewed by this process.

The (final, weak) appHolder instantiation is associated with the old UI thread handler, and so if the runOnUiThread is executed inside it, refers to the old holder (via runOnUiThread). This is is the situation for the first case.

However, in the second case, the call to runOnUiThread is no longer executed in that code (called into being pre-onPause), but rather in a method inside the main activity which will have updated the handler runOnUiThread calls.

In short: make sure that calls to runOnUiThread (and indeed handler.post()) are always done in a way that guarantees they are using the most recent live version of the activity (and it's UI thread), and aren't linking to previous versions.

Community
  • 1
  • 1
Neil Townsend
  • 6,024
  • 5
  • 35
  • 52
  • Hi Neil! Very interesting pointer! (+1) AppHelper is indeed created on the Oncreate, but I have to check a couple things related to a Handler I use later on... I am pretty sure it is on the UI thread because I use it on a million places across the app to perform UI work without problem... But I have a doubt about your answer: What's the relation between runOnUiThread and a Handler? Isn't runOnUiThread's handler an internal thing? – rupps May 08 '13 at 16:02
  • BTW-I suspect this is related to the Synthesized methods to access parent methods inside the inner class, but why they execute outside of the runUiThread() is something I dont understand... – rupps May 08 '13 at 16:24
  • Thanks for the docs on RunOnUiThread, but the funny thing is I don't have more than one activity, and AppHelper is supposed to exists only once.That's why this issue is X-files for me. I am more and more convinced it is related to how inner classes access their parent, via synthesized static getters & setters, because besides that, I can't see any other difference. If there was such a problem with the Activity's main handler, I guess it would manifest in other places, the app downloads hundreds of assets, creates views dynamically, shares a handler that uses heavily without an issue ... – rupps May 08 '13 at 18:48
  • It is very odd! I wondered whether the use of a weak reference for appHandler might mean that it had changed by the time it was used. Does it happen after and onPause/onResume only or before? – Neil Townsend May 08 '13 at 19:24
  • Neil! I finally discovered what was going on. You pointed me in the direction of a possible activity change. Thing was, after creating apphelper, I initialized a Voice Synth library, also in oncreate, that involved an external intent to check Voice was installed, so the activity entered onPause for milliseconds then onResume. Although theres no OnDestroy/OnCreate involved, this was affecting the runOnUiThread somehow, maybe trying to execute it while the app was asleep, or something... I moved the Voice Synth routine to other place and things work as expcted. Thanks for your effort :-) – rupps May 08 '13 at 21:21
  • Pleasure - I'll update the answer to clarify this in case it's of help to anyone else! – Neil Townsend May 09 '13 at 08:02