java – Why backup related process might cause Application's onCreate is not executed?-ThrowExceptions

Exception or error:

It is common to have Application class as follow

public class WeNoteApplication extends MultiDexApplication {
    public static WeNoteApplication instance() {
        return me;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        me = this;

During normal circumstance, Application‘s onCreate will always be called before entry point Activity‘s 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());

However, if I run the following command while the app is launched

c:\yocto>adb shell bmgr restore com.yocto.wenote
restoreStarting: 1 packages
onUpdate: 0 = com.yocto.wenote
restoreFinished: 0
done

The app will be closed. If, I tap on the app icon to launch again. This is what happens

  1. Application‘s onCreate is not executed!
  2. Activity‘s onCreate is executed, and WeNoteApplication.instance() is null

I look at some Google’s Android source code (WorkManager for instance)

https://github.com/googlecodelabs/android-workmanager/issues/80

In their comment, they states that

// 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,...

It seems that, if backup related process is involved, Application‘s onCreate will not be executed!

Why it is so? Is this behavior ever documented some where?


Issue tracker

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


Full example for bug demonstration

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

How to solve:

You can bypass your issue with this workaround.

The idea behind this is to create a custom BackupAgent to receive notification of onRestoreFinished event and then kill your process, so the next time you will open the app the system will create your custom Application class.

Usually using a custom BackupAgent force you to implement the abstract methods onBackup and onRestore, which are used for key-value backup. Luckily if you specify android:fullBackupOnly in the manifest, the system will use the file-based Auto Backup instead, as explained here.

First of all, create the custom 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);
                }
            }
        }
    }
}

then add android:backupAgent="com.yocto.cheok.CustomBackupAgent" and android:fullBackupOnly="true" to the Android manifest:

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

Next time you will lunch the app after a restore you will get:

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!”

You are actually right based on your statement and the reason for that was clearly documented on android docs.

Android provides two ways for apps to back up their data:
Auto backup for apps and Key/Value Backup.

Both ways makes use of bmgr tool and basically what Auto backup does is same as what you did.

c:\yocto>adb shell bmgr restore com.yocto.wenote

Custom Application class does not exist after restore, Why is it so?

The docs clearly states that

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
.

Even while your app is completely restored using bmgr tool it can still be under restricted mode (without its Custom Application class available but an instance of base-class Application).

Referencing your Custom Application class at this state or any method in it from anywhere in your app would surely return a null reference because it does not exist yet in your app due to the statement above.

You are expected to bring the app back to its default state by killing it entirely and re-starting it again, which is the one last thing Auto backup does behind the scene that you are not doing via your commands. This simply means your command statements wasn’t completed before you re-launched the app.

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

Add log inside Custom Application class onCreate

Log.d("MyApplicationLog", "MyApplication --> " + MyApplication.intstance());

Add log inside Launcher Activity class onCreate

Log.d("MainActivityLog", "MyApplication --> " +  MyApplication.intstance());

Command 1

--Configure backup transport
c:\me\MyWebApp>adb shell bmgr transport android/com.android.internal.backup.LocalTransport

Output

Selected transport android/com.android.internal.backup.LocalTransport (formerly com.google.android.gms/.backup.BackupTransportService)

Command 2

--Backup app
c:\me\MyWebApp>adb shell bmgr backupnow com.android.webviewapp 

Output

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

Click app manually on launcher or run the monkey command which is synonymous to app click action

--Launch app
c:\me\MyWebApp>adb shell monkey -p com.android.webviewapp 1

Output on Logcat

enter image description here

Command 3

--Restore app backup
c:\me\MyWebApp>adb shell bmgr restore com.android.webviewapp

Output

restoreStarting: 1 packages
onUpdate: 0 = com.android.webviewapp
restoreFinished: 0
done

Click app manually from launcher or run the above monkey command again

Output after launch
enter image description here

You can launch app as many times as you want the output would still be null for Custom Application until you run the below command

Command 4

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

Click on app manually from launcher or run the above monkey command again

Output after launch
enter image description here

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.

###

The only place I can find any documentation of this is in Test Backup and Restore. This documents that your app will be closed down, and that for full backup the Application base class will be used in place of your class. I can’t see that the reason for this is documented anywhere, but I suspect it is because a custom Application class might interfere with backup or restore by, for example, opening or modifying files.

I don’t think that there is any way that this problem could be solved in the Android code base, since Android cannot know what an app’s custom Application class is going to do, so can’t safely run an automatic backup while the custom Application class is running.

There are two possible ways of getting round this in your app:

  • As suggested in the Application class documentation, don’t derive from Application. Instead use singletons and Context.getApplicationContext().
    • I was once told by a member of Android development team that allowing custom Application classes had been a significant mistake in the design of the Android API.
  • Switch to using Key/Value backup to backup your app. This is considerably more work, but, since your app is now in control of backup it can ensure that there is no conflict between backup or restore and the running app.

Leave a Reply

Your email address will not be published. Required fields are marked *