Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android runOnUiThread/AsyncTask cannot resolve CalledFromWrongThreadException

I'm working for an Android app and implementing a ProgressBar by using AsyncTask class.

The problem is that on some devices, it causes "CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views." in onPostExecute. On those devices, the problem occurs 100%. On other devices, it works fine.

public final class MyAsyncTask extends AsyncTask<String, Integer, String>
{
    private ProgressBar progress;
    private ListActivity activity;

    public MyAsyncTask(ListActivity activity, ProgressBar progress)
    {
        this.progress = progress;
        this.activity = activity;
    }

    protected void onPreExecute()
    {
        this.progress.setVisibility(view.VISIBLE);
    }

    protected String doInBackground(String[] arg0)
    {
        // getting xml via httpClient
        return string;
    }

    protected void onPostExecute(String result)
    {
        this.progress.setVisibility(view.GONE);
    }

I don't understand why onPostExecute does not run on the UI thread, on those certain devices.

Next, I tried to call it with runOnUiThread, to make absolutely sure that it runs on the UI thread.

runOnUiThread(new Runnable() {
    @Override
    public void run() {
        ProgressBar progress = (ProgressBar)findViewById(R.id.some_view_progressbar);
        MyAsyncTask task = new MyAsyncTask(activity, progress);
        task.execute();
    }
} );

Even this did not solve the problem. The same exception still occurs.

From Log, I confirmed that Thread.currentThread().getId() is certainly different from the app's main activity's thread inside the handler.

I'm stuck. Any advice will be appreciated.

NOTE:I edited the sample code (not a real code) above to fix the wrong method name and missing "return string". I will add more information later.

like image 568
mmrn Avatar asked Aug 28 '13 02:08

mmrn


1 Answers

I don't see anything wrong with MyAsyncTask itself, but there are still other things that can go wrong.

Starting the AsyncTask

From the Android Docs

Threading rules

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

  • The AsyncTask class must be loaded on the UI thread. This is done automatically as of JELLY_BEAN.
  • 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.)

You don't show where you normally instantiate, and execute the task, so make sure that you do this in code that's already on the UI/main thread. Note that the first bullet point above might explain why this works for you on some devices, and not on others.

Creating the View Hierarchy

The message tells you

Only the original thread that created a view hierarchy can touch its views.

and you're assuming that this is because your async task is (strangely) trying to modify the UI on a background thread. However, it is possible that you get this error because the async task modifies the UI on the main thread, but the UI (ProgressBar) was not created correctly in the first place.

See this question for an example of how you can erroneously create the view on the wrong thread (anything other than the main thread), and get this same error.

More

I would, however, like to see exactly where you are logging the thread ID, and what value(s) you're getting. If you check out my first two suggestions, and they don't solve your problem, then we may need more information.

You also mention a Handler (?), but don't show how or where you use that. Normally, using AsyncTask removes the need to use Handler, so I'm a little worried about how you might be using that.

Update

Per the discussion in comments below, it looks like the issue here is the one discussed in this question. Some code, probably running on a background thread, is first to cause the AsyncTask class to be loaded. The original (pre-Jelly Bean) implementation of AsyncTask required class loading to occur on the main thread (as mentioned in the Threading Rules above). The simple workaround is to add code on the main thread (e.g. in Application#onCreate()) that forces early, deterministic class loading of AsyncTask:

Class.forName("android.os.AsyncTask");
like image 111
Nate Avatar answered Sep 27 '22 19:09

Nate