为啥备份相关过程可能会导致应用程序的 onCreate 未执行?
Posted
技术标签:
【中文标题】为啥备份相关过程可能会导致应用程序的 onCreate 未执行?【英文标题】:Why backup related process might cause Application's onCreate is not executed?为什么备份相关过程可能会导致应用程序的 onCreate 未执行? 【发布时间】:2019-12-04 06:24:51 【问题描述】: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
【问题讨论】:
"看来,如果涉及到备份相关的过程,Application的onCreate就不会执行了!" - 这不好。更糟糕的是,您的进程似乎仍然存在,Android 仍在继续使用它。如果备份机制将您的进程分叉成这样的异常状态,则备份机制需要在完成后终止该进程,并且 Android 需要确保它不会尝试将这个损坏的进程用于其他任何事情。显然,根据您的分析,这些都没有完成。 :-( 【参考方案1】:您可以使用此解决方法绕过您的问题。
这背后的想法是创建一个自定义的BackupAgent
来接收onRestoreFinished
事件的通知,然后杀死你的进程,这样下次你打开应用程序时,系统会创建你的自定义应用程序类。
通常使用自定义的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
【讨论】:
听起来是一个合理的解决方法。不确定是否有任何可能的副作用? 我什么都想不出来。备份和还原仍由系统处理,此代码添加的唯一内容是在还原完成后终止进程。 请问使用android:fullBackupOnly="true"
的原因是什么?因为,它的默认值为 false。
我对@987654336@ 发表了我对***.com/questions/57357731/… 的疑问
另外,对于不支持自动备份的 Android 6,我可以知道代码的行为方式吗?【参考方案2】:
"看来,如果涉及到备份相关的过程,Application的 onCreate 不会被执行!”
根据您的陈述,您实际上是对的,原因在 android docs 上已明确记录。
Android 为应用提供了两种备份数据的方式: Auto backup for apps 和 Key/Value Backup。
两种方式都使用bmgr tool,基本上Auto backup所做的与您所做的相同。
c:\yocto>adb shell bmgr restore com.yocto.wenote
恢复后自定义应用类不存在,为什么会这样?
docs 明确指出
在自动备份和恢复操作期间,系统会启动应用程序 在受限模式下,以防止应用程序访问 可能会导致冲突并让应用程序在其执行回调方法 BackupAgent。在这种受限模式下,应用的主要活动不是 自动启动,其Content Providers 未初始化,并且 基类Application 被实例化而不是任何子类 在应用程序的 manifest 中声明。
即使您的应用已使用 bmgr tool 完全恢复,它仍可能处于受限模式(没有可用的自定义应用程序类,但基类 Application 的实例)。
在此状态下引用您的自定义应用程序类或从应用程序中的任何位置引用其中的任何方法肯定会返回空引用,因为由于上述声明,它在您的应用程序中尚不存在。
您应该通过完全杀死应用程序并重新启动它来将应用程序恢复到默认状态,这是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
下面是我的测试用例,基于你的code 使用Android Studio IDE 和Android O 设备
在自定义应用程序类 onCreate 中添加日志
Log.d("MyApplicationLog", "MyApplication --> " + MyApplication.intstance());
在 Launcher Activity 类 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
从启动器手动点击应用程序或再次运行上述猴子命令
启动后的输出
简单地说:Android 操作系统始终假定备份操作仍在 一直持续到应用程序进程重新启动,它不会恢复对应用程序自定义应用程序类的访问。
【讨论】:
是否没有为键值备份启用受限应用模式,即仅用于自动备份?【参考方案3】:我唯一能找到这方面的任何文档的地方是Test Backup and Restore。这记录了您的应用程序将被关闭,并且对于完整备份,应用程序基类将用于代替您的类。我看不到任何地方都记录了这种情况的原因,但我怀疑这是因为自定义应用程序类可能会干扰备份或恢复,例如打开或修改文件。
我认为在 Android 代码库中无法解决此问题,因为 Android 无法知道应用的自定义 Application 类将要做什么,因此无法安全地运行自动备份自定义应用程序类正在运行。
在您的应用中有两种可能的解决方法:
正如Application class documentation 中所建议的,不要从应用程序派生。而是使用单例和 Context.getApplicationContext()。 曾经有一位 Android 开发团队的成员告诉我,允许自定义应用程序类是 Android API 设计中的一个重大错误。 切换到使用Key/Value backup 来备份您的应用程序。这是相当多的工作,但是,由于您的应用现在可以控制备份,因此可以确保备份或恢复与正在运行的应用之间没有冲突。【讨论】:
以上是关于为啥备份相关过程可能会导致应用程序的 onCreate 未执行?的主要内容,如果未能解决你的问题,请参考以下文章