Почему процесс, связанный с резервным копированием, может привести к тому, что Application onCreate не выполняется?

Как правило, класс Application следующий

public class WeNoteApplication extends MultiDexApplication {
    public static WeNoteApplication instance() {
        return me;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        me = this;

В обычных условиях Application onCreate всегда вызывается перед точкой входа Activity onCreate.

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Normally, it will NOT be null.
        android.util.Log.i("CHEOK", "WeNoteApplication -> " + WeNoteApplication.instance());

Однако, если я запускаю следующую команду во время запуска приложения

c:\yocto>adb shell bmgr restore com.yocto.wenote
restoreStarting: 1 packages
onUpdate: 0 = com.yocto.wenote
restoreFinished: 0
done

Приложение будет закрыто. Если я коснусь значка приложения, чтобы запустить снова. Это то, что происходит

  1. Application onCreate не выполняется!
  2. Activity onCreate выполняется, а WeNoteApplication.instance() является null

Я смотрю на исходный код Google Android (например, WorkManager)

https://github.com/googlecodelabs/android-workmanager/issues/80

В своем комментарии они утверждают, что

// 1. The app is performing an auto-backup.  Prior to O, JobScheduler could erroneously
//    try to send commands to JobService in this state (b/32180780).  Since neither
//    Application#onCreate nor ContentProviders have run,...

Похоже, что если связан процесс резервного копирования, Application onCreate не будет выполнен!

Почему так? Задокументировано ли когда-нибудь это поведение?


Отслеживание проблем

https://issuetracker.google.com/issues/138423608


Полный пример для демонстрации ошибки

https://github.com/yccheok/AutoBackup-bug

Ответы

Ответ 1

Вы можете обойти вашу проблему с помощью этого обходного пути.

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

Обычно использование пользовательского BackupAgent заставляет вас реализовывать абстрактные методы onBackup и onRestore, которые используются для резервного копирования значения ключа. К счастью, если вы укажете android:fullBackupOnly в манифесте, система будет использовать вместо этого автоматическое резервное копирование на основе файлов, как описано здесь.

Прежде всего, создайте пользовательский BackupAgent:

package com.yocto.cheok;

import android.app.ActivityManager;
import android.app.backup.BackupAgent;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
import android.content.Context;
import android.os.ParcelFileDescriptor;
import android.os.Process;

import java.util.List;

public class CustomBackupAgent extends BackupAgent {

    private Boolean isRestoreFinished = false;

    @Override
    public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) {
        //NO-OP - abstract method
    }

    @Override
    public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) {
        //NO-OP - abstract method
    }

    @Override
    public void onRestoreFinished() {
        super.onRestoreFinished();

        isRestoreFinished = true;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        if (isRestoreFinished) {
            ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);

            if (activityManager != null) {
                final List<ActivityManager.RunningAppProcessInfo> runningServices = activityManager.getRunningAppProcesses();

                if (runningServices != null &&
                        runningServices.size() > 0 &&
                        runningServices.get(0).processName.equals("com.yocto.cheok")
                ) {
                    Process.killProcess(runningServices.get(0).pid);
                }
            }
        }
    }
}

затем добавьте android:backupAgent="com.yocto.cheok.CustomBackupAgent" и android:fullBackupOnly="true" в манифест Android:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yocto.cheok">

    <application
        android:name="com.yocto.cheok.CheokApplication"
        android:allowBackup="true"
        android:backupAgent="com.yocto.cheok.CustomBackupAgent"
        android:fullBackupContent="@xml/my_backup_rules"
        android:fullBackupOnly="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name="com.yocto.cheok.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>

В следующий раз, когда вы восстановите приложение после восстановления, вы получите:

2019-07-28 22:25:33.528 6956-6956/com.yocto.cheok I/CHEOK: CheokApplication onCreate
2019-07-28 22:25:33.642 6956-6956/com.yocto.cheok I/CHEOK: In MainActivity, CheokApplication = [email protected]

Ответ 2

  "Похоже, что если задействован процесс резервного копирования, приложение onCreate не будет выполнен! "

На самом деле вы правы, основываясь на своем заявлении, и причина этого была четко задокументирована в документации для Android.

Android предоставляет приложениям два способа резервного копирования своих данных: Автоматическое резервное копирование для приложений и Резервное копирование ключей/значений.

Оба способа используют bmgr tool, и в основном то, что делает Автоматическое резервное копирование, совпадает с тем, что вы делали.

c:\yocto>adb shell bmgr restore com.yocto.wenote

Пользовательский класс приложения не существует после восстановления. Почему это так?

В документе четко указано, что

Во время операций автоматического резервного копирования и восстановления система запускает приложение в ограниченном режиме, чтобы предотвратить доступ приложения к файлам, которые может вызвать конфликты и позволить приложению выполнять методы обратного вызова в его BackupAgent. В этом ограниченном режиме основное действие приложения не автоматически запускается, контент-провайдеры не инициализируются и базовый класс Приложение создается вместо любого подкласса объявлено в приложении манифестом.

Даже если ваше приложение полностью восстановлено с помощью bmgr tool, оно все равно может находиться в ограниченном режиме (без доступного класса пользовательских приложений, но с экземпляром базового класса приложения).

Ссылка на класс Custom Application в этом состоянии или любой метод в нем из любого места в вашем приложении наверняка вернет нулевую ссылку, поскольку он еще не существует в вашем приложении из-за приведенного выше заявления.

Ожидается, что вы вернете приложение в его состояние по умолчанию, полностью его убив и снова запустив, что является последним, что Автоматическое резервное копирование делает за кулисами, чего вы не делаете с помощью своих команд. Это просто означает, что ваши операторы команд не были выполнены до того, как вы перезапустили приложение.

--Kill app process and restart app

c:\yocto>adb shell am force-stop com.yocto.wenote
c:\yocto>adb shell monkey -p com.yocto.wenote 1

Ниже приведен мой тестовый пример на основе вашего кода с использованием Android Studio IDE и устройства с Android O

Добавить журнал в класс пользовательских приложений onCreate

Log.d("MyApplicationLog", "MyApplication --> " + MyApplication.intstance());

Добавить журнал в классе активности Launcher onCreate

Log.d("MainActivityLog", "MyApplication --> " +  MyApplication.intstance());

Команда 1

--Configure backup transport
c:\me\MyWebApp>adb shell bmgr transport android/com.android.internal.backup.LocalTransport

Выход

Selected transport android/com.android.internal.backup.LocalTransport (formerly com.google.android.gms/.backup.BackupTransportService)

Команда 2

--Backup app
c:\me\MyWebApp>adb shell bmgr backupnow com.android.webviewapp 

Выход

Running incremental backup for 1 requested packages.
Package @[email protected] with result: Success
Package com.android.webviewapp with progress: 512/1024
Package com.android.webviewapp with progress: 1536/1024
Package com.android.webviewapp with progress: 2048/1024
Package com.android.webviewapp with progress: 2560/1024
Package com.android.webviewapp with result: Success
Backup finished with result: Success

Нажмите приложение вручную на панели запуска или выполните команду monkey, которая является синонимом действия нажатия приложения

--Launch app
c:\me\MyWebApp>adb shell monkey -p com.android.webviewapp 1

Вывод в Logcat

enter image description here

Команда 3

--Restore app backup
c:\me\MyWebApp>adb shell bmgr restore com.android.webviewapp

Выход

restoreStarting: 1 packages
onUpdate: 0 = com.android.webviewapp
restoreFinished: 0
done

Нажмите приложение вручную из панели запуска или снова введите указанную выше команду обезьяны

Выход после запуска enter image description here

Вы можете запускать приложение столько раз, сколько вы хотите, чтобы для пользовательского приложения вывод по-прежнему был нулевым, пока вы не выполните приведенную ниже команду

Команда 4

--Force close app or kill running process
c:\me\MyWebApp>adb shell am force-stop com.android.webviewapp

Нажмите на приложение вручную из панели запуска или снова введите указанную выше команду обезьяны

Выход после запуска enter image description here

Проще говоря: ОС Android всегда предполагает, что операция резервного копирования все еще до тех пор, пока процесс приложения не будет перезапущен, он не восстановит доступ к приложениям класса Custom Application.

Ответ 3

Единственное место, где я могу найти любую документацию по этому вопросу, находится в тестовом резервном копировании и восстановлении. Это подтверждает, что ваше приложение будет закрыто и что для полного резервного копирования вместо вашего класса будет использоваться базовый класс Application. Я не вижу, чтобы причина этого была где-либо задокументирована, но я подозреваю, что причина в том, что пользовательский класс Application может мешать резервному копированию или восстановлению, например, открывая или изменяя файлы.

Я не думаю, что каким-либо образом эту проблему можно решить в базе кода Android, поскольку Android не может знать, что собирается делать пользовательский класс приложения, поэтому не может безопасно запустить автоматическое резервное копирование, пока пользовательское приложение класс работает.

Есть два возможных способа обойти это в вашем приложении:

  • Как указано в документации класса Application, не наследуйте от Application. Вместо этого используйте singletons и Context.getApplicationContext().
    • Однажды один из членов команды разработчиков Android сказал, что использование пользовательских классов приложений было значительной ошибкой при разработке Android API.
  • Переключитесь на использование резервного копирования ключа/значения для резервного копирования приложения. Это значительно больше работы, но, поскольку ваше приложение теперь контролирует резервное копирование, оно может гарантировать отсутствие конфликта между резервным копированием или восстановлением и работающим приложением.