Android UI Thread Message Queue dispatch order
AndroidAndroid AsynctaskAndroid Problem Overview
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:
- Configuration Change occurs, user rotates the device. AsyncTask is running.
- Fragment
onDetach()
is called - AsyncTask
doInBackground()
method finishes - AsyncTask
onPostExecute()
is called - 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 ?
Android Solutions
Solution 1 - Android
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:
-
Configuration changes are handled inside a single message in the main thread's message queue.
-
As soon as the
doInBackground()
method returns, theAsyncTask
schedules theonPostExecute()
method to be invoked on the main thread by posting a message to the main thread's message queue. -
The configuration change's message will contain the code that will invoke the
Activity
andFragment
lifecycle methods (such asonDetach()
andonAttach()
). TheAsyncTask
's message will contain the code that will invoke theonPostExecute()
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 thereforeonPostExecute()
can never be invoked in between the calls toonDetach()
andonAttach()
.
Read my response to Doug Stevenson in this thread for a more detailed explanation (including links to the source code that prove the claim).
Solution 2 - Android
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