Been testing the new Android 6 auto backup/restore function, and run into a problem with my app crashing immediately after a restore. Further investigation revealed that the Application.onCreate() initialization method was not being called before the main Activity.onCreate() method. This strikes me as a likely bug in the new autorestore logic. But I thought I would ask for advice here before reporting it as an official bug.
The sequence of events I go through is
Run the app, always open a main activity window.
Force a backup of app data by entering
adb shell bmgr fullbackup net.anei.cadpage
Use the app manager to force close the app and to clear all app and cache data
Restore app information with
adb shell bmgr restore
Manually launch the app
Resulting logs show that the Activity.onCreate() method is called before the Application.onCreate() is. The app crashes because some critical initialization was not performed by the Application.onCreate() method.
Is there something obvious that I am missing???
FWIW, launching the app a second time after the crash works perfectly.
It's actually intentional, though intrusive.
For full-data backup and restore operations, the package is launched with a base class Application instance, not your manifest-declared subclass. This is because, unfortunately, many apps open files or databases via Application subclasses, and this blocks the ability of the backup machinery to correctly read/write the underlying files. Similarly, your app's content providers are not automatically instantiated for full-data backup/restore operations. The app process is then destroyed following the operation, because of course your app cannot continue to run normally without its expected Application subclass or content providers.
You also don't say exactly what command you're using to perform a test restore, but I suspect you're using the bmgr command with this syntax:
adb shell bmgr restore PACKAGE
This doesn't do what you expect. In particular, it invokes the code path that happens when your app calls BackupManager.requestRestore(observer). In this specific code path, the app is NOT shut down following the restore operation, because the app has asked to observe the operation itself. This means that you're left with the app process still running but with a base class Application. It's a power-user API that is pretty much only safe when the app uses the original key/value backup API. You need to test instead using the other bmgr syntax:
adb shell bmgr restore TOKEN PACKAGE
where TOKEN is the identifier for which dataset should be used. At least on the most recent versions of the OS you can see the current and ancestral dataset tokens in the output of adb shell dumpsys backup.
This all needs to be better documented and made less surprising.
Subclassing Application is generally discouraged; this is one reason. Try to use your own lazy-init statics instead of subclassing Application.
Related
I'm building some general testing tool for Android apps and trying to get the activity name (e.g. com.android.calculator/.Calculator, the com.android.calculator can be obtained through UiDevice.currentPackageName, the pain is from the second part) during the testing. This is to say that my tool may test other apps that are not in the same package as mine. For example, testing the stock calculator app.
Here is a list of attempts I have made:
the UiDevice.currentActivityName is deprecated and it does not provide the accurate information
InstrumentationRegistry.getInstrumentation().targetContext.getSystemService<ActivityManager>(), then access the ActivityManager.getRunningTasks(1).get(0).topActivity, which always returns the same value even though the view has switched (and this is deprecated too)
Similar to 2, I tried ActivityManager.getAppTasks().get(0).getTaskInfo().topActivity, but this keeps giving me null
ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(RESUMED). This gives me an empty collection
Application.ActivityLifecycleCallbacks. I registered it with the application, but no callback is received.
(Kinda works, but not perfect) using adb dumpsys activity then find it there. But this way I have to wait for some arbitrary time until the view is updated, otherwise the result is still inaccurate
So I am currently in a cul-de-sac now, and I appreciate any help on this issue. I try to avoid reflection, because as I know it does not work on newer devices. So any clue will be appreciated.
As there is no answer posted, I wanna share my current strategy, which is not perfect, but somewhat helpful.
So first of all, as UiDevice.getActivityName is deprecated, I can only obtain the activity name through adb. Specifically, I use adb shell dumpsys activity top for API lower than 28 and adb shell dumpsys activity activities otherwise.
Then I subscribed to AccessibilityEventListener callbacks. Specifically, I run the adb command whenever I receive callbacks with type AccessibilityEvent.TYPE_WINDOWS_CHANGED. You can set up AccessibilityEventListener using this method: setOnAccessibilityEventListener
I'm following the guide to specify exclusions from full backups but running into a crash when I try and test it.
$ adb shell bmgr fullbackup <PACKAGE>
Works fine - files are excluded as expected.
I clear data then run:
$ adb shell bmgr restore <PACKAGE>
The restore works fine but then the next time I try and run the app I'm getting a ClassCastException:
Caused by: java.lang.ClassCastException: android.app.Application cannot be cast to com.domain.app.MyCustomApplicationClass
It appears that for some reason there is an instance of my application but it's not an instance of the custom application class as specified in the manifest.
Running the app a second time works fine and I can verify that all data was correctly restored.
I am testing this on a debug build and would like to try and resolve this error before pushing the latest changes to production.
The usual cause of this is inducing a manual restore "the wrong way." This is very poorly documented, I'm afraid, but there are different ways of invoking "bmgr restore", one of which will cause exactly the problem you describe.
(The problem, specifically, is that full-data backup/restore operations currently require that the app be launched with neither its content providers nor any app-defined Application subclass instantiated; instead, you run with a base-class Application instance. Trying to cast back to your declared subclass throws ClassCastException as you might imagine.)
In the normal course of things, your app is killed following restore. HOWEVER, if you trigger the restore like this:
adb shell bmgr restore PACKAGE
this does not happen. That particular invocation syntax runs a "my app wants to restore its data 'live' right now; do not kill me before or after" code path, the one that you get via BackupManager.requestRestore(). In this code path the app is intentionally not killed following restore. It's an artifact of the time when key/value was the only backup/restore paradigm, and in that paradigm there are no such Application subclass issues etc.
You need to make sure that when you trigger a restore via bmgr, you are using the full syntax:
adb shell bmgr restore TOKEN PACKAGE
This syntax invokes the complete restore-at-install code path, the one that will tear down your app following the restore specifically to avoid trying subsequent execution with a base-class Application.
'TOKEN' is the identifier of the dataset containing the data you wish to restore. If you are using the local debugging transport, then TOKEN is always "1". If you are using cloud backup, then it will be the device's own current backup dataset identifier if there is one, or the ancestral dataset if the device has not generated one itself. You can see these in the output of
adb shell dumpsys backup | egrep 'Current:|Ancestral:'
The dataset's identifying TOKEN is the hex string given there.
I have an app that needs to create a session in order to work. When the app is started via it's android.intent.action.MAIN and android.intent.category.LAUNCHER activity (LoginActivity), the session is created nicely, stored in a singleton class and the user is navigated to MainActivity.
Now the app tends to crash because the session does not exist. I assume this is because Android started MainActivity directly while the previously created session has been killed. Can that be the reason?
Do I need to expect that my app is restarted at any activity?
It is definitely the case, as one example (or used to be as I have not tested this in a while), that the Android 'OS' can start an app at an activity other than main if the app crashed.
This is supported by the Android online documentation which highlights the fact that Android apps can have multiple entry points, unlike many other systems. This can be a bit confusing at first: (http://developer.android.com/guide/components/fundamentals.html):
Therefore, unlike apps on most other systems, Android apps don't have a single entry point (there's no main() function, for example).
This is often used when an activity in one app is available to be 'called' from another application.
I think it is probably going a bit far to say it will be at an arbitrary activity as there is, I think, some logic to it - for example restarting the activity where the crash occurred, or from the point at which it was previously if the app is closed by the system to free up memory etc.
I'm appending init.rc in Android root with:
service logcat /system/bin/logcat -v long -f /mnt/sdcard/logcat.log
This solution doesn't generate any logs. The logcat.log file doesn't exist.
How can i start gathering logcat output through init.rc ?
A couple of things that could be causing problems above:
1. you defined your service to be called logcat. That looks awfully close to what might be a reserved/pre-existing name. I would choose a more distinguished service name.
2. there is no explicit start trigger for the service, hence its entirely dependent on the context in which its defined (i.e. which init phase). Pick the wrong phase (i.e. too early) and /mnt may not even exist.
3. the service will by default be running as root and thus the logcat.log file will be rw only by root. Not good to run processes as root. And not good to force readers to be root in order to read the log file.
Here the approach I've used to achieve what you're looking to do.
Problem: Ordinarily, Android log messages remain in the kernel’s (volatile) memory only and thus doesn’t survive across reboots.
Solution: To retain those log messages across reboots requires them to be written to persistent storage (i.e. the filesystem). The following code defines such a service that is started by Android during init.
Step 1, define a service that the Android init process will spawn to do this activity. This goes in init.rc.
service persistentLogging /system/bin/logcat -r 1024 -n 9 -v threadTime -f /cache/logs/log
user system
group system log
disabled
Notes about the above:
it creates a service called persistentLogging (that will be referred to in the second step below) by the start trigger.
it requests logcat to do a rolling log file (consisting of 10 files / 1Mb each) in directory - /cache/logs (i.e. log, log.1, log.2, … log.9). Adjust to suit your needs.
the service is to run as system user. This means the log file will be read+write only by system. If your app has system privileges then you’ll be able to read the log file. I’ve also defined the service to be in the log group too since that seems appropriate although since the files are not readable by group its a moot point.
the service is initially disabled. It will be started by a trigger defined below
the service is NOT oneshot. Hence, should it die, Android will attempt to restart it.
Step 2, define a trigger for starting the service. This also goes in your init.rc file.
on post-fs
mkdir /cache/logs 0775 system log
start persistentLogging
Notes about the above:
the commands are triggered during the ‘post-fs’ phase so that they occur after filesystem partitions have been mounted and when other system directories are having their permissions changed. Ideally, this service should start as late as possible because its not important or used by any other start-up activity.
the trigger first creates the target directory before starting the service. Remember the mkdir command syntax is defined by the init.rc language. In Android this syntax is not a sh syntax eventhough it looks a lot like it.
eventhough the above logging service doesn’t start until the post-fs phase of init, it will nevertheless dump all logging information since the beginning of kernel's startup as these log messages are already in the kernel buffers and this logging service is merely copying those messages to a file.
although both code fragments above ultimately need to appear in init.rc file, it is more maintainable if those additions are made to the init.${ro.hardware}.rc file defined for the device. e.g. init.freescale.rc which is automatically included by init.rc.
If we need to start the logcat and collect all the log buffers after on post-fs-data/boot completed from init.rc you can use below code in init.rc file.
on property:dev.bootcomplete=1
start logging
on post-fs-data
start logging
service logging /system/bin/logcat -b all -v threadTime -f /data/directory_name/log -r 100000 -n 9
user system
group root system log
disabled
My Android App is getting a bit hefty and it runs slow. I haven't got round to optimizing yet. I'm still ironing out some bugs.
Just some background: my app is a game, that the user can win or lose (naturally). I havent made a "win sequence" or "lose sequence yet" so I just put the code in
if(userWins())
this.finish();
where the app exits abruptly. Nothing wrong so far. But, when I try to open the app again I get a blank screen, when I should get option buttons. If I turn off my phone and turn on again I can use the app, but otherwise I can't. I have no idea why this is.
Side note: My copy of AngryBirds has a similar problem. If I exit the game screen "improperly" (i.e. while in the middle of a game, not in the options menu) I cannot turn on the app again until I reboot my phone.
When you call finish() Android don't kill the process yet. In case you restart your application, the process will be kept running unless the OS need the ressources.
see: http://developer.android.com/reference/android/app/Activity.html#ProcessLifecycle
This is why it works when you reboot. (you do not need to reboot, just kill the process using adb shell kill <PID> or a task killer if you aren't root)
Your application is likely leaving some variables initialized with old values, and when you restart your activity you find yourself in an unstable state with variables pointing to the old data and other variables to newly initialized data.
You need to either set the old data to null in the onDestroy() event, either ensure initialisation is done in the onCreate() event. You choose wich one depending on if the data can be reused or not.
Look for singletons, or for this kind of initialisation structures:
if (mVar == null) {
mVar = "stuff";
}