Вопрос

I have already read all questions about this. And it's not the same case. Believe me.

I know an AsyncTask doInBackground function has his own thread, and you must touch the view in onPreExecute and onPostExecute.

Besides, I'm using isCancelled and isFinishing to really be sure the task is alive. And with all these things, the app is still crashing.

This is the code:

public class MainActivity  {

    private Activity activity;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.Main);
        activity = this;
        new MyTask(activity).execute();
    }


    private class MyTask extends AsyncTask<Void, Void, User[]> {

            // ...

            @Override
            protected void onPreExecute() {
            }

            @Override
            protected User[] doInBackground(String... params) {
                    // Api call
                    // return results
            }

            @Override
            protected void onPostExecute(User[] users) {

                    if (isCancelled() || isFinishing()) return;

                    findViewById(R.id.panelViews).removeAllViews();
                    findViewById(R.id.element).setText("something");

                    // ... more

            }
    }

}

As you can see:

  • the touch of the view is in the onPostExecute
  • I use the "isCancelled" in case the user cancels the task exiting the screen
  • The same with "isFinishing"

And even now I'm getting the error "Only the original thread that created a view hierarchy can touch its views" in the line when I try to change something of the view (in the example a simple text)

If I cancel (pressing back for example) when it's trying to get the API results is OK, and it works. If I cancel exactly when the API results come, and before it starts changing things, it crashes (I inserted a "sleep" in the onPostExecute to accomplish this test).

The error code is in line 112, that is the "findViewById(R.id.panelViews).removeAllViews();" described above:

android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
    at android.view.ViewRoot.checkThread(ViewRoot.java:2988)
    at android.view.ViewRoot.requestLayout(ViewRoot.java:648)
    at android.view.View.requestLayout(View.java:8416)
    at android.view.View.requestLayout(View.java:8416)
    at android.view.View.requestLayout(View.java:8416)
    at android.view.View.requestLayout(View.java:8416)
    at android.view.View.requestLayout(View.java:8416)
    at android.widget.ScrollView.requestLayout(ScrollView.java:1303)
    at android.view.View.requestLayout(View.java:8416)
    at android.view.View.requestLayout(View.java:8416)
    at android.view.ViewGroup.removeAllViews(ViewGroup.java:2354)
    at com.test.activities.MainActivity$MyTask.onPostExecute(MainActivity.java:112)
    at com.test.activities.MainActivity$MyTask.onPostExecute(MainActivity.java:1)
    at android.os.AsyncTask.finish(AsyncTask.java:417)
    at android.os.AsyncTask.access$300(AsyncTask.java:127)
    at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:429)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:130)
    at android.os.HandlerThread.run(HandlerThread.java:60)
Это было полезно?

Решение

Looking at the call stack, following call indicates that AsyncTask class was loaded on non-UI thread.

at android.os.HandlerThread.run(HandlerThread.java:60)

One of the Threading Rules mentioned in AsyncTask reference documentation is:

The AsyncTask class must be loaded on the UI thread. This is done automatically as of JELLY_BEAN.

Here is a bug related to this issue.

One of the solution is, load the AsyncTask class manually in Application onCreate method. This ensures the class gets loaded on main thread, before any application components uses it.

public class MyApplication extends Application {

    @Override
    public void onCreate() {

        try {
            Class.forName("android.os.AsyncTask");
        } catch (ClassNotFoundException e) {

        }

        super.onCreate();
    }
}

Remember to specify MyApplication in AndroidManifest file.

Другие советы

The solution in this answer should force the code to be run on the ui thread. It uses the Activity#runOnUiThread(Runnable) function. Your code would end up like this:

@Override
protected void onPostExecute(User[] users) {
    if (isCancelled() || isFinishing()) return;

    runOnUiThread(new Runnable() {

        @Override
        public void run() {
            findViewById(R.id.panelViews).removeAllViews();
            findViewById(R.id.element).setText("something");
            // ... more
        }
    });
}

This occurs when you start an AsyncTask from a background thread. The onPreExecute and onPostExecute don't automatically run on the main thread but in reality the thread the AsyncTask was started from.

In other words, if you were already within a background thread when you called execute on the AsyncTask then that same thread is where the onPostExecute method is called from meaning you would get that exception.

Try this, This should solve your problem, perform your task in runOnUiThread()

@Override
protected void onPostExecute(User[] users) {

    runOnUiThread(new Runnable() {
        public void run() {

            if (isCancelled() || isFinishing()) return;

            findViewById(R.id.panelViews).removeAllViews();
            findViewById(R.id.element).setText("something");                 

        }//run
    }//runOnUiThread
}//onPostExecute
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top