1

My android app uses a PhoneGap-based Activity for login. But after the user login, in the next activity, I run a task in the background. But when the login success method is trying to edit a view (after the PhoneGap activity is closed), it always throws

android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

My code is:

// BeginActivity extends DroidGap
public class BeginLoginActivity extends BeginActivity 
{
    @Override
    protected String getFirstUrl() {
        Intent intent = getIntent();
        String pageState = intent.getStringExtra(SplashActivity.EXTRA_PAGE_STATE);

        if (pageState == null) {
            pageState = "login";
        }

        return "file:///android_asset/www/login.html#" +pageState;
    }

    //invoked at login.html via javascript
    public void gotoMain() {
        Intent intent = new Intent(this, MainNative.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
        finish();
    }
}

In MainNative class, I code a task that runs in the background

public class MainNative extends Activity {
    ...
    public void loadItems() {
        ...
        new WatchlistHelper (this).execute(getItemId());
    }

    public void setWatchedStatus(Boolean _true) {
        // this is where the WebViewCoreThread is thrown, 
        // whenever a phonegap based Activity previously has been opened
        // and it run well if there's no phonegap Activity has run
        watchlistButton_.setImageResource(_true ? R.drawable.native_rating_important
                : R.drawable.native_rating_not_important);
        watchlistButton_.setTag(_true);
        watchlistButton_.setVisibility(VISIBLE);
        watchlistLoading_.setVisibility(GONE);
    }
    ...
}

WatchlistHelper :

public static class WatchlistHelper extends AsyncTask<String, Void, Boolean> 
{
    private MainNative mContext_;

    ...

    @Override
    protected Boolean doInBackground(String... _ids) {
        // My code that run in background
        return isTrue;
    }

    @Override
    protected void onPostExecute(Boolean _isTrue) {
        mContext_.setWatchedStatus(_isTrue);
    }
}

The logs from logcat:

06-21 14:45:57.462: E/AndroidRuntime(265): FATAL EXCEPTION: WebViewCoreThread
06-21 14:45:57.462: E/AndroidRuntime(265): android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
06-21 14:45:57.462: E/AndroidRuntime(265):  at android.view.ViewRoot.checkThread(ViewRoot.java:2802)
06-21 14:45:57.462: E/AndroidRuntime(265):  at android.view.ViewRoot.requestLayout(ViewRoot.java:594)
06-21 14:45:57.462: E/AndroidRuntime(265):  at android.view.View.requestLayout(View.java:8125)
06-21 14:45:57.462: E/AndroidRuntime(265):  at android.view.View.requestLayout(View.java:8125)
06-21 14:45:57.462: E/AndroidRuntime(265):  at android.view.View.requestLayout(View.java:8125)
06-21 14:45:57.462: E/AndroidRuntime(265):  at android.widget.RelativeLayout.requestLayout(RelativeLayout.java:254)
06-21 14:45:57.462: E/AndroidRuntime(265):  at android.view.View.requestLayout(View.java:8125)
06-21 14:45:57.462: E/AndroidRuntime(265):  at android.view.View.requestLayout(View.java:8125)
06-21 14:45:57.462: E/AndroidRuntime(265):  at android.view.View.requestLayout(View.java:8125)
06-21 14:45:57.462: E/AndroidRuntime(265):  at android.widget.ScrollView.requestLayout(ScrollView.java:1200)
06-21 14:45:57.462: E/AndroidRuntime(265):  at android.view.View.requestLayout(View.java:8125)
06-21 14:45:57.462: E/AndroidRuntime(265):  at android.widget.RelativeLayout.requestLayout(RelativeLayout.java:254)
06-21 14:45:57.462: E/AndroidRuntime(265):  at android.view.View.requestLayout(View.java:8125)
06-21 14:45:57.462: E/AndroidRuntime(265):  at android.view.View.requestLayout(View.java:8125)
06-21 14:45:57.462: E/AndroidRuntime(265):  at android.widget.RelativeLayout.requestLayout(RelativeLayout.java:254)
06-21 14:45:57.462: E/AndroidRuntime(265):  at android.view.View.requestLayout(View.java:8125)
06-21 14:45:57.462: E/AndroidRuntime(265):  at android.view.View.requestLayout(View.java:8125)
06-21 14:45:57.462: E/AndroidRuntime(265):  at android.widget.ImageView.setImageResource(ImageView.java:275)
06-21 14:45:57.462: E/AndroidRuntime(265):  at com.posaurus.android.elements.MainNative.setWatchedStatus(MainNative.java:160)
06-21 14:45:57.462: E/AndroidRuntime(265):  at com.posaurus.android.elements.MainNative$WatchlistHelper.onPostExecute(MainNative.java:205)
06-21 14:45:57.462: E/AndroidRuntime(265):  at com.posaurus.android.elements.MainNative$WatchlistHelper.onPostExecute(MainNative.java:1)
06-21 14:45:57.462: E/AndroidRuntime(265):  at android.os.AsyncTask.finish(AsyncTask.java:417)
06-21 14:45:57.462: E/AndroidRuntime(265):  at android.os.AsyncTask.access$300(AsyncTask.java:127)
06-21 14:45:57.462: E/AndroidRuntime(265):  at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:429)
06-21 14:45:57.462: E/AndroidRuntime(265):  at android.os.Handler.dispatchMessage(Handler.java:99)
06-21 14:45:57.462: E/AndroidRuntime(265):  at android.os.Looper.loop(Looper.java:123)
06-21 14:45:57.462: E/AndroidRuntime(265):  at android.webkit.WebViewCore$WebCoreThread.run(WebViewCore.java:621)
06-21 14:45:57.462: E/AndroidRuntime(265):  at java.lang.Thread.run(Thread.java:1096)
Nate
  • 31,017
  • 13
  • 83
  • 207
Morilla Thaisa
  • 121
  • 1
  • 1
  • 10

1 Answers1

3

See this PhoneGap document here:

Threading

JavaScript in the WebView does not run on the UI thread. It runs on the WebCore thread. The execute method also runs on the WebCore thread.

The problem is that gotoMain() is being run on the WebCore thread, which means you're trying to start the MainNative activity on the WebCore thread, which is incorrect.

You should be able to fix this by modifying your gotoMain() method to make sure it does its work on the UI thread:

public void gotoMain() {
    // run code on the UI thread:
    runOnUiThread(new Runnable() {
        public void run() {
            Intent intent = new Intent(BeginLoginActivity.this, MainNative.class);
            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(intent);
            finish();
        }
    });
}

Edit: it's also possible that you are running into this problem. In older versions of Android (before Jelly Bean), it's necessary to make sure that the AsyncTask class is loaded on the main thread. With the way your PhoneGap app is setup, it's possible that the first time your code references AsyncTask (forcing the class to be loaded) is on a background thread. If this happens, the AsyncTask class will essentially be unusable. If you look at the answer I link to, there is a one line piece of code that you can put anywhere in your app that (1) runs early, and (2) you know runs on the main thread ... placing this code on the main thread will then force class loading on the correct thread, and fix the problem.

Class.forName("android.os.AsyncTask");
Community
  • 1
  • 1
Nate
  • 31,017
  • 13
  • 83
  • 207
  • Obviously it's not "the login success method is trying to edit a view". After `MainNative` Activity is open, it loads a data, then after a successfull request, it inflates a layout where `watchlistButton_` is in, (up to this step, modifying views is ok) then it runs a background task and its callback calls `setWatchedStatus(Boolean _true)` to set the `watchlistButton_` image. – Morilla Thaisa Jun 24 '13 at 05:49
  • @MorillaThaisa, I can't quite understand what you're trying to say. You're either calling `BeginLoginActivity#gotoMain()` on the WebCore thread, or calling `MainNative#loadItems()` on the WebCore thread. Either of those would be wrong. I can't tell you much more, because you don't show the code that calls `gotoMain()` or `loadItems()`. You also don't show us the code where you say you inflate the layout with `watchListButton_`. – Nate Jun 24 '13 at 09:47
  • Here's how it's going. First, `BeginLoginActivity#gotoMain()` is called on WebCore thread. Then, `MainNative` activity is open, where on its `onCreate` method it inflates the view. After the view is ready, `MainNative#loadItems()` is called and running a task `WatchlistHelper` in background. On `WatchlistHelper#onPostExecute`, it calls `MainNative#setWatchedStatus(boolean)` to change/edit the view, this is where the WebViewCoreThread thrown. Sorry for my bad english. But, thx for the answer anyway. I ended up code `runOnUiThread` on every code that touches views – Morilla Thaisa Jun 24 '13 at 11:29
  • @MorillaThaisa, as I said in my answer, starting your `MainNative` activity from `gotoMain()`, on the WebCore thread, is wrong. See the link in my answer. You don't need to use `runOnUiThread()` **everywhere** that touches views. You just need to make sure that where you use javascript to call Java, which you said was `gotoMain()`, you use `runOnUiThread()` there (see answer). You should also remember to **accept** and **upvote** answers that help you. That's how you say "thank you" on stack overflow, and you'll find that it makes it easier to get answers from other people in the future. – Nate Jun 24 '13 at 20:59
  • I've tried the code on your answer, the error still the same. I only **upvote** it. I've tried too stopping the thread, doesn't work. – Morilla Thaisa Jun 25 '13 at 04:42
  • What thread are you talking about? You don't show any thread in your code. Are you talking about the `AsyncTask`? What do you mean by "stopping"? – Nate Jun 25 '13 at 04:51
  • In `gotoMain()`, I called `getThreadPool().shutdown();`. I just tried it, so I didn't write on my question. – Morilla Thaisa Jun 25 '13 at 05:15
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/32307/discussion-between-morilla-thaisa-and-nate) – Morilla Thaisa Jun 25 '13 at 05:35
  • Why do you want to call `getThreadPool().shutdown()`? Again, I've tried to explain what the problem is. Either `gotoMain()` or `loadItems()` is being called on the WebCore thread, without a call to `runOnUiThread()` inside of it. – Nate Jun 25 '13 at 09:25