Android: got CalledFromWrongThreadException in onPostExecute() - How could it be?

后端 未结 6 729
你的背包
你的背包 2020-12-13 09:48

I have an app in production for a few weeks, using ACRA, and I had zero errors until one strange error reported today.

I\'ve got:

    android.view.Vi         


        
6条回答
  •  无人及你
    2020-12-13 10:44

    Had the same issue. Solved in my case

    Briefly explanation:

    1. Running AsynckTask for the very first time on non UI thread with looper leads to loading AsyncTask.class and initialization sHandler to handler constructed on that non UI looper.
    2. Now sHandler is connected to that non UI thread for ANY instance of AsyncTask subclasses and onPreExecute, onProgressUpdate and onPostExecute methods will be invoked on that non UI thread (unless AsyncTask.class will be unloaded)
    3. Any attempt to deal with UI inside any of the above methods will lead to crash with android.view.ViewRootImpl$CalledFromWrongThreadException
    4. To avoid such situation one should always run (at least for the very first time) AsyncTask on UI thread in order to let AsyncTask's sHandler-field be initialized with UI's looper

    The story:

    There were two production apps: A - main android app and B - some utilty app.

    After integration app B ito app A we received a lot of crashes:

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

    for method running from AsynckTask.onPostExecute()

    After some investigation it appeared that utility app B used AsyncTask within its HandlerThread

    The traces was found in AsyncTask's source code:

    private static final InternalHandler sHandler = new InternalHandler();
    

    This is the handler which is used to send onPostExecute() to UI thread.

    This handler is static and it will be initialized during class loading i.e. first new AsyncTask() appearance

    It means that onPostExecute will always be posted to that thread where new AsyncTask() was called for the first time (unless AsyncTask.class will be unloaded and loaded again)

    In my case the flow was something like this:

    1 - starting app A
    2 - initializing B form A
    3 - B creates its own HandlerThread and launches AsyncTask <- now onPostExecute wil be posted to this HandlerThread no matter where from an instance of AsyncTask will be launched in future
    4 - create AsyncTask in the app A for a long operation and update UI in its onPostExecute
    5 - when executing onPostExecute() the CalledFromWrongThreadException is thrown
    

    Then a friend of mine showed me related documentation from android.developers (Threading rules section):

    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.

    Hope it can help to make clear the situation)

提交回复
热议问题