About The Home Button
Every once in a while, somebody will ask “Can I override the Home button?”. The response usually reads like this:
Of course you can’t! Handling the back button is already hard enough as it is, can you imagine what the world would look like if developers were allowed to mess with the home button!
Behind this snarky answer lies a couple of truths:
- It would break the user’s expectations if the home button didn’t return home directly
- Users could get trapped in an app
- It would not cover every cases
- What if the user leaves the app because of a phone call?
- What if the user turns off the phone?
- What if the user switched to another app by tapping on the “recents” button?
So, in hindsight, unless you are writing a kiosk1 application, what you are probably looking for is a way to know when the user is leaving the Application. This would be so easy if the application had an onStop()
method, right?
The Missing Application onStart() and onStop()
Actually, why stop at onStop()
? Let’s go one step further and try to map the whole foreground/background lifecycle so that we always know in what state we are and when that state changes.
Why would you want that? Well, let’s say that your app has a messaging feature. If you receive a message and the app is in foreground, you might want to show a notification in your user interface. On the other hand, If the app is in background, showing a notification would be more appropriate.
Other times, you might want to be notified of the whole foreground/background lifecycle to track session lengths. Or you might want to know if the user is moving on to something else so that you can clear your caches.
Thankfully, you can get this information in a reliable way without resorting to crazy solutions like using ActivityManager#getRunningTask
or instrumenting the activity lifecycle to detect when an Activity#onStop()
is not followed by an Activity#onStart()
fast enough.
Getting Notified When The App Goes In Background
Starting with API Level 14 (Android 4.0 / ICS), we have access to the Application#onTrimMemory(int level)
method. This method contains an interesting level called TRIM_MEMORY_UI_HIDDEN
that can be used to know that we are going to the background.
Here is an example of a custom Application
class:
public class MyApplication extends Application {
// ...
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if (level == TRIM_MEMORY_UI_HIDDEN) {
isBackground = true;
notifyBackground();
}
}
// ...
}
Yay!! Now you can know for sure that your app is being sent to the background!
EDIT: Getting Notified When The Screen Turns Off
As noted by Guillaume Imbert in the comments, onTrimMemory
is not called when the screen gets turned off. To handle this, you will need to register a BroadcastReceiver
on Intent.ACTION_SCREEN_OFF
.
public class MyApplication extends Application {
// ...
@Override
public void onCreate() {
super.onCreate();
// ...
IntentFilter screenOffFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (isBackground) {
isBackground = false;
notifyForeground();
}
}
}, screenOffFilter);
}
}
Note that there is no need to listen for Intent.ACTION_SCREEN_ON
, it’s all handled down below
Getting Notified When The App Returns In Foreground
There is no flag or trim level to know that we are re-entering the foreground. The best that we can do is wait for an Activity#onResume()
.
We could add all that code in a base Activity but that is not necessary. It would be cleaner to keep the foreground logic close to the background logic, so we will take advantage of Application#registerActivityLifecycleCallbacks()
. This method allows you to add an ActivityLifecycleCallbacks
which, as the name implies, calls you back for each and every lifecycle event. In our case, it means that we can have code that will run for every Activity#onResume()
without modifying any of our activities.
Here is what it would look like in our custom Application
class:
public class MyApplication extends Application {
// ...
@Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
// ...
@Override
public void onActivityResumed(Activity activity) {
if (isBackground) {
isBackground = false;
notifyForeground();
}
}
// ...
});
}
// ...
}
Tying It All Together
Alright, here is a more complete sample to be notified when the app enters foreground and is sent to background.
public class MyApplication extends Application {
// Starts as true in order to be notified on first launch
private boolean isBackground = true;
@Override
public void onCreate() {
super.onCreate();
listenForForeground();
listenForScreenTurningOff();
}
private void listenForForeground() {
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
//...
@Override
public void onActivityResumed(Activity activity) {
if (isBackground) {
isBackground = false;
notifyForeground();
}
}
//...
});
}
private void listenForScreenTurningOff() {
IntentFilter screenStateFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
isBackground = true;
notifyBackground();
}
}, screenStateFilter);
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if (level == TRIM_MEMORY_UI_HIDDEN) {
isBackground = true;
notifyBackground();
}
}
private void notifyForeground() {
// This is where you can notify listeners, handle session tracking, etc
}
private void notifyBackground() {
// This is where you can notify listeners, handle session tracking, etc
}
public boolean isBackground() {
return isBackground;
}
}
In a Nutshell
- Target API 14
- Use
Application#onTrimMemory(int level)
andTRIM_MEMORY_UI_HIDDEN
to know when your are being sent to the background - Handle the screen turning off by registering a Broadcaster receiver for
Intent.ACTION_SCREEN_OFF
- Register an
ActivityLifecycleCallbacks
to know when the app comes back to the foreground - Don’t try to override the home button
- Be a nice person
Oh and big thanks to Ben Oberkfell for reviewing this post!
-
A kiosk application is basically having an app from which the user can’t escape. It is named that way because it usually means that a tablet will be embedded in a kiosk to display only one app. Think about “Point of Sales” scenario like ordering food in a restaurant, buying a train ticket, etc. If you are in this situation, the best way to go is to configure your app to be a single-use device. All the steps to achieve that are outlined in this codelab ↩