Android 4.4.X:taskAffinity和launchmode与Activity生命周期的关系

10

我开发了一个简单的应用程序,在Android 4.4.X设备上表现出一些奇怪的行为。

假设我想要有两个“主”活动,其中第一个在每次恢复时通过启动“HelloActivity”每秒钟说“您好”,而第二个具有android:launchMode =“singleTask”android:taskAffinity =“.MyAffinity”定义。 第二个是由第一个启动的。

我的代码

清单文件非常简单:

<uses-sdk
    android:minSdkVersion="8"
    android:targetSdkVersion="14" />

<application
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >

    <activity
        android:name="com.example.affinitylaunchmodebugtest.MainActivity"
        android:configChanges="keyboardHidden|orientation|screenSize"
        android:windowSoftInputMode="adjustResize">
        <intent-filter>
            <category android:name="android.intent.category.DEFAULT" />

            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <activity
        android:label="HELLO"
        android:name="com.example.affinitylaunchmodebugtest.HelloActivity"
        android:configChanges="keyboardHidden|orientation|screenSize">
    </activity>

    <activity
        android:label="AffinityTestActivity"
        android:name="com.example.affinitylaunchmodebugtest.AffinityTestActivity"
        android:configChanges="keyboardHidden|orientation|screenSize"
        android:launchMode="singleTask"
        android:taskAffinity=".MyAffinity">
    </activity>
</application>

MainActivity 在按下按钮时启动 AffinityTestActivity 并记录其生命周期。每隔一秒,它还会在恢复时启动 HelloActivity:

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        System.out.println(this+" onCreate");
        super.onCreate(savedInstanceState);

        Button b = new Button(MainActivity.this);
        b.setText("START AFFINITY TEST ACTIVITY");
        b.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                System.out.println(this+" starts "+AffinityTestActivity.class.getSimpleName());
                Intent intent = new Intent(MainActivity.this, AffinityTestActivity.class);
                startActivity(intent);
            }
        });
        setContentView(b);
    }

    private boolean skipHello = true;

    @Override
    protected void onResume() {
        System.out.println(this+" onResume");
        super.onResume();

        if (!skipHello) {
            System.out.println(this+" starts "+HelloActivity.class.getSimpleName());
            Intent intent = new Intent(MainActivity.this, HelloActivity.class);
            startActivity(intent);
            skipHello = true;
        } else {
            skipHello = false;
        }
    }

    @Override
    protected void onPause() {
        System.out.println(this+" onPause");
        super.onPause();
    }

    @Override
    protected void onDestroy() {
        System.out.println(this+" onDestroy");
        super.onDestroy();
    }

}

AffinityTestActivity在按钮单击时调用finish()并记录其生命周期:

public class AffinityTestActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        System.out.println(this+" onCreate");
        super.onCreate(savedInstanceState);

        Button b = new Button(AffinityTestActivity.this);
        b.setText("FINISH");
        b.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                System.out.println(this+" finishes");
                finish();
            }
        });
        setContentView(b);
    }

    @Override
    protected void onResume() {
        System.out.println(this+" onResume");
        super.onResume();
    }

    @Override
    protected void onPause() {
        System.out.println(this+" onPause");
        super.onPause();
    }

    @Override
    protected void onDestroy() {
        System.out.println(this+" onDestroy");
        super.onDestroy();
    }
}

HelloActivity 和 AffinityTestActivity 实际上是相同的 - 它们只有一个调用 finish() 方法的按钮和打印日志以记录其生命周期。

测试场景

  1. 启动 MainActivity。
  2. 启动 AffinityTestActivity。
  3. 结束 AffinityTestActivity (当 AffinityTestActivity 结束时,MainActivity 被恢复并启动了 HelloActivity)。
  4. 分析输出结果。

日志

Android 4.4.2 和 4.4.3: (在 Nexus 7 II 和 Samsung Galaxy S5 上进行测试) 可以看到,日志以 HelloActivity 的 onPause 结尾,这是不合理的(在步骤3中,HelloActivity 显示在顶部)。此外,AffinityTestActivity 没有被销毁,而 MainActivity 没有暂停。

06-20 11:13:20.547: I/System.out(18650): com.example.affinitylaunchmodebugtest.MainActivity@41f17e50 onCreate
06-20 11:13:20.557: I/System.out(18650): com.example.affinitylaunchmodebugtest.MainActivity@41f17e50 onResume
06-20 11:13:25.371: I/System.out(18650): com.example.affinitylaunchmodebugtest.MainActivity$1@41f6e5c0 starts AffinityTestActivity
06-20 11:13:25.581: I/System.out(18650): com.example.affinitylaunchmodebugtest.MainActivity@41f17e50 onPause
06-20 11:13:25.591: I/System.out(18650): com.example.affinitylaunchmodebugtest.AffinityTestActivity@41f6a480 onCreate
06-20 11:13:25.611: I/System.out(18650): com.example.affinitylaunchmodebugtest.AffinityTestActivity@41f6a480 onResume
06-20 11:13:36.452: I/System.out(18650): com.example.affinitylaunchmodebugtest.AffinityTestActivity$1@41f1ede8 finishes
06-20 11:13:36.662: I/System.out(18650): com.example.affinitylaunchmodebugtest.AffinityTestActivity@41f6a480 onPause
06-20 11:13:36.682: I/System.out(18650): com.example.affinitylaunchmodebugtest.MainActivity@41f17e50 onResume
06-20 11:13:36.682: I/System.out(18650): com.example.affinitylaunchmodebugtest.MainActivity@41f17e50 starts HelloActivity
06-20 11:13:36.782: I/System.out(18650): com.example.affinitylaunchmodebugtest.HelloActivity@41f8dbb8 onCreate
06-20 11:13:36.802: I/System.out(18650): com.example.affinitylaunchmodebugtest.HelloActivity@41f8dbb8 onResume
06-20 11:13:36.852: I/System.out(18650): com.example.affinitylaunchmodebugtest.HelloActivity@41f8dbb8 onPause

旧版 Android(<4.4.2,测试过 2.3.5、4.1.2 和 4.2.1 设备以及 4.0.3 模拟器)表现如预期 - HelloActivity 在 onResume 后不会被暂停,AffinityTestActivity 被销毁:

06-20 11:16:30.867: I/System.out(3296): com.example.affinitylaunchmodebugtest.MainActivity@40f998b0 onCreate
06-20 11:16:30.907: I/System.out(3296): com.example.affinitylaunchmodebugtest.MainActivity@40f998b0 onResume
06-20 11:16:34.157: I/System.out(3296): com.example.affinitylaunchmodebugtest.MainActivity$1@40f9b350 starts AffinityTestActivity
06-20 11:16:34.277: I/System.out(3296): com.example.affinitylaunchmodebugtest.MainActivity@40f998b0 onPause
06-20 11:16:34.297: I/System.out(3296): com.example.affinitylaunchmodebugtest.AffinityTestActivity@40fab810 onCreate
06-20 11:16:34.357: I/System.out(3296): com.example.affinitylaunchmodebugtest.AffinityTestActivity@40fab810 onResume
06-20 11:16:38.687: I/System.out(3296): com.example.affinitylaunchmodebugtest.AffinityTestActivity$1@40fad640 finishes
06-20 11:16:38.707: I/System.out(3296): com.example.affinitylaunchmodebugtest.AffinityTestActivity@40fab810 onPause
06-20 11:16:38.717: I/System.out(3296): com.example.affinitylaunchmodebugtest.MainActivity@40f998b0 onResume
06-20 11:16:38.717: I/System.out(3296): com.example.affinitylaunchmodebugtest.MainActivity@40f998b0 starts HelloActivity
06-20 11:16:38.747: I/System.out(3296): com.example.affinitylaunchmodebugtest.MainActivity@40f998b0 onPause
06-20 11:16:38.777: I/System.out(3296): com.example.affinitylaunchmodebugtest.HelloActivity@40fbdd48 onCreate
06-20 11:16:38.827: I/System.out(3296): com.example.affinitylaunchmodebugtest.HelloActivity@40fbdd48 onResume
06-20 11:16:39.877: I/System.out(3296): com.example.affinitylaunchmodebugtest.AffinityTestActivity@40fab810 onDestroy

我的问题:

  • 为什么我的HelloActivity在Android 4.4.X设备上启动并显示在顶部后立即暂停?
  • 我该如何避免这种情况,强制应用程序具有“正常”的活动生命周期,就像老版本的Android(<4.4.2)一样?

我正在开发一个更复杂的应用程序,需要处理其活动的生命周期,而这种行为会破坏我的应用程序功能。

非常感谢!

1个回答

6
我已经根据您提供的代码创建了一个项目,并且在我的Nexus 7上成功模拟了您的问题。虽然我没有具体的学术答案,但是我最好的解释如下:
1) MainActivity被启动。
2) 单击按钮,AffinityTestActivity在一个新任务中启动。
3) 单击按钮,AffinityTestActivity完成。
4) MainActivity恢复旧任务状态。
5) 在MainActivity的onResume中,HelloActivity的意图在同一任务中被调用。
6) 在进行了一些调整后得出的神秘部分:将旧任务带回前台并继续与其进行交互,这可能是对HelloActivity的onPause方法触发的原因(可能不是操作系统开发人员预期的结果)。虽然这并不是最令人满意的答案(鉴于我的有限操作系统级调度代码和时间问题的经验),但是我的实验表明沿着这个方向有一些东西。我首先发现这种干扰是通过logcat中频繁出现的错误看到的:
06-24 11:06:28.015  27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.MainActivity@64e05830 onPause
06-24 11:06:28.055  27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.AffinityTestActivity@64e22fc0 onCreate
06-24 11:06:28.075  27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.AffinityTestActivity@64e22fc0 onResume
06-24 11:06:28.175      665-685/? I/ActivityManager﹕ Displayed com.stackoverflow/.AffinityTestActivity: +163ms
06-24 11:06:29.997  27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.AffinityTestActivity$1@64e24bf8 finishes
06-24 11:06:30.007  27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.AffinityTestActivity@64e22fc0 onPause
06-24 11:06:30.027  27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.MainActivity@64e05830 onResume
06-24 11:06:30.027  27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.MainActivity@64e05830 starts HelloActivity
06-24 11:06:30.027     665-6346/? I/ActivityManager﹕ START u0 {cmp=com.stackoverflow/.HelloActivity} from pid 27200
06-24 11:06:30.117  27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.HelloActivity@64e33b18 onCreate
06-24 11:06:30.127  27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.HelloActivity@64e33b18 onResume
06-24 11:06:30.137  27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.HelloActivity@64e33b18 onPause
06-24 11:06:30.287      665-685/? I/ActivityManager﹕ Displayed com.stackoverflow/.HelloActivity: +182ms
06-24 11:06:32.389  27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.HelloActivity$1@64e356b0 finishes
06-24 11:06:32.389  27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.HelloActivity@64e33b18 onDestroy
06-24 11:06:32.399  27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.MainActivity@64e05830 onPause
06-24 11:06:32.399  27200-27200/com.stackoverflow E/ActivityThread﹕ Performing pause of activity that is not resumed: {com.stackoverflow/com.stackoverflow.MainActivity}
java.lang.RuntimeException: Performing pause of activity that is not resumed: {com.stackoverflow/com.stackoverflow.MainActivity}
at android.app.ActivityThread.performPauseActivity(ActivityThread.java:3015)
at android.app.ActivityThread.performPauseActivity(ActivityThread.java:3003)
at android.app.ActivityThread.handlePauseActivity(ActivityThread.java:2981)
at android.app.ActivityThread.access$1000(ActivityThread.java:135)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1207)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5001)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
06-24 11:06:32.409  27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.MainActivity@64e05830 onResume
06-24 11:06:32.769  27200-27200/com.stackoverflow I/System.out﹕ com.stackoverflow.AffinityTestActivity@64e22fc0 onDestroy

如您所见,MainActivity的onPause方法直到HelloActivity完成后才被调用。这也不对。这表明在将任务带到前台时,在onResume中启动一个活动会导致生命周期中发生一些意外的冲突。

为了看看如果我给活动/任务一点时间来完成任何未见处理会发生什么,我使用了一个处理程序来调用MainActivity中的HelloActivity意图:

 @Override
protected void onResume() {
    System.out.println(this + " onResume");
    super.onResume();

    if (!skipHello) {
        System.out.println(this+" starts "+HelloActivity.class.getSimpleName());

        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                Intent intent = new Intent(MainActivity.this, HelloActivity.class);
                startActivity(intent);
            }
        }, 1000);

        skipHello = true;
    } else {
        skipHello = false;
    }
}

这导致了更好的行为。HelloActivity 的行为正常,onPause 未被调用。显然,这对于工作代码来说并不理想,但它表明仅仅将执行时间向前移动一秒就可以解决问题。这是任务内部调度冲突的更多证据。
接下来,我尝试给 HelloActivity 分配它自己的任务:
<activity
    android:label="HELLO"
    android:name="com.stackoverflow.HelloActivity"
    android:configChanges="keyboardHidden|orientation|screenSize"
    android:launchMode="singleTask"
    android:taskAffinity=".DifferentTask">
</activity>

(顺便提一下,这种配置并没有太多意义,但我假设它反映了你真实项目中更有逻辑目的的情况。)
在这种情况下,一切都运作得很好。HelloActivity的生命周期不会干扰MainActivity的生命周期。然而,现在它有自己任务的额外开销和作为singleTask运行activity所伴随的问题(点击“主屏幕”按钮并重新打开应用程序将带您回到MainActivity,在关闭应用程序之前查看的最后一个活动是HelloActivity,但它将无法访问其新任务中的HelloActivity)。
我的最佳建议是找到避免这种特定场景的方法。 :) 尽管这似乎是Android的后续版本中的错误,但它是一个奇怪的边缘案例。如果这不是一个选择,您可以追求我用来解决它的其中一条路线。我尝试过其他几件事情,但很难规避事实,即调度在操作系统级别上受到控制,超出了我们的掌握。
很抱歉我不能给你一个更深入的答案,但那就是我现在能给你的全部!

谢谢您的回答,我已经发现在HelloActivity启动时使用一些延迟(我在测试中使用了计时器和500毫秒的延迟)可以解决问题。我怀疑这是Android操作系统中的一个错误,我可能会提交一个错误报告。如果没有更好的答案,我会等待几天并将声望奉献给您。;) 谢谢 - Berťák
你有时间提交那个错误报告了吗? :) - Brian
我刚刚提交了一个错误报告 https://code.google.com/p/android/issues/detail?id=72782 :) 谢谢 - Berťák
为了更好地理解,请参考此链接。https://inthecheesefactory.com/blog/understand-android-activity-launchmode/en - Karthikeyan Ve

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