为什么备份相关的进程可能会导致应用程序的onCreate方法不被执行?

23

通常情况下,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. ApplicationonCreate 方法不会执行!
  2. ActivityonCreate 方法会被执行,但 WeNoteApplication.instance()null
我查看了一些谷歌的 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


演示bug的完整示例

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


看起来,如果涉及备份相关的进程,应用程序的onCreate将不会被执行!这不好。更糟糕的是,似乎您的进程仍然存在,而Android正在继续使用它。如果备份机制以这种不寻常的状态分叉您的进程,则备份机制需要在完成后终止该进程,并且Android需要确保它不尝试将此损坏的进程用于其他任何事情。根据您的分析,显然没有做到这一点。 :-( - CommonsWare
3个回答

11
您可以使用这个解决方案绕过您的问题。
其背后的想法是创建一个自定义的BackupAgent来接收onRestoreFinished事件的通知,然后杀死进程,这样下一次打开应用程序时,系统将创建您的自定义应用程序类。
通常使用自定义BackupAgent会强制您实现抽象方法onBackuponRestore,用于键值备份。幸运的是,如果在清单中指定了android:fullBackupOnly,则系统将使用基于文件的Auto Backup,如此处所述。
首先,创建自定义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清单中添加android:backupAgent="com.yocto.cheok.CustomBackupAgent"android:fullBackupOnly="true"

<?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

听起来是一个合理的解决方案。不确定是否会有任何可能的副作用? - Cheok Yan Cheng
我想不出什么。备份和还原仍由系统处理,此代码添加的唯一内容是在还原完成后终止进程。 - Mattia Maestrini
我可以知道使用android:fullBackupOnly="true"的原因吗?因为它的默认值是false。 - Cheok Yan Cheng
我在 https://dev59.com/7LXna4cB1Zd3GeqPMYUH 上发布了关于 android:fullBackupOnly 的疑问。 - Cheok Yan Cheng
另外,我可以知道代码在不支持自动备份的Android 6上的行为如何吗? - Cheok Yan Cheng
对于 Android 6.0 之前的版本,您必须提供自定义实现的 onBackuponRestore,如此处所述,因为不支持自动备份。 - Mattia Maestrini

8
"看起来,如果涉及备份相关的进程,应用程序的onCreate将不会被执行!"
根据您的陈述,您是正确的,其原因已在android文档中清楚地记录。
"Android为应用程序提供了两种方式进行数据备份:应用程序自动备份键/值备份。"
这两种方式都使用了bmgr工具,基本上自动备份所做的与您所做的相同。
c:\yocto>adb shell bmgr restore com.yocto.wenote

在恢复后,自定义应用程序类不存在,为什么会这样?文档清楚地说明:
在自动备份和恢复操作期间,系统会以受限模式启动应用程序,既防止应用程序访问可能导致冲突的文件,又让应用程序执行其BackupAgent中的回调方法。在此受限模式下,不会自动启动应用程序的主活动,也不会初始化其内容提供者,而是实例化基类Application,而不是在应用程序清单中声明的任何子类。
即使使用 bmgr 工具 完全恢复应用程序,它仍可能处于受限模式(未启用其自定义应用程序类,但存在基类 Application 的实例)。
在此状态下引用自定义应用程序类或其中的任何方法都肯定会返回 null 引用,因为由上述语句造成它尚不存在于您的应用程序中。
您需要将应用程序完全关闭并重新启动,将其恢复到默认状态,这是自动备份在幕后执行的最后一步操作,而您没有通过命令执行。这意味着您的命令语句在重新启动应用程序之前尚未完成。
--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的设备的测试用例。
在Custom Application类的onCreate方法中添加日志。
Log.d("MyApplicationLog", "MyApplication --> " + MyApplication.intstance());

在启动器活动类的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 上的输出

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

手动从启动器点击应用程序或再次运行上面的猴子命令

启动后的输出 在此输入图片描述

您可以随意启动应用程序,但在运行以下命令之前,自定义应用程序的输出仍将为空

命令4

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

从启动器手动点击应用程序或再次运行上面的monkey命令

启动后的输出 在此输入图片描述

简单来说:Android操作系统总是假定备份操作仍在进行中,直到重新启动应用程序进程才会恢复对应用程序自定义应用程序类的访问权限。


受限制的应用程序模式是否未启用键值备份,即仅用于自动备份? - JohnyTex

0

我能找到的唯一关于这个问题的文档是在测试备份和还原中。这篇文档说明你的应用程序将被关闭,并且在进行完整备份时,应用程序基类将代替你的类。我无法看到这个原因在任何地方有记录,但我怀疑这是因为自定义应用程序类可能会通过打开或修改文件等方式干扰备份或还原。

我认为在Android代码库中没有任何方法可以解决这个问题,因为Android无法知道应用程序的自定义应用程序类将要做什么,所以不能在自定义应用程序类运行时安全地运行自动备份。

在你的应用程序中有两种可能的解决方法:

  • Application类文档中建议的那样,不要派生自Application。而是使用单例和Context.getApplicationContext()。
    • 我曾经被Android开发团队的一位成员告知,允许自定义Application类是Android API设计中的一个重大错误。
  • 切换到使用键/值备份来备份您的应用程序。这需要更多的工作,但由于您的应用程序现在控制备份,因此可以确保备份或还原与运行中的应用程序之间没有冲突。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接