62

While working with retain Fragments in Android to hold an AsyncTask during configuration changes, which i guess it's the best approach, some doubts appear in my mind about UI Thread's Message Queue invocation order.

Ex: Imagine this scenario:

  1. Configuration Change occurs, user rotates the device. AsyncTask is running.
  2. Fragment onDetach() is called
  3. AsyncTask doInBackground() method finishes
  4. AsyncTask onPostExecute()is called
  5. Fragment onAttach() is called

So can UI Thread Message Queue be like this:

Queue top -> onDetach() | onPostExecute() | onAttach()

I know it cannot, the call to onPostExecute() will wait until the configuration change completes, as far as i know, but how does that work ? Are the calls from Activities, Fragments life-cycles executed consecutively ?

Marian Paździoch
  • 8,813
  • 10
  • 58
  • 103
Sergio Serra
  • 1,479
  • 3
  • 17
  • 25
  • 1
    Yes, since `onPostExecute()` runs un the UI thread, it would be executed consecutively with the UI and configuration changes, which also run on the UI thread. My assumption is that `onDetach()` and `onAttach()` run somewhat atomically, as the ActivityManager probably doesn't yield to other threads during configuration changes, but that's something you'd have to dig into Android source to confirm. – 323go Nov 13 '13 at 21:04
  • yeah looking in android source code is probably the best way to find out, how it works. – Sergio Serra Nov 13 '13 at 21:08
  • 3
    @323go To clarify further, Android never has to "yield to other threads" in this case. Atomicity is guaranteed by the single-threaded nature of the Android UI toolkit (i.e. events are executed sequentially in a single, centralized message queue). – Alex Lockwood Dec 25 '13 at 19:23

2 Answers2

119

It is not possible for onPostExecute() to be called in between Fragment#onDetach() and Fragment#onAttach() during a configuration change. The reasoning behind this claim is threefold:

  1. Configuration changes are handled inside a single message in the main thread's message queue.

  2. As soon as the doInBackground() method returns, the AsyncTask schedules the onPostExecute() method to be invoked on the main thread by posting a message to the main thread's message queue.

  3. The configuration change's message will contain the code that will invoke the Activity and Fragment lifecycle methods (such as onDetach() and onAttach()). The AsyncTask's message will contain the code that will invoke the onPostExecute() method. Since the main thread processes messages in its message queue sequentially, it is impossible for the two messages to be executed at the same time, and therefore onPostExecute() can never be invoked in between the calls to onDetach() and onAttach().

Read my response to Doug Stevenson in this thread for a more detailed explanation (including links to the source code that prove the claim).

Alex Lockwood
  • 83,063
  • 39
  • 206
  • 250
  • 1
    during a configuration change, is it guaranteed that `onAttach` is posted to the message queue right after `onDetach`? – David T. Mar 18 '14 at 18:53
  • 5
    @DavidT. Both `onDetach` and `onAttach` are invoked inside the same message by the main UI thread. – Alex Lockwood Mar 19 '14 at 00:37
  • 3
    Isn't this just a current implementation detail and thus we'd be relying on white box behavior? E.g. couldn't this behavior change in the future? – greg7gkb Aug 10 '15 at 15:57
  • @greg7gkb You might be right about that... I feel like I remember reading that this was intentional behavior that won't break in the future from some of the Android guys, but I forget where this is documented. – Alex Lockwood Sep 05 '15 at 14:27
  • I see some flaws in your proposed solution: https://stackoverflow.com/questions/53603796/ui-updates-across-config-changes-the-weird-point – Manish Kumar Sharma Dec 04 '18 at 00:26
  • Google+ RIP, is there mirror for "response to Doug Stevenson" link? – CeH9 Oct 07 '19 at 22:45
0

I wrote a simple test to see the lifecycle about the AsyncTask in a retained Fragment. It can confirm that @Alex Lockwood's answer is true. So it's safe to say the AsyncTask in a retained Fragment is the best practice. And Google should put this approach into their official documents.

public class RecordDataFragment extends Fragment {
    public static boolean detach = false;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        Timber.d("retain, onAttach");
        detach = false;
    }

    @Override
    public void onDetach() {
        super.onDetach();
        Timber.d("retain, onDetach");
        detach = true;
    }

    public static class TestTask extends AsyncTask<String, Void, Void> {

        protected Void doInBackground(String... username) {

            Timber.d("retain, looping.");

            while(!detach){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            Timber.d("retain, exit looping.");

            return null;
        }

        protected void onPostExecute(Void nothing) {
            Timber.d("retain, onPostExecute");
        }
    }
}

public class RecordFragment extends Fragment {

    static boolean called = false;

    @Override
    public void onResume() {
        super.onResume();
        Timber.d("retain, onResume");

        if(!called) {
            new RecordDataFragment.TestTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
            called = true;
        }

    }
}

2019-11-22 12:28:55.503 D/RecordDataFragment: retain, onAttach
2019-11-22 12:32:00.263 D/RecordFragment: retain, onViewStateRestored
2019-11-22 12:32:03.538 D/RecordFragment: retain, onResume
2019-11-22 12:32:03.544 D/RecordDataFragment$TestTask: retain, looping.
2019-11-22 12:32:07.273 D/RecordDataFragment: retain, onDetach
2019-11-22 12:32:07.297 D/RecordDataFragment$TestTask: retain, exit looping.
2019-11-22 12:32:07.403 D/RecordFragment: retain, onDestroy
2019-11-22 12:32:07.566 D/RecordDataFragment: retain, onAttach
2019-11-22 12:32:08.621 D/RecordFragment: retain, onViewStateRestored
2019-11-22 12:32:08.870 D/RecordFragment: retain, onResume
2019-11-22 12:32:09.663 D/RecordDataFragment$TestTask: retain, onPostExecute
Kimi Chiu
  • 2,103
  • 3
  • 22
  • 37