I'm trying to implement some automatic logout code for my Application on Android.
I need to detect if all the activities belonging to an Application have entered the background as opposed to working with onPause() and onResume() for each individual activity. iOS has a helpful applicationDidEnterBackground: method that I could utilize, but I'm unable to find a similar function in Android's Application class.
One approach seems to be to have an AtomicInteger counter and increment it once an activity becomes visible and decrement it when it's finished or onStop() gets called. So if the counter becomes zero, I can start a service that runs in the background and handles the logout. Is this how it's usually done?
There is no global callback for this, but for each activity it is onStop(). You don't need to mess with an atomic int. Just have a global int with the number of started activities, in every activity increment it in onStart() and decrement it in onStop().
You really don't want to log out the user when the "application" goes in the background, any more than you log out the user of a Web app when the user switches to another tab or minimizes their browser window for a moment. If you were to do either of those things in a Web app, your users would consider your Web app to be an epic fail. Similarly, if the user gets a phone call with a wrong number, or the alarm clock goes off, they'll be rather irritated with you if they have to immediately go back in and sign in when they were just using your app 5 seconds ago. Here, by "irritated", I mean one-star ratings on the Market and nasty comments.
A Web app automatic log out is based upon inactivity, using a server session cookie.
Similarly, when I build a secured Android app, I'll be implementing an inactivity-based mechanism, perhaps something like this:
Step #1: Create a Session class with a static singleton instance. The Session object holds the last-accessed timestamp.
Step #2: In each activity's onResume(), see if the Session singleton exists. If not, it's a brand-new process, so if this isn't the authentication activity, immediately do a startActivity() to bring up the authentication activity.
Step #3: Back in each activity's onResume(), if the Session object exists, call something like extend(). This would return a boolean, true indicating the session is still good (and the timestamp has been updated to now), false otherwise. If it returns false, do the same stuff as if the Session object were null.
Step #4: Your authentication activity, upon success, sets up the singleton Session object with the current timestamp.
Step #5: Your Session class' extend() method is where you make the determination if the session is too old.
No matter how the user gets into your application, if the session is too old (or it's a brand-new process), they are forced to authenticate. Yet, if the user briefly is interrupted -- where you and/or the user can define "briefly" -- they don't have to re-authenticate.
I think your suggestion is probably the best way to go. Unfortunately I don't think there's an API call to detect if your app is in the background or not. You'll just have to manipulate the onPause() and onResume() methods. Just keep in mind that you'll need need to account for transitions between activities, so once your AtomicInteger reaches 0, I'd wait a short amount of time and recheck that it's still 0 to make sure it wasn't just transitioning activities.
Create an Application class and include in the manifest
<application
android:name="com.example.hello.MyApplication"
public class MyApplication extends Application implements
ActivityLifecycleCallbacks, ComponentCallbacks2
override the following method
#Override
public void onTrimMemory(int level) {
// this method is called when the app goes in background.
// you can perform your logout service here
super.onTrimMemory(level);
}
this is valid of API level 14 and above.
You can even perform the the logout based on the amount of time the app is in background, which i would suggest is a better option. here is what you can do to create as "session timeout"
save the time stamp in SharedPreferences inside the onTrimMemory(int level) method
on all your activities onStrat() get the time stamp from sharedPref and compare it with current time. based on this you can perform a logout.
and clear the shared pref on onCreat of MyApplication
Related
In the wonderful article by Chet Haase I read this advice which I find quite important:
never make a network request in your Application object. That object
may be created when one of the app’s Services or BroadcastReceivers is
started; hitting the network will turn code that does a local update
at a specific frequency into a regular DDoS.
The application I work on currently follows this (bad) practise: it performs a user login in Application.onCreate() - on a background thread of course, but still. This is a requirement: user needs to be logged in before any activity would do any other tasks, which usually depend on logged in user. I currently do this using RxJava in a way that any activity task observables are flatMapped onto an userlogin event and it works quite nice.
So if I should take that login task out of Application, where should it go? At first I thought it would be nice to use ActivityLifecycleCallbacks and watch for the first activity to be created. But this callback (onActivityCreated) will be called after creation, which is too late for me.
So I think that this should be done by creating some BaseActivity class and putting login and other initialization calls in it's first onCreate(). But I don't feel this is too good, because I'm mixing some app-wide logic in an activity class, it's smelly...
Anything I could have missed?
SplashActivity
An activity that starts the application. It checks for resources availability and if needed, obtains them. It also checks whether there is an active user session, and if there isn't performs a log in, if there are remembered credentials, or redirects the user to the Login/Register screen
BaseActivity
An activity that is specific for your app and that holds initialization and lifecycle callback code that is applicable for all your activities in the application.
In our application, we rely almost solely on data from our web service. On first launch, we start a LoginActivity where we handle logging in and retrieving this data before continuing onto the MainActivity where all of the UI that consumes the data begins.
We store the all of this data in a static data model, and it works great for what we need, however there are some instances when the application is killed off (due to memory constraints) while the Activity retains its state. So if I go and launch several other apps (to cause this to happen) then relaunch the app, it attempts to resume the MainActivity (which relies on the data from the service) and crashes due to fact that the application is no longer active and the data model no longer contains any data.
I've discovered that the Application.onCreate() method will be called in this case, so it seems like I'd want to handle returning to the login screen at that time, but it doesn't seem to be recommended practice to launch an activity from the application's context, not to mention that we'd already be in the process of resuming the activity.
What I would really like is for the application to not retain the activity state after the Application is no longer active (and all of the static data has been lost) and to just launch from the LoginActivity as expected.
This seems like a very common scenario; any suggestions on something simple that I may be overlooking, or any more info I can give?
I have the same problem.
Now I first check for static data in onResume() of MainActivity, while the static data is null I use startActivityForResult to open LoginActivity to prepare the static data, then continue when it returns Activity.OK in onActivityResult.
If you really want to have an app restarted to LoginActivity, first thing comes to mind is checking in every other Activity.onCreate:
if (yourStaticData == null) {
finish();
return;
}
I'd suggest not doing that and making app behave correctly when the process is killed.
clearTaskOnLaunch may be what You are looking for.
I'm reading article "Managing the Activity Lifecycle" (http://developer.android.com/training/basics/activity-lifecycle/index.html) and I'm little confused. Maybe first I'll try to say what my application do. So, this is some kind of http client. User login to the server and client store authorization (session id). After login, TimerTask is executing, which for every 10 seconds get some small json from server and by the way server know that authorization key is still alive (normally it is valid for ~30 minutes). In this json could be some events which should be shown to the user (I'm using notification manager for this) or questions for which user should answer (I'm showing custom dialog with "Yes", "No", "Don't know" and then POST answer to the server).
This works fine when my application is in foreground, but I don't really know what should I do if the application is stopped/paused.
My doubts:
I want that TimerTask should works even if user click home button or another application appear. There are two reasons: One - user need to be notified about events, two - I need to keep alive session id. But this article say that when activity is in foreground, it should release resources. What this mean? What are restrictions? Must I stop my timer?
Documentation say that system can kill application when it is no longer needed. What does it mean no longer needed? When user don't use it or when application code do nothing for a while? Can my TimerTask keep alive application?
Storing authorization key. I need remember session id in situations where activity is recreated by system like orientation change, etc. I'm using for this SharedPreferences object. Problem is that using this object, key is saved in database and I can't recognize when my application is closed permanently (which mean "logout") or just recreated because orientation is changed. This occur situations when user run application again after couple hours and my activity restore dead session id (my application look like "logged in" because authorization variable is not empty and I use this state as a flag). I need some temporary version of SharedPreferences object. What about bundle object passed in onSaveInstanceState? Is it temporary?
Regards
It looks like you are on the right track. I think that you should continue reading documentation. Some suggestions:
Activity life cycle
Context.bindService()
ServiceConnection
On the first time my app is running, on the root activity, the user is required to select a certain options that determines which data would be downloaded for him from a database.
Once he had picked that option, the data is downloaded and kept on a singleton class that should hold that data as long as the application is running. (Even in the background)
My problem is that sometimes after the user exits my application for a while (using the home button), Android apparently kills some of the activities in my app, and somehow with them, it resets the important singleton class. Causing my app to receive all kinds of null reference exception once I try to access the data that is supposed to be kept on the singleton.
I understand that the Android OS can sometimes choose to kill an application, but allow the user to return to it from his last visited activity. Sometimes it just kills the whole application and forces the user to begin from the start.
I was looking for a solution, and I found out about "android:alwaysRetainTaskState" attribute that I can apply on my root activity. Apparently with it, whenever android decides to kill my app, it would at least force the user to begin from the first activity where I can re-download the data, instead of allowing the user to begin with a more advanced activity causing him to get null exceptions.
However when I applied it on my application it did not work, and when I returned to my application I was beginning from an advanced activity, with an empty singleton instance.
I also tried "android:clearTaskOnLaunch" and it worked, but it's an overkill, since I don't want that every time the user would return to the app, he would have to start over. I want it to happen only if android decided to kill the app.
Is there something I'm doing wrong? Is there any chance maybe there's a better solution for keeping the singleton alive?
Thanks!
Nope. There is not better solution. This is how it works. Android can kill your application's process whenever it wants to (as long as it is in the background). You can keep data in a singleton, but it is always possible that Android kills your process anyway. When the user returns to your application Android will create a new process and then create a new instance of the activity that the user is resuming. You need to put code in onCreate() of all your activities that recognizes that your singleton is gone and then does an appropriate thing (either redirects to the root activity of your application or reinitializes your singleton.
It is pretty easy to tell if your process has been killed and recreated. Just add a static boolean variable to your singleton that you set to true when it has initialized. In onCreate() of all your activities, check the state of that variable and do something intelligent if the variable is false.
I am working on an Android app that deals with sensitive user information. One of the requirements is that the user is required to log back into the application whenever they leave it and come back. This is easily dealt with for the case when the user presses the Home button and then relaunches the app (android:clearTaskOnLaunch attribute on the Activity in AndroidManifest.xml). However, we need to do the same thing when the user long presses the Home button, switches to another application, then comes back.
I have researched this every way that I can think of and have not found a workable solution. Is this even possible with Android?
When answering, please keep in mind that this is a business requirement which I have no control over.
Well, I had the same problem yesterday. This is what I did and it works fine:
Added android:launchMode="singleTask" to the main activity in the AndroidManifest.xml
Called my boss and say: ey, this is going to take a long while... hold on!
Went and drank beer all night.
Just to clarify, my main activity only has a button that says login and launches the login page.
What have you tried? You can always clear whatever session you are saving in the proper Activity lifecycle method.
If I understand you correctly that you want to require an authorisation every time someone backs into the app, whether afresh or coming back to it, then you can override the onRestart() activity lifecycle event on the activity (or activities). So in onRestart() you can redirect the user to the login screen (you may also wish to consider onResume() depending on your requirements)
The lifecycle chart on this page will make this clearer:
http://developer.android.com/reference/android/app/Activity.html
Would it be possible to make it a time based thing, rather than strictly left the app and returned?
You could have a separate service that keeps track of when the last time the user accessed the application was.
I.e, in each onPause the Activity tells the service that an Activity was paused. The service records the time of that.
In each onResume, the Activity informs the Service that it wishes to resume. If some amount of time has passed since the last onPause, then the Service indicates that a login is required.
I think this would make a nicer user experience than just every time they leave the app. That could be very frustrating, to take 30 seconds to read a text, and then have to sign in again.
I suppose if you tweak it to have the timeout be very short, it has very similar behavior to what you requested anyways, but with the option of making it less draconion.
I think the easiest way to implement this, would be to add a field to your main activity like private boolean isLocked = true;.
To lock the app when another one is shown, set isLocked = true in the onPause() method. To make sure, that your don't lock your app, when returning from your own activities, start them via startActivityForResult() and unlock it in onActivityResult.
You can now check in onResume() wether your app is locked and redirect the user to your login screen.