5

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.

mmrn
  • 249
  • 2
  • 8
  • 18
  • Did you tried removing the @Override? Some times I remove it and issue is solved. – Rotary Heart Aug 28 '13 at 02:15
  • why do you run a thread inside another thread!!! async task is managed thread,and it's not blocking code. remove runOnUiThread block and just execute your async task. – moh.sukhni Aug 28 '13 at 02:42
  • what is this `doSomethingInBackground()` and where is `doInBackground()`? also post the code which you are invoking inside `doInBackground()` – Muhammad Babar Aug 28 '13 at 05:23
  • @MuhammadBabar, I'm assuming that method was *pseudo-code*, and not exactly what they're using. Clearly, that code wouldn't even compile, as there is no `return` statement in the method (that returns `String`), and `doInBackground()` is abstract. – Nate Aug 28 '13 at 07:18
  • @Moh.Sukhni I think it is strange too, MyAsyncTask should work, but since it doesn't, I'm trying some other tricks like using runOnUiThread. – mmrn Aug 28 '13 at 14:34
  • @MuhammadBabar The method name was wrong, and I edited it. doInBackground calls a method to get a xml file via httpClient. The method has about 50 lines of code and a bit too specific to post here. – mmrn Aug 28 '13 at 14:41
  • @Nate Yes you are right, the code above is not an actual code. doInBackground() method returns a string. – mmrn Aug 28 '13 at 14:43
  • @RotaryHeart I removed "@Override" but the result was the same. – mmrn Aug 28 '13 at 15:29
  • please post the code of `doInBackground()`? – Muhammad Babar Aug 29 '13 at 05:41

3 Answers3

7

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");
Community
  • 1
  • 1
Nate
  • 31,017
  • 13
  • 83
  • 207
  • According to your detailed suggestion, I'm investigating the code all over again. And yes, it seems the problem occurs only on devices before Jelly Bean. So far, I've confirmed that ProgressBar and AsyncTask are created inside UI thread, but the problem still occurs, so I'm looking for other traps. I will report more information later. – mmrn Aug 29 '13 at 02:24
  • About a Handler, I'm not using it for now. I did try to resolve the issue by using a Handler, but it didn't fix the problem and I removed it. In the original question, I just mistakenly used the word "Handler". For doInBackground() method, your assumption is right. – mmrn Aug 29 '13 at 02:30
  • 1
    @user2484173, ok, if you're saying that the problem only occurs on older devices, I'm wondering if the *class loading* is occuring on the wrong thread (essentially rendering the `AsyncTask` class useless). Do you have any code that runs on background threads, or in a `Service`, that might be using `AsyncTask`? [See this discussion](http://code.google.com/p/android/issues/detail?id=20915), and you might try the simple, one-line workaround I recommend [in this answer](http://stackoverflow.com/a/18501792/119114). – Nate Aug 29 '13 at 04:10
  • Thanks to your postings, adding Class.forName("android.os.AsyncTask"); in onCreate() resolved the problem. So the answer is that AsyncTask class was being loaded in some wrong thread somehow. I don't see a complete story yet, but now my app works okay. – mmrn Aug 29 '13 at 08:14
  • @user2484173, that's great news. This certainly seems to me like a weakness in the original `AsyncTask` implementation. It appears that someone at Google agrees, and they fixed it in Jelly Bean. – Nate Aug 29 '13 at 08:16
1

Make sure you are invoking aysnctask.execute() from the main thread only.

siva
  • 1,850
  • 13
  • 14
0

Write a handler in UI thread and call the handler from onPostExecute. It will solve the problem.

Something like this. Have a handler in UI thread (main thread):

handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        //run on UI Thread
     }
}; 

and call in onPostExecute() like this:

 handler.sendEmptyMessage(MSG);
Sushil
  • 8,250
  • 3
  • 39
  • 71
  • 1
    `onPostExecute()` should already be run on the main thread, so if this solution does actually fix the problem, then something else is wrong. `AsyncTask` should remove the need to use `Handler` objects. – Nate Aug 28 '13 at 04:24
  • AsyncTask is nothing but thread maintained by the system. So, on some phones may be their can be some synchronization or timing bugs coz of alteration or customization of framework code. Some OEMs do even change the threading logic in framework for more efficiency. So this can be side effect of it. Better you use handlers whihc will guarentee that things will work right :) – Sushil Aug 28 '13 at 04:49
  • 1
    I totally disagree. Do you have a reference supporting your claim that OEMs have modified AsyncTask to run `onPostExecute()` on a non-main thread (if the task was started on the main thread)? That would be a very large bug. – Nate Aug 28 '13 at 04:56
  • @Nate.. I said it can be a bug or side effect. I didnt say they want to run onPostExecute() on a non-main thread. And I cannot give you reference but lot of customizations are done. I am a framework engineer and have worked for samsung for long time. – Sushil Aug 28 '13 at 05:09
  • 1
    If it's a bug, then `Handler` might not work correctly, either (*Ahhh!*). In any case, when there's an issue like this, I'm about 100x more likely to suspect that it's a bug in the code of a new application programmer, rather than a bug in the vendor's port of Android. – Nate Aug 28 '13 at 05:23