【问题标题】:android 4.4.X: taskAffinity & launchmode vs. Activity lifecycleandroid 4.4.X:taskAffinity & launchmode 与 Activity 生命周期
【发布时间】:2014-06-20 10:24:21
【问题描述】:

我开发了一个简单的应用程序,它演示了我注意到的 Android 4.4.X 设备上的一些奇怪行为。

假设我想要 2 个“主要”活动,其中第一个活动在第二次恢复时说“Hello”(通过启动“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() 和 printlns 的按钮来记录其生命周期。

测试场景

  1. 启动 MainActivity。
  2. 启动 AffinityTestActivity。
  3. 完成 AffinityTestActivity(当 AffinityTestActivity 完成时,MainActivity 会恢复,HelloActivity 也会启动)。
  4. 分析输出。

日志

Android 4.4.2 和 4.4.3:(在 Nexus 7 II 和三星 Galaxy S5 上测试) 可以看到,log 以 HelloActivity 的 onPause 结束,这没有任何意义(HelloActivity 在第 3 步中显示在顶部)。 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 版本

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 版本 (

我开发的应用程序要复杂得多,并且可以使用其活动的生命周期,这种行为违反了我的应用程序的功能。

非常感谢!

【问题讨论】:

    标签: android android-activity launchmode taskaffinity


    【解决方案1】:

    我已经根据您提供的代码创建了一个项目,并且能够在我自己的 Nexus 7 上重新创建您的问题。虽然我没有给您一个具体的学术答案,但我最好的解释如下:

    1) MainActivity 启动

    2) 按钮被点击。 AffinityTestActivity 在新任务中启动。

    3) 按钮被点击。 AffinityTestActivity 结束。

    4) MainActivity 在旧任务中恢复。

    5) 在 MainActivity 的 onResume 中,HelloActivity 的 Intent 在同一个任务中被调用。

    6) 经过一番修改后,我的理论的神秘部分:将旧任务带到前台的某些部分在 onResume 调用期间继续与旧任务的根 MainActivity 交互。这种交互会导致 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
    

    如您所见,直到 HelloActivity 完成后才调用 MainActivity 的 onPause 方法。那也不对。对我来说,这表明在 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 运行活动的伴随问题(点击“主页”按钮并重新打开应用程序会将您带到 MainActivity,即使它在新任务中也无法访问 HelloActivity是关闭应用程序之前查看的最后一个活动)。

    我最好的建议是找到一种方法来避免这种特殊情况。 :) 这似乎是更高版本的 Android 中的一个错误,尽管是一个奇怪的边缘情况。如果这不是一个选择,您可以选择我用来绕过它的路线之一。我尝试了其他一些方法,但很难绕开调度控制在我们无法掌握的操作系统级别这一事实。

    很抱歉,我无法为您提供更深入的答案,但这就是我现在所知道的一切!

    【讨论】:

    • 感谢您的回答,我已经发现在 HelloActivity 启动时使用一些延迟(我在测试中使用了 Timer 和 500ms 延迟)就可以了。我怀疑这是 Android 操作系统中的错误,我可能会提交错误报告。如果没有人有更好的答案,我会等几天等待其他答案,并提供声誉是你的。 ;) 谢谢
    • 有时间提交错误报告吗? :)
    • 我刚刚提交了一个错误code.google.com/p/android/issues/detail?id=72782 :) 谢谢
    • 为了更好地理解,请参阅此链接。 inthecheesefactory.com/blog/…
    猜你喜欢
    • 2013-04-06
    • 2013-07-10
    • 1970-01-01
    • 2019-02-02
    • 1970-01-01
    • 1970-01-01
    • 2015-05-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多