为什么备份相关进程会导致Application的onCreate没有执行?
Why backup related process might cause Application's onCreate is not executed?
常见的有Application
class如下
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
错误演示的完整示例
我唯一能找到相关文档的地方是 Test Backup and Restore。这表明您的应用程序将被关闭,并且为了完整备份,将使用应用程序库 class 代替您的 class。我看不到任何地方都记录了这样做的原因,但我怀疑这是因为自定义应用程序 class 可能会通过打开或修改文件等方式干扰备份或恢复。
我认为无法在 Android 代码库中解决此问题,因为 Android 无法知道应用程序的自定义应用程序 class 是什么要做,所以不能安全地 运行 自动备份,而自定义应用程序 class 是 运行ning.
在您的应用中有两种可能的方法来解决这个问题:
- 如 Application class documentation 中所建议的那样,不要从应用程序派生。而是使用单例和 Context.getApplicationContext()。
- Android 开发团队的一名成员曾经告诉我,允许自定义应用程序 classes 是 Android API 设计中的一个重大错误.
- 切换到使用 Key/Value backup 备份您的应用。这是相当多的工作,但是,由于您的应用程序现在控制备份,它可以确保备份或恢复与 运行ning 应用程序之间没有冲突。
您可以使用此解决方法绕过您的问题。
这背后的想法是创建一个自定义 BackupAgent
来接收 onRestoreFinished
事件的通知,然后终止您的进程,因此下次您打开该应用程序时,系统将创建您的自定义申请 class.
通常使用自定义 BackupAgent
强制您实现抽象方法 onBackup
和 onRestore
,它们用于键值备份。幸运的是,如果您在清单中指定 android:fullBackupOnly
,系统将改为使用基于文件的自动备份,如 here.
所述
首先,创建自定义 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 = com.yocto.cheok.CheokApplication@7b28a29
"It seems that, if backup related process is involved, Application's
onCreate will not be executed!"
根据您的陈述,您实际上是正确的,原因在 android docs.
上有明确记录
Android provides two ways for apps to back up their data:
Auto backup for apps and Key/Value Backup.
这两种方式都利用了bmgr tool and basically what Auto backup 做的和你做的一样。
c:\yocto>adb shell bmgr restore com.yocto.wenote
Custom Application class does not exist after restore, Why is it so?
docs 明确指出
During auto backup and restore operations, the system launches the app
in a restricted mode to both prevent the app from accessing files that
could cause conflicts and let the app execute callback methods in its
BackupAgent. In this restricted mode, the app's main activity is not
automatically launched, its Content Providers are not initialized, and
the base-class Application is instantiated instead of any subclass
declared in the app's manifest.
即使您的应用已使用 bmgr tool it can still be under restricted mode (without its Custom Application class available but an instance of base-class Application) 完全恢复。
在此状态下引用您的自定义应用程序 class 或从您应用程序中的任何地方引用其中的任何方法肯定会 return 空引用,因为它在您的应用程序中尚不存在,因为声明以上。
您应该通过完全终止应用程序并重新启动它来将应用程序恢复到默认状态,这是 Auto backup 在幕后所做的最后一件事,您没有通过您的命令。这仅仅意味着您的命令语句在您重新启动应用程序之前未完成。
--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
Below is my testcase based on your code using Android Studio IDE and a device with Android O
在自定义应用程序中添加日志 class onCreate
Log.d("MyApplicationLog", "MyApplication --> " + MyApplication.intstance());
在 Launcher 中添加日志 Activity class 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 @pm@ 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
在启动器上手动点击应用程序或 运行 与应用程序点击操作同义的猴子命令
--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
从启动器手动点击应用程序或再次运行上述猴子命令
启动后的输出
Simply put: Android OS always assumes that a backup operation is still
on-going until the app process is restarted it wont restore access to apps Custom Application class.
常见的有Application
class如下
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
错误演示的完整示例
我唯一能找到相关文档的地方是 Test Backup and Restore。这表明您的应用程序将被关闭,并且为了完整备份,将使用应用程序库 class 代替您的 class。我看不到任何地方都记录了这样做的原因,但我怀疑这是因为自定义应用程序 class 可能会通过打开或修改文件等方式干扰备份或恢复。
我认为无法在 Android 代码库中解决此问题,因为 Android 无法知道应用程序的自定义应用程序 class 是什么要做,所以不能安全地 运行 自动备份,而自定义应用程序 class 是 运行ning.
在您的应用中有两种可能的方法来解决这个问题:
- 如 Application class documentation 中所建议的那样,不要从应用程序派生。而是使用单例和 Context.getApplicationContext()。
- Android 开发团队的一名成员曾经告诉我,允许自定义应用程序 classes 是 Android API 设计中的一个重大错误.
- 切换到使用 Key/Value backup 备份您的应用。这是相当多的工作,但是,由于您的应用程序现在控制备份,它可以确保备份或恢复与 运行ning 应用程序之间没有冲突。
您可以使用此解决方法绕过您的问题。
这背后的想法是创建一个自定义 BackupAgent
来接收 onRestoreFinished
事件的通知,然后终止您的进程,因此下次您打开该应用程序时,系统将创建您的自定义申请 class.
通常使用自定义 BackupAgent
强制您实现抽象方法 onBackup
和 onRestore
,它们用于键值备份。幸运的是,如果您在清单中指定 android:fullBackupOnly
,系统将改为使用基于文件的自动备份,如 here.
首先,创建自定义 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 = com.yocto.cheok.CheokApplication@7b28a29
"It seems that, if backup related process is involved, Application's onCreate will not be executed!"
根据您的陈述,您实际上是正确的,原因在 android docs.
上有明确记录Android provides two ways for apps to back up their data: Auto backup for apps and Key/Value Backup.
这两种方式都利用了bmgr tool and basically what Auto backup 做的和你做的一样。
c:\yocto>adb shell bmgr restore com.yocto.wenote
Custom Application class does not exist after restore, Why is it so?
docs 明确指出
During auto backup and restore operations, the system launches the app in a restricted mode to both prevent the app from accessing files that could cause conflicts and let the app execute callback methods in its BackupAgent. In this restricted mode, the app's main activity is not automatically launched, its Content Providers are not initialized, and the base-class Application is instantiated instead of any subclass declared in the app's manifest.
即使您的应用已使用 bmgr tool it can still be under restricted mode (without its Custom Application class available but an instance of base-class Application) 完全恢复。
在此状态下引用您的自定义应用程序 class 或从您应用程序中的任何地方引用其中的任何方法肯定会 return 空引用,因为它在您的应用程序中尚不存在,因为声明以上。
您应该通过完全终止应用程序并重新启动它来将应用程序恢复到默认状态,这是 Auto backup 在幕后所做的最后一件事,您没有通过您的命令。这仅仅意味着您的命令语句在您重新启动应用程序之前未完成。
--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
Below is my testcase based on your code using Android Studio IDE and a device with Android O
在自定义应用程序中添加日志 class onCreate
Log.d("MyApplicationLog", "MyApplication --> " + MyApplication.intstance());
在 Launcher 中添加日志 Activity class 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 @pm@ 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
在启动器上手动点击应用程序或 运行 与应用程序点击操作同义的猴子命令
--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
从启动器手动点击应用程序或再次运行上述猴子命令
启动后的输出
Simply put: Android OS always assumes that a backup operation is still on-going until the app process is restarted it wont restore access to apps Custom Application class.