I'm developing an Android application and I would like to emulate the same behavior of the official Google Plus app when it is opened, which is:
If the app is still in the Recent Apps list and you open it (from icon or from recent apps, it doesn't matter) you will resume the app from where you left.
If the app is no longer in the Recent Apps list and you open it, you will be greeted with a splash screen and then directed to the Home page.
My app has one activity that uses a navigation drawer and several fragments as pages. My goal is to retain the current fragment and display it when the app is resumed from the Recent Apps. I can manage to do so using SharedPreferences by storing the tag of the current fragment and loading it when the user reopens the app. The problem with is solution is that this kind of data is persistent so the user will always be greeted to the last page even if he first removes the app from the Recent Apps list and reopens it.
So how can I detect whether the app is already in the Recent Apps list or not? Or is there any method that is called when an app is removed from that list so I can clear the last fragment tag from SharedPreferences? Or am I using the wrong approach?
Thank you for you patience in reading this.
When you return to your app through the recent-tasks list, there are three possibilities:
It has been a long time, over 30 minutes, and your process is not running. In that case, Android just launches your app as if the user tapped on your home screen launcher icon. You are not given your saved instance state, as it is deemed to be stale.
It has not been a long time, so Android wants the user to think that your app has been running since they left it. However, due to memory pressure, Android terminated your process while it was in the background. In this case, Android forks a new process for you, creates an instance of your activity, and passes to you your saved instance state Bundle in onCreate() and onRestoreInstanceState().
Your process is already running. In that case, your activity is just brought back to the foreground. You do not need your instance state, as your activity instance is still running. Android has not touched your widgets, and so if your UI is not the way you want it to be, that is your own fault, because you did something (e.g., in onPause() or onStop()) that screwed up your UI. From your descriptions, I am interpreting it that you are testing this scenario, in which case onRestoreInstanceState() is not called, because it is not needed.
So, as long as you do not screw up your own UI, your app will work as you described it for Google Plus, so long as you handle the saved instance state for scenario #2 above.
On Android 5.0+, there are related scenarios tied into the PersistableBundle that you can use with variations of onSaveInstanceState() and kin. I have not played with the PersistableBundle much and so I do not have any particular advice related to it.
As it turns out, the behavior I mentioned is automatic and there was a problem with my project that prevented it: this was the activity in my AndroidManifest.xml
<activity android:name=".MainActivity"
android:label="#string/app_name"
android:theme="#style/AppTheme.NoActionBar"
android:noHistory="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
android:noHistory needs to be set false:
A value of "true" means that the activity will not leave a historical trace. It will not remain in the activity stack for the task, so the user will not be able to return to it. - Documentation
Related
I have many "internal" activities in my Android application that I only want to be started from inside my application by code I've written. These "internal" activities have no intent-filter tag in the Android manifest file. I have one activity, named SplashActivity, that I use as a splash screen that has the typical launch intent-filter:
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
I was hoping/expecting that whenever Android launched my app and created my custom Application object, it would always start my SplashActivity. However, some of my users have encountered launches where one of my "internal" activities which have no intent-filter are started. I believe that activity was typically the last activity used in a previous invocation of the app. I have not been able to reproduce the issue myself. However, is there some scenario where Android will launch my app, creating my custom Application object, but starting one of my internal activities that has no intent-filter. Under what circumstances will Android do so?
To easily reproduce the scenario where Android application launch starts an activity that has no intent-filter, first open your application to any such activity. Press the Home button. Then using Android Device Monitor (DDMS) to find the process that is running your application and stop/kill that process. Then launch your app. Android will create your Application object but will start/restore the activity that was last displayed instead of the starting the one with the MAIN LAUNCHER intent-filter.
Android can kill the OS process hosting your app at any time. Usually this happens when your app has been in the background for a while (ie: the user navigated away from your app to go do something else). This happens all the time.
When the user then returns to your app, Android creates a new OS process for the app, and creates a new instance of the Activity that was on the top of the stack (ie: the Activity that was showing on screen before the app was pushed to the background).
If you don't want this to happen, you can add the following attribute to the <activity> declaration for SplashActivity:
android:clearTaskOnLaunch="true"
This will force Android to always restart your app from the beginning if your user returns to it. However, this might make your users complain, because if the user is using your app, then takes a phone call, then returns to your app, it will start from the beginning again.
It is better if you detect the problem yourself, and redirect to the SplashActivity only when necessary (ie: when your app needs to be initialized because the process has been killed and restarted). To do this, declare a static variable named initialized in SplashActivity that you set to true when your SplashActivity has successfully initialized the app. In every other Activity, do this in onCreate():
super.onCreate(savedInstanceState);
if (!SplashActivity.initialized) {
// Android killed my process, need to redirect the user to `SplashActivity`
redirectIntent = new Intent(this, SplashActivity.class);
startActivity(redirectIntent);
finish();
return;
}
This happens when the application is still "alive".
When you exit an application, Android does not kill it. It will remain there until the memory is needed.
In this case, when the use re-launches it he will get the last Activity.
Can you try adding
android:clearTaskOnLaunch
for the root activity in your manifest file.
I have an LogIn activity screen for a user to log into my app.
I wrote this in my manifest as I want the app to start on this logIn activity:
<activity android:name=".LogInActivity" android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".UserPostActivity" android:label="#string/app_name"></activity>
Once the user logs on, the app saves a token / boolean into SharedPreferences so that the app knows the user has logged in already and doesn't not load the logon activity next time the user starts my app - instead it will go to the UserPostActivity which will show user posts. It starts the UserPostActivity using an intent.
This is typical of apps like Facebook where you login once and then it moves onto the user Feeds each time you use your app.
I'm unsure if this is the most efficient pattern to code the app as then it always has to go to LogInActivity first, check the boolean / token and then use the intent to move to UserPostActivity each time a logon user uses the app. I'm concerned about the impact on startup time of the app with this current flow.
Is there a better way to code this?
Making the decision whether or not to show the login activity cannot be done inside the LogInActivity because it is always launched because the the deciding takes place inside it.
Normally the flow is to start with a SplashActivity, where you do these kind of checks.
In SplashActivity, you can show a splash screen with your app's logo and a progressbar for example, in the meanwhile you check if it's the first start or not. If it is, continue to LogInActivity, if it's not the first time, continue to UserPostActivity.
If you are worried about impact on loading time, you can make a splash activity without a UI so that it doesn't have to parse an xml file and set up the UI. Read more about that here Must every activity have a layout?
By the way, check out this Once library. It was made for handling actions that should only happen 'Once'.
There are a number of ways you might structure this, depending on your requirements.
If you are concerned about the user seeing the login screen every time they open the app you can prevent this by starting your UserPostActivity inside the onCreate of your LogInActivity - this should ensure the user sees the 'UserPostActivity' straight away (and should reduce load time as you can do it event before setContentView).
Depending on your activity stack it may become annoying to have the logon activity behind the UserPostActivity so you may want to make sure you finish() the LogInActivity as soon as you are done with it.
An alternative structure you could use is have the UserPostActivity appear first and then startActivityForResult the LogInActivity. The LogInActivity can then return with the success/failure of logon. A note about what this will look like visually - the user will see the same as a 'back' animation from the logon activity to the UserPostActivity. This may be something you desire or not... You can obviously always change animations around but it's nice to have your coded structure match the visual structure you show the user (and you get the benefit of always having the 'system' animations that the user will be familiar with).
Another alternative idea:
Start with the UserPostActivity and check for your credential. If you don't find it, move to the LogInActivity and finish() the UserPostActivity immediately. After the login is complete, it can start the UserPostActivity again. This should give you a forward flow through your app and will give you the UserPostActivity loaded first, which is correct 99% of the time...
I hope that gives you some ideas, make sure you check that the app behaves correctly when you background it and open it from the launcher icon again. There are a bunch of issues related to this that might trip you up!
I have found very strange bug with the Android application, while installing the application in Android Device with given flow.
Installed the app from playstore(old version).
Launched the application(Now I'm in the HomeScreen,.i.e., HomeActivity).
Now, I upgraded the app by installing the latest build(under production build, yet to be released to playstore).
Click the launcher icon, wait till Home Screen becomes visible and keep the app in the background by pressing menu button.
Now, Launch the application by clicking launcher icon.
In this case, Splash Screen(Activity) gets launched and then takes me to the home screen. In the logs, call goes to onStop() of the HomeActivity. This means Activity is not destroyed. So, When I click launcher icon, it should resume the HomeActivity, instead it recreates the splashActivity. But when i launch the app from background, activity resumes and no splash screen gets displayed.
Is it a correct behavior? If so, then I'm not getting the callback to onDestroy().
Below is my activity code in the manifest:
<activity
android:name="com.app.ui.HomeActivity"
android:label="#string/app_name"
android:launchMode="singleTask"
android:configChanges="orientation|keyboard|keyboardHidden|screenSize|screenLayout|uiMode"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustNothing">
</activity>
<activity
android:name="com.app.ui.SplashActivity"
android:label="#string/app_name"
android:screenOrientation="sensorPortrait"
android:theme="#android:style/Theme.Translucent.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
I did not set any launch mode to SplashActivity. And after some time, it becomes proper and splash screen is not shown.
Also, when i launch the app from background or press back button and then click the app icon, this bug is not reproduced. This happens only when I click launcher icon, keeping app in the background and after some time, it becomes proper.
Also, when I installed the application without updating, this issue didn't occur. Only when I update the apk, this happens
Why this happens in the initial launches..Is it a bug in the android..?If so, How to avoid this issue..
The reason the app is closed after some time in the background is because your device's kernel is killing the process in order to run more processes in it's place. I am assuming you are accessing other apps while your app is running in the background, and that is why it is getting killed (and therefore skipping the onDestroy() call in the Android lifecycle!).
From the documentation on Android Developer's guide to Activity Lifecycle for onDestroy():
Note: do not count on this method being called as a place for saving data! For example, if an activity is editing data in a content provider, those edits should be committed in either onPause() or onSaveInstanceState(Bundle), not here. This method is usually implemented to free resources like threads that are associated with an activity, so that a destroyed activity does not leave such things around while the rest of its application is still running. There are situations where the system will simply kill the activity's hosting process without calling this method (or any others) in it, so it should not be used to do things that are intended to remain around after the process goes away.
Link to documentation
I am creating a cross platform app in Adobe Flex with AIR 3.3 SDK. For Android, I have created a native extension which launches a native android activity with a simple view in it by firing an Intent.
The issue is that when we minimize the app and resume it by either choosing the app icon from apps menu or by long pressing home button to get list of recent apps, Android automatically calls the onDestroy of the native android activity and kills it. The same scenario works perfectly fine on Android 4.0. The problem is happening only on Android 2.x devices. We haven't been able to test honeycomb yet.
After reading the Android documents, I understand that onDestroy is either called when someone calls finish on the activity or if the device is running low on space. And, to distinguish between the both, one can use the isFinishing() method. This would return true if activity has been destroyed by calling finish.
In our case, in the scenario described above, activity's finish function is not called. onDestroy is called when the app resumes but isFinishing() still returns true. This suggests that the app is not low on memory. I have tried adding all kind of Flags to the intent but nothing changes the behavior.
I have no idea from where else can the onDestroy function be called? I need to figure out a way to stop the native activity from being destroyed on app resume.
The code to launch the native activity is something like this:
Intent intent = new Intent("android.intent.action.K12");
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.putExtra("params", parameters);
context.getActivity().startActivity(intent);
The entry for the activity in the manifest goes like this:
<activity android:name="com.k12.main.MainActivity">
<intent-filter>
<action android:name="android.intent.action.K12" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
I have read a lot of blogs and SO questions but could not get around this issue. Please help.
I want to detect each time the user opens the app, by tap on home / desktop icon. There seem not to be a straight forward way to do it. Found a few workarounds but nothing seems to be really reliable.
Things like, extend application object and use method "onCreate()", but this is not what I need because it's not called always when the user taps on the app's icon (can be just brought from the background, launching doesn't necessarily recreate the application), and also the application may be destroyed and recreated while running. Then Application.onCreate() will also be called.
There war also some approaches involving BroadcastReceiver and checking intent flags but everything seems to be also not quite reliable?
I need this because I want to track with Google Analytics, when the user opens the app.
Thanks
Whenever your application is launched by normal way [if user taps on icon on home launcher] then main Activity for which
<activity
android:name=".xyz"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
is defined, in this application onCreate method will surely be called. So here you can put you Google Analaytics tracking code.
In other ways, like broadcast receiver it really depends upon which activity is called upon and whether it is start of application. There too you can put in onReceive method
As far as i feel, Android has definate way of start an app, and its always reliable. Only lifecycle of Android are bit tricky.
Try to look into "android application lifecycle".
But onResume is launched every time you start your activity
Else try : onStart which is called every time your application has been sent to "background". It really does state so in the developer docs.