Android UI线程消息队列分发顺序

62

在Android中,使用保留的Fragment来保存AsyncTask以处理配置更改是最佳方法之一。然而,在这个过程中,我对UI线程消息队列调用顺序产生了疑问。

例如:

  1. 发生配置更改,用户旋转设备。AsyncTask正在运行。
  2. 调用片段onDetach()
  3. AsyncTask的doInBackground()方法完成。
  4. 调用AsyncTask的onPostExecute()
  5. 调用片段onAttach()

那么,UI线程消息队列可能会像这样吗:

队列顶部-> onDetach() | onPostExecute() | onAttach()

我知道这是不可能的,我所知道的是,对onPostExecute()的调用将等待配置更改完成,但是它是如何工作的?Activity和Fragment生命周期中的调用是否按顺序执行?


1
是的,由于onPostExecute()在UI线程上运行,因此它将与UI和配置更改连续执行,这些更改也在UI线程上运行。我的假设是onDetach()onAttach()会以某种原子方式运行,因为ActivityManager在配置更改期间可能不会让其他线程让步,但这是您必须深入挖掘Android源代码才能确认的事情。 - 323go
是的,在Android源代码中查找可能是了解它如何工作的最佳方法。 - Sergio Serra
3
为进一步澄清,Android在这种情况下从未需要“让出给其他线程”。原因是Android UI工具包的单线程特性保证了原子性(即事件在一个集中的消息队列中按顺序执行)。 - Alex Lockwood
2个回答

119
在配置更改期间,onPostExecute()不可能在Fragment#onDetach()Fragment#onAttach()之间被调用。这个声明的原因有三个:
  1. 配置更改在主线程的消息队列中被处理。
  2. doInBackground()方法返回后,AsyncTask通过向主线程的消息队列发布一条消息,安排调用onPostExecute()方法在主线程上执行。
  3. 配置更改的消息将包含将调用ActivityFragment生命周期方法(如onDetach()onAttach())的代码。AsyncTask的消息将包含调用onPostExecute()方法的代码。由于主线程按顺序处理消息队列中的消息,因此不可能同时执行这两条消息,因此onPostExecute()永远不可能在调用onDetach()onAttach()之间被调用。
阅读我在这个线程中对Doug Stevenson的回应,以获得更详细的解释(包括证明该声明的源代码链接)。

1
在配置更改期间,是否保证onDetach之后立即将onAttach发布到消息队列中? - David T.
5
@DavidT. onDetachonAttach都在主UI线程的同一个消息中被调用。 - Alex Lockwood
3
这难道不只是当前的实现细节吗,因此我们将依赖于白盒行为?例如,这种行为在未来可能会改变吗? - greg7gkb
@greg7gkb 你可能是对的... 我记得有些 Android 开发人员说过这是故意的行为,并且不会在未来出现问题,但我忘记了这在哪里记录。 - Alex Lockwood
我看到你提出的解决方案中存在一些缺陷:https://stackoverflow.com/questions/53603796/ui-updates-across-config-changes-the-weird-point - Manish Kumar Sharma
Google+已逝,是否有“回应Doug Stevenson”的链接镜像? - CeH9

0
我编写了一个简单的测试来查看在保留的Fragment中关于AsyncTask的生命周期。 它可以证实@Alex Lockwood的答案是正确的。 因此,可以说在保留的Fragment中使用AsyncTask是最佳实践。Google应该将这种方法纳入官方文档中。
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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接