I have Activity, which is to save its data in case system decides to kill it while it is in the background.
So, I’ve got onSaveInstanceState:
#Override
protected void onSaveInstanceState(Bundle outState){
outState.putString("value", "some_value");
}
I check whether Bundle object is null in onCreate:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
isRestarted=(savedInstanceState==null);
How do I write test method? I tried
public void testRecreate(){
Instrumentation mInstr=this.getInstrumentation();
mInstr.callActivityOnSaveInstanceState(mActivity, null);
mActivity.finish();
mActivity=this.getActivity();
assertEquals(false, mActivity.isRestarted);
}
but it seems to be wrong.
You can use some hidden API functionality. In your test's setup, call the
android.app.ActivityManagerNative.getDefault().setAlwaysFinish()
method via reflection (because it's a hidden API) and confirm that the value was successfully set using
android.provider.Settings.System.getInt(getContentResolver(), Settings.System.ALWAYS_FINISH_ACTIVITIES, 0)
Then in the test cleanup, set this setting back to false.
Enabling the AlwaysFinish setting causes the system to destroy activities as soon as they are no longer on the screen, immediately triggering the onSaveInstanceState event. For this code to work, you will need the ALWAYS_FINISH and WRITE_SETTINGS permissions.
See the code for the SetAlwaysFinish tool linked in this blog: How to test onSaveInstanceState and onRestoreInstanceState on a real device
For manual testing, this routine works:
Launch the activity you want to test
Hit the home menu button on your device (activity is stopped)
Kill the process from DDMS (activity is still not destroyed)
Bring back your app again from the recent apps list
This should trigger onCreate again on your activity while not restarting the entire application. If you're not saving and loading the state properly, you'll know soon enough.
If you haven't been through it before, I found it worthwhile to do this at least once.
Stuff like that could be easily test using robolectric. Check it out!
Related
Im trying to integrate Tapresearch survey in my android app. The company provides an SDK. The SDK gives the ability to use events listeners. One of the listeners doesn't work which is onSurveyModalClosed()` where normally the app resumes running. All other listeners works fine except this one.
When Tapresearch open surveys it runs this activity : com.tapr.internal.activities.survey.SurveyActivity .
Is there any way check if this activity has been finished without using the listener onSurveyModalClosed() ?
For your question, "is there any way check if this activity has been finished without using the listener", yes there is a way.
Just put a Log message in both the methods. And see which Log message is printed in the console or which is printed first and take actions accordingly.
#Override
public void onSurveyModalClosed() {
//Survey isn't visible resume app
Log.i("Sequence_of_execution", "SurveyModalClosed");
}
#Override
protected void onStop() {
super.onStop();
// Your code goes here
Log.i("Sequence_of_execution", "ActivityStopped");
}
I guess onSurveyModalClosed() is called when you manually close the survey by taking any action and not when the Activity stops.
And for your question in comments to check if you are coming from SurveyActivity to MainActivity, do this since you don't have control over SurveyActivity-
Declare a shared preferences variable called "SurveyActivityStarted" in MainActivity and set it to false.
When starting the SurveyActivity from MainActivity, set "SurveyActivityStarted" to true.
on onResume of the MainActivity check if "SurveyActivityStarted" is true, if its true its coming from SurveyActivity.
Reinitialise "SurveyActivityStarted" to false in the MainActivity for future use cases.
I am not understanding how android activities are managed.
I have an activity and every once in a while i have noticed that, when my app goes into the background, android destroys whatever the current activity is (say Activity3) and several other singletons and objects etc. Thats fine. The problem is when the app is resumed then intuition tells me that since android has destroyed the activity and objects for memory or whatever, then android would just restart the app completely from Activity1 so all the objects and data members would get properly initalized.
NOT SO!
It seems that when my app is resumed, the Activity3 is recreated and onCreate is called with the same parameters as it was the first time (when it was called from Activity2) only this time all the singletons and other objects that were initialized in Activity1 and Activity2 are recreated with their default values and are rendered useless.
How is this a safe policy/technique? How can android just randomly destroy objects and activities and then when the user resumes just call onCreate on the recent activity and expect everything to be hunky doory and NOT have to go through the proper startup procedure/initialization?
UPDATE / SOLUTION
Thanks to the commentors for their excellent info.
ACCORDING TO ANDROID DOCUMENTATION
onCreate
Bundle: If the activity is being re-initialized after previously being shut down then this Bundle contains the data it most recently supplied in onSaveInstanceState(Bundle). Note: Otherwise it is null.
THEREFORE what I ended up doing is I set TWO flags. One in onSaveInstanceState in the Bundle so to know that it is a valid Bundle set by me. The other in the class itself to determine if onCreate was called because of recreation or Auto-Rotation. And so in onCreate I checked to see if onSaveInstanceState is not null, check the Bundle flag, and check bInit (which defaults to false). If both flags are true then it means android dumped and destroyed our apps memory and the safest way to ensure everything is initialized again in a linear-style application is to just restart it and launch the beginning activity.
public class SomeMiddleActivity extends AppCompatActivity
{
private static boolean bInit = false; // only way it will be false again is if android cleared our memory and we are recreating
#Override
public void onSaveInstanceState(Bundle state)
{
// set a flag so that onCreate knows this is valid
state.putBoolean("StateSaved", true);
super.onSaveInstanceState(state);
}
#Override
protected void onCreate(Bundle savedInstanceState)
{
// this must be called first always for some reason
super.onCreate(savedInstanceState);
if (savedInstanceState != null)
{
if (savedInstanceState.getBoolean("StateSaved", false) && !bInit)
{
// we were recreated... start app over
Intent intent = new Intent(getApplicationContext(), Startup.class);
startActivity(intent);
finish();
return;
}
}
bInit = true; // this will stay true until android has cleared our memory
.......
}
Although this has worked thus far, if anyone has a different suggestion let me know. I will be posting another article on this.
And FYI: the onSaveInstanceState(Bundle, PersistableBundle) version of onSaveInstanceState is never called ever so I dont know why they even implement it. (?)
#goldenb #Rishabh Thanks to goldenb and Rishabh for the insight.
Android, if destroys, also gives you tools to handle it.
Mobile devices have limited amount of memory which needs to be shared among Applications running simultaneously. Thus, smart resource allocation is necessary. The Apps running on foreground are used by End-User and gain high priority for better performance and user experience. Thus, applications running in background need to release the resources to suffice the memory requirements for foreground applications. Hence, background applications are destroyed (not completely) sometimes (in case of low memory).
Android Activities have Callbacks likes onSaveInstanceState() and onRestoreInstanceState() which enable you to save your current state of Activity (i.e., values of variables) when it is destroyed and retrieve them when the Activity is recreated.
You can get more information from here: How to save and retrieve the state of Activity using onSaveInstanceState and onRestoreInstanceState.
You can perform validations on the retreived state to ensure the Activity performs exactly as it was doing pre-destruction. You would find it very easy and logical once you get hands-on it.
Just giving my 50 cents on the issue. The correct way to deal with the issue of an activity being killed by the system for its resources in background is a common problem in android and according to Google the solution for this is:
onPause() is where you deal with the user leaving your activity. Most
importantly, any changes made by the user should at this point be
committed (usually to the ContentProvider holding the data).
Emphasis is mine. But what this means is that the Android lifecycles are designed so that under normal conditions onPause should be called as an Activity or Fragment is sent to the background. They hint at this in several of the android documentation pages:
As your activity enters the paused state, the system calls the onPause() method on your Activity, which allows you to stop ongoing actions that should not continue while paused (such as a video) or persist any information that should be permanently saved in case the user continues to leave your app.
Also worthy of your attention: if you wish that views are restored during Activity recreation, you should have set the ID attribute of all views ;)
Note: In order for the Android system to restore the state of the
views in your activity, each view must have a unique ID, supplied by
the android:id attribute.
PS.
You were wondering why onSaveInstanceState(Bundle, PersistableBundle) is not called, one possibility is that you do not have the right activity attribute set
This is the same as onRestoreInstanceState(Bundle) but is called for
activities created with the attribute persistableMode set to
persistAcrossReboots..
Can somebody clarify please why I have such a weird behavior. Up to documentation the Bundle savedInstanceState which is set in onSaveInstanceState() is alive as long as application alive, so when it it in foreground or background. After the application is being killed the savedInstanceState instance is killed as well. Here is what I have:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
if (savedInstanceState != null) {
Log.i("Dev", "not null");
} else {
Log.i("Dev", "null");
}
}
Here is how I set it:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("bool", true);
}
Then, I'm starting the application in the emulator. After application is opened I click home button so the Launcher is visible. Then I kill the application's process using adb. After that I start the application from the list of recently used application expecting for "null" in the Logcat, but what I actually see is "not null", so my understanding is incorrect?
The Bundle is saved for as long as Android wants it to be saved/can save it. One of the "features" (quotes because it ends up being a bad idea as often as it is a good one) of Android is that applications are never exited (to the user's view). Their mechanism of doing this is the onSaveInstanceState- it stores the Bundle, and when the app is later reinitialized by some method (such as from the recent activities menu) it will pass that Bundle to the onCreate and let it re-initialize itself.
Of course this also causes problems. For example, if you save login info, exiting an application won't log you out. So a user can then just hand his phone to a friend to watch a video, thinking that he exited his mobile banking app and is safe, yet the friend can call it back up and recreate it. If your app has large data structures in static variables or singletons they will not be recreated unless you code it carefully. Apps that require activities to be explored in order can be restarted from the middle.
Now Android can choose to forget your Bundle. If you put several MB in it, I would expect android to forget it rapidly. But it will remember it for as long as it can.
Isn't it very clearly stated here ? Or do I missunderstand your question?
[..]To save additional data about the activity state, you must override the onSaveInstanceState() callback method. The system calls this method when the user is leaving your activity and passes it the Bundle object that will be saved in the event that your activity is destroyed unexpectedly. If the system must recreate the activity instance later, it passes the same Bundle object to both the onRestoreInstanceState() and onCreate() methods.
I mean for me this is also reasonable in most situations. Because when your activity / app is in the background and the android system closes it (let's say because it needs more memory), then it first saves the state. So next time the users opens your activity, you can restore it's previous state (and that may also exactly be what the user wants, since it wasn't him who closed the activity, but the system itself).
If I press home and come back to my app a little later I will find that the state has been preserved perfectly. For some reason however if I lock the phone and then unlock it, my app has been returned to the original state bar a few things here and there. When I looked into the logs I found that onCreate had been called while the phone was in a locked state. Because locking the phone is quite an off hand thing to do, having your game reset every time you do so is not desirable to the user. How can this be avoided at least for a longer period of time than a few seconds after locking the phone?
This is how Android OS works, it decides by it's own when to destroy your view. To avoid loosing this information there is a Method that can be reimplemented in your activity
#Override
public void onSaveInstanceState(Bundle outState){
iGameContent.saveGame(outState);
}
Save all your needed data into outState, and in the onCreate method, check if its a new instance or saved instance, like this:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.game);
if (savedInstanceState!=null){
iGameContent.loadGame(savedInstanceState);
}else{
// Normal initialization
}
}
An example of the save/load to a Bundle is the following:
public void loadGame(Bundle aBundle){
iBadsHit = aBundle.getInt("iBadsHits",0);
}
public void saveGame(Bundle aBundle){
aBundle.putInt("iBadsHit", iBadsHit);
}
If your log is showing that onCreate has been called then that means your apps process was killed.
Do you know the Android Activity Lifecycle? If not, read up on it here: Android Activities
The behavior on screen lock could vary from one device to other. Some events could cause the destruction of the app. You can try to handle some of this events to avoid this situation specifying it on the AndroidManifest.xml:
android:configChanges="keyboardHidden|orientation"
These two are the most problematic in screen lock. Yo can find more information on the last chapter of this nvidia document
I have an issue. For analytic purposes I need to track when the APP (not activity) is resumed. The problem I have now is that if I put the tracker on the OnResume event of an activity, it will get fired every time the user goes back and forth on different activities.
How can I avoid that? How can I track the real "Application Resume," (when user actually exits the app and come back) and not the activity resume?
Any ideas is greatly appreciated. Thanks.
I encountered the same problem and solved it by creating base activity :
public class mActivity extends Activity{
public static final String TAG = "mActivity";
public static int activities_num = 0;
#Override
protected void onStop() {
super.onStop();
activities_num--;
if(activities_num == 0){
Log.e(TAG,"user not longer in the application");
}
}
#Override
protected void onStart() {
super.onStart();
activities_num++;
}
}
all the other activities in my app inherited mActivity. When an activity is no longer visible than onStop is called. when activities_num == 0 than all activities are not visible (meaning the the user close the app or it passed to the background). When the user start the application (or restarting it from the background) onStart will be called (onStart is called when the activity is visible) and activities_num > 0. hopes it helps...
Use the Application object of your app (see http://developer.android.com/reference/android/app/Application.html). If you create a custom Application class and configure it in your AndroidManifest.xml file you can do something like this:
Start tracking in the onCreate() of the Application object.
Instrument all your Activities so their onPause() and onResume() methods check with the Application object and see if they are the first Activity to run, or if they are continuing a previously running instance of the app.
Stop tracking in the onDestroy() of the Application object.
To a certain degree most of the analytics packages (Flurry and their ilk) do something similar to this. You'll need to do a little state machine work to get this to work right, but it shouldn't be too complicated.
Instead of OnResume(), hook into the OnCreate() event of your main activity.