android 4.4.X: taskAffinity & launchmode vs. жизненный цикл Activity

Я разработал простое приложение, которое демонстрирует странное поведение на устройствах 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 и Samsung Galaxy S5). Как видите, журнал завершается с помощью onPause HelloActivity, что не имеет смысла (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 (<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 ответ

Решение

Я создал проект на основе предоставленного вами кода, и мне удалось воссоздать вашу проблему на моем собственном Nexus 7. Хотя у меня нет конкретного академического ответа для вас, мое лучшее объяснение заключается в следующем:

1) MainActivity запущена

2) Кнопка нажата. AffinityTestActivity запускается в новой задаче.

3) Кнопка нажата. AffinityTestActivity заканчивается.

4) MainActivity возобновляет работу в рамках старой задачи.

5) В onResume MainActivity намерение HelloActivity вызывается в той же задаче.

6) Загадочная часть, которая является моей теорией после небольшого переделывания: некоторая часть переноса старой задачи на передний план продолжает взаимодействовать с MainActivity, корнем старой задачи, во время вызова onResume. Это взаимодействие вызывает запуск метода onPause HelloActivity (вероятно, не предназначенный для разработчиков ОС). Хотя это не самый удовлетворительный ответ (учитывая мой ограниченный опыт работы с кодом планирования на уровне ОС и проблемами с синхронизацией), мои эксперименты указывают на что-то подобное. Моя первая подсказка к этому вмешательству была эта частая ошибка, замеченная в logcat:

06-24 11:06:28.015  27200-27200/com.stackru I/System.out﹕ com.stackru.MainActivity@64e05830 onPause
06-24 11:06:28.055  27200-27200/com.stackru I/System.out﹕ com.stackru.AffinityTestActivity@64e22fc0 onCreate
06-24 11:06:28.075  27200-27200/com.stackru I/System.out﹕ com.stackru.AffinityTestActivity@64e22fc0 onResume
06-24 11:06:28.175      665-685/? I/ActivityManager﹕ Displayed com.stackru/.AffinityTestActivity: +163ms
06-24 11:06:29.997  27200-27200/com.stackru I/System.out﹕ com.stackru.AffinityTestActivity$1@64e24bf8 finishes
06-24 11:06:30.007  27200-27200/com.stackru I/System.out﹕ com.stackru.AffinityTestActivity@64e22fc0 onPause
06-24 11:06:30.027  27200-27200/com.stackru I/System.out﹕ com.stackru.MainActivity@64e05830 onResume
06-24 11:06:30.027  27200-27200/com.stackru I/System.out﹕ com.stackru.MainActivity@64e05830 starts HelloActivity
06-24 11:06:30.027     665-6346/? I/ActivityManager﹕ START u0 {cmp=com.stackru/.HelloActivity} from pid 27200
06-24 11:06:30.117  27200-27200/com.stackru I/System.out﹕ com.stackru.HelloActivity@64e33b18 onCreate
06-24 11:06:30.127  27200-27200/com.stackru I/System.out﹕ com.stackru.HelloActivity@64e33b18 onResume
06-24 11:06:30.137  27200-27200/com.stackru I/System.out﹕ com.stackru.HelloActivity@64e33b18 onPause
06-24 11:06:30.287      665-685/? I/ActivityManager﹕ Displayed com.stackru/.HelloActivity: +182ms
06-24 11:06:32.389  27200-27200/com.stackru I/System.out﹕ com.stackru.HelloActivity$1@64e356b0 finishes
06-24 11:06:32.389  27200-27200/com.stackru I/System.out﹕ com.stackru.HelloActivity@64e33b18 onDestroy
06-24 11:06:32.399  27200-27200/com.stackru I/System.out﹕ com.stackru.MainActivity@64e05830 onPause
06-24 11:06:32.399  27200-27200/com.stackru E/ActivityThread﹕ Performing pause of activity that is not resumed: {com.stackru.com.stackru.MainActivity}
java.lang.RuntimeException: Performing pause of activity that is not resumed: {com.stackru.com.stackru.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.stackru I/System.out﹕ com.stackru.MainActivity@64e05830 onResume
06-24 11:06:32.769  27200-27200/com.stackru I/System.out﹕ com.stackru.AffinityTestActivity@64e22fc0 onDestroy

Как видите, метод MainActivity onPause даже не вызывался до тех пор, пока HelloActivity не была завершена. Это тоже не правильно. Для меня это показывает, что запуск действия в onResume при выводе задачи на передний план вызывает некоторые непреднамеренные конфликты в жизненном цикле.

Чтобы увидеть, что произошло, если я дал секунду активности / задаче для завершения любой невидимой обработки, я использовал обработчик для вызова намерения HelloActivity в MainActivity:

 @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.stackru.HelloActivity"
    android:configChanges="keyboardHidden|orientation|screenSize"
    android:launchMode="singleTask"
    android:taskAffinity=".DifferentTask">
</activity>

(Кстати, эта конфигурация не имеет особого смысла, но я предполагаю, что она отражает сценарий в вашем реальном проекте, имеющий более логичное назначение.)

По этому сценарию все работает нормально. Жизненный цикл HelloActivity не влияет на жизненный цикл MainActivity. Тем не менее, теперь у него есть накладные расходы на его собственную задачу и сопутствующие проблемы запуска деятельности, как singleTask (нажатие кнопки "Домой" и повторное открытие приложения приведут вас к MainActivity, в результате чего HelloActivity будет недоступна в новой задаче, даже если это было последнее действие, просмотренное перед закрытием приложения).

Моя лучшая рекомендация - найти способ избежать этого конкретного сценария.:) Кажется, это ошибка в более поздних версиях Android, хотя и странный крайний случай. Если это не вариант, вы можете воспользоваться одним из маршрутов, которые я использовал для его обхода. Я пробовал пару других вещей, но трудно обойти тот факт, что планирование контролируется на уровне операционной системы вне нашего понимания.

Извините, что я не смог получить более подробный ответ, но это все, что у меня есть сейчас!

Другие вопросы по тегам