Почему процесс, связанный с резервным копированием, может привести к тому, что 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
Приложение будет закрыто. Если я коснусь значка приложения, чтобы запустить снова. Это то, что происходит
Application
onCreate
не выполняется!
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
Команда 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
Нажмите приложение вручную из панели запуска или снова введите указанную выше команду обезьяны
Выход после запуска
Вы можете запускать приложение столько раз, сколько вы хотите, чтобы для пользовательского приложения вывод по-прежнему был нулевым, пока вы не выполните приведенную ниже команду
Команда 4
--Force close app or kill running process
c:\me\MyWebApp>adb shell am force-stop com.android.webviewapp
Нажмите на приложение вручную из панели запуска или снова введите указанную выше команду обезьяны
Выход после запуска
Проще говоря: ОС Android всегда предполагает, что операция резервного копирования все еще до тех пор, пока процесс приложения не будет перезапущен, он не восстановит доступ к приложениям класса Custom Application.
Ответ 3
Единственное место, где я могу найти любую документацию по этому вопросу, находится в тестовом резервном копировании и восстановлении. Это подтверждает, что ваше приложение будет закрыто и что для полного резервного копирования вместо вашего класса будет использоваться базовый класс Application. Я не вижу, чтобы причина этого была где-либо задокументирована, но я подозреваю, что причина в том, что пользовательский класс Application может мешать резервному копированию или восстановлению, например, открывая или изменяя файлы.
Я не думаю, что каким-либо образом эту проблему можно решить в базе кода Android, поскольку Android не может знать, что собирается делать пользовательский класс приложения, поэтому не может безопасно запустить автоматическое резервное копирование, пока пользовательское приложение класс работает.
Есть два возможных способа обойти это в вашем приложении:
- Как указано в документации класса Application, не наследуйте от Application. Вместо этого используйте singletons и Context.getApplicationContext().
- Однажды один из членов команды разработчиков Android сказал, что использование пользовательских классов приложений было значительной ошибкой при разработке Android API.
- Переключитесь на использование резервного копирования ключа/значения для резервного копирования приложения. Это значительно больше работы, но, поскольку ваше приложение теперь контролирует резервное копирование, оно может гарантировать отсутствие конфликта между резервным копированием или восстановлением и работающим приложением.