为什么备份相关进程会导致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;

在正常情况下,ApplicationonCreate 将始终在入口点 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. ApplicationonCreate没有执行!
  2. ActivityonCreate被执行,而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,...

看来,如果涉及备份相关进程,ApplicationonCreate将不会被执行!

为什么会这样?这种行为是否曾在某处记录过?


问题跟踪器

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


错误演示的完整示例

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

我唯一能找到相关文档的地方是 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 强制您实现抽象方法 onBackuponRestore,它们用于键值备份。幸运的是,如果您在清单中指定 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.