There are only two things that can make a team united, fearful, or loyal. ---"Speed and passion 7"
Original link: http://engineering.meetme.com/2015/04/android-determine-when-app-is-opened-or-closed/
Problems that exist
Inevitably, Android development will encounter the need to check when the app enters the foreground and when it is closed by the user. Strangely enough, it is not easy to achieve this goal. It's not hard to check the app's first launch, but it's not as easy to tell when it's reopened and closed.
This article will describe a technique for judging the app to open, reopen, and close.
Let's get started.
The key to judging an app's opening and closing is to determine if its activities is being displayed in the foreground. Let's start with a simple example, an app that has only one activity, and doesn't support horizontal mode. To determine whether the app is open or close, you only need to check the activity's OnStart and OnStop methods:
@Overrideprotected void OnStart () { super.onstart (); The application has been opened!} @Overrideprotected void OnStop () { super.onstop (); The application has been closed!}
The problem with the above example is that the method fails when it is necessary to support horizontal mode. When we rotate the device, the activity will be rebuilt and the OnStart method will be called again, and the app will be incorrectly judged to be opened for the second time.
In order to handle the device rotation, we need to add a verification step. When activity exits, a timer is used to determine if the activity of the app has been launched in a short time, and if not, the user has actually exited the app, and if the activity is restarted, the user still stays in the app.
This check also applies to apps that have multiple activities, because jumping from one activity to another in the app can be handled in this way.
Using this technique I have created a management class, and all of the activities will be notified of this management class when it is visible and not visible. This management class handles the above verification steps for each activity, thus avoiding false detection. It also provides a publish subscription (viewer) mode, which can be used by any module that is interested in launching and closing the app.
The use of this management class is divided into three steps:
1) add it to your project
Import Android.app.activity;import android.os.handler;import Android.os.looper;import Android.os.Message;import Android.support.annotation.nonnull;import Android.text.format.dateutils;import Java.lang.ref.Reference;import Java.lang.ref.weakreference;import Java.util.hashset;import java.util.set;/** * This class was responsible for tracking All currently open activities. * By doing so this class can detect when the application was in the foreground * and when it was running in the background. */public class Appforegroundstatemanager {private static final String TAG = AppForegroundStateManager.class.getSimpleN Ame (); private static final int message_notify_listeners = 1; public static final Long App_closed_validation_time_in_ms = * Dateutils.second_in_millis; Seconds private reference<activity> mforegroundactivity; Private set<onappforegroundstatechangelistener> mlisteners = new hashset<> (); Private Appforegroundstate mappforegroundstate = appforEgroundstate.not_in_foreground; Private Notifylistenershandler Mhandler; Make this class A thread safe singleton private static class Singletonholder {public static final Appforegro Undstatemanager INSTANCE = new Appforegroundstatemanager (); } public static Appforegroundstatemanager getinstance () {return singletonholder.instance; } private Appforegroundstatemanager () {//Create the handler on the main thread mhandler = new Notifylis Tenershandler (Looper.getmainlooper ()); } public enum Appforegroundstate {in_foreground, not_in_foreground} public interface Onappforegro Undstatechangelistener {/** Called when the foreground state of the app changes */public void Onappforegro Undstatechange (Appforegroundstate newstate); }/** an activity should call the when it becomes visible */public void onactivityvisible (activity activity) { if (mforegroundactivity! = null) MforegroundactiviTy.clear (); mforegroundactivity = new weakreference<> (activity); Determineappforegroundstate (); }/** an activity should call the if it is no longer visible */public void onactivitynotvisible (activity activit Y) {/* * The foreground activity may has been replaced with a new foreground activity in our app. * So only clear the foregroundactivity if the new activity matches the foreground activity. */if (mforegroundactivity! = null) {Activity ref = Mforegroundactivity.get (); if (activity = = ref) {//This was the activity that was going away, clear the reference Mfore Groundactivity.clear (); Mforegroundactivity = null; }} determineappforegroundstate (); }/** Use-determine if this app was in the foreground */public Boolean Isappinforeground () {return MAPPFO Regroundstate = = Appforegroundstate.in_foreground; }/** * Determine the current state, update the tracking global, and notify subscribers if the State has C Hanged. */private void Determineappforegroundstate () {/* Get the current state */appforegroundstate oldstate = Mappforegroundstate; /* Determine what is the new state should is */FINAL Boolean isinforeground = mforegroundactivity! = NULL && Mforegroundactivity.get ()! = NULL; Mappforegroundstate = Isinforeground? AppForegroundState.IN_FOREGROUND:AppForegroundState.NOT_IN_FOREGROUND; /* If the new state is different and the old State, the Notify subscribers of the state, change * * if (Mappforegroun Dstate! = oldstate) {validatethennotifylisteners (); }}/** * Add a listener to being notified of app foreground state change events. * * @param listener */public void AddListener (@NonNull Onappforegroundstatechangelistener listener) {m Listeners.add (listener); }/** * Remove a listener from being notified of the app foreground state change events. * * @param listener */public void RemoveListener (Onappforegroundstatechangelistener listener) {Mlisten Ers.remove (listener); }/** Notify all listeners the app foreground state has changed */private void notifylisteners (appforegroundstate n Ewstate) {android.util.log.i (TAG, "notifying subscribers that app just entered state:" + newstate); for (Onappforegroundstatechangelistener listener:mlisteners) {listener.onappforegroundstatechange (newState); }}/** * This method would notify subscribes that the foreground state have changed when and if Appropria Te. * <br><br> * We don't want to just notify listeners right away when the app enters of leaves the Foregrou nd. When changing orientations or opening and * Closing the app quickly we briefly pass through a not_in_foreground state That's must be IGNored. To accomplish this a delayed message would be is * Sent when we detect a change. We'll not notify this a foreground change happened until the delay time have been reached. If a second * foreground change was detected during the delay period then the notification would be canceled. */private void Validatethennotifylisteners () {//If the app has a pending notifications then throw out the E Vent as the state change have failed validation if (Mhandler.hasmessages (message_notify_listeners)) {and ROID.UTIL.LOG.V (TAG, "Validation failed:throwing out app foreground state change Notification"); Mhandler.removemessages (message_notify_listeners); } else {if (mappforegroundstate = = Appforegroundstate.in_foreground) {//If the app entered th E foreground then notify listeners right away; There is no validation time for this mhandler.sendemptymessage (message_notify_listeners); } else { We need to validate that the app entered the background. A delay is used-allow-for-time when the application went into the//background and we do not want to co NSider the app being backgrounded such as in apps purchasing flow and full screen ads. Mhandler.sendemptymessagedelayed (Message_notify_listeners, App_closed_validation_time_in_ms); }}} Private class Notifylistenershandler extends Handler {private Notifylistenershandler (Looper loo Per) {super (Looper); } @Override public void Handlemessage (Message inputmessage) {switch (inputmessage.what) { The decoding is do case message_notify_listeners:/* NOTIFY subscribers of The state Change */ANDROID.UTIL.LOG.V (TAG, "App just changed foreground state to:" + mappforegrounds Tate); Notifylisteners (mappforegroundstate); Break Default:super.handleMessage (InputMessage); } } }}
2) Activities need to send notifications when visibility changes
All activities in the app should add the following code to notify the management class when visibility changes. The best way to achieve this is to add this code to the baseactivity of the project.
@Overrideprotected void OnStart () { super.onstart (); Appforegroundstatemanager.getinstance (). onactivityvisible (this);} @Overrideprotected void OnStop () { appforegroundstatemanager.getinstance (). onactivitynotvisible (this); Super.onstop ();}
3) Subscribe to the app's foreground visibility Change event
Subscribing to the app foreground visibility Change event in the module of interest, the OnCreate function of the application class is a good place to ensure that every time your app starts and closes, you'll be notified.
public class MyApplication extends application { @Override public void OnCreate () { super.oncreate (); Appforegroundstatemanager.getinstance (). AddListener (this); } @Override public void Onappforegroundstatechange (Appforegroundstatemanager.appforegroundstate newstate) { if (AppForegroundStateManager.AppForegroundState.IN_FOREGROUND = = newstate) { //App just entered the FOREGROUND. Do something here! } else { //App just entered the background. Do something Here! }}}
Further thinking
There are some details that need to be discussed further, and some of the points discussed below can be fine-tuned for specific applications.
Calibration time
Checking the timer check to see if the app really gets into the background how much time interval is appropriate? The above code is set to 30 seconds for the following reasons.
When your app is running, there may be third-party activities that cover the full screen, and some common examples are Google in-app purchases and the Facebook sign-in registration page. In these cases your app will be forced into the background and the foreground is used to display these third-party pages. It's obviously wrong to think of this as a user leaving your app. The 30-second timeout setting is used to avoid this situation. For example, when a user completes an in-app purchase within 30 seconds, most of the users can do it, and the user does not suddenly leave the app.
If this is not the case for your app, I recommend that you set your calibration time to 4 seconds, which is appropriate for low-provisioning devices when the screen spins the time interval to recreate the activity.
CPU hibernation
A possible problem is when the user closes the app or the app is still in the foreground when the user locks the screen, and the CPU may not wait for the timer to detect and hibernate. In order to ensure that the timer can normally detect the user exiting the app in this case, we need to hold wakelock to prevent the CPU from hibernating until the app shutdown event is confirmed. This is not a problem in practice compared to the use of wakelock.
Determine how the app is started
Now that we know how to detect when the app starts and shuts down, we don't know how the app started. is the user clicking on the notification bar message? Or click a link? Or do they start directly from the desktop icon or recent use?
Trace startup mechanism
First we need to know where to check how the app is started. Based on the previous example, we can print out when the app starts and how it starts.
public class MyApplication extends application {public final String TAG = MyApplication.class.getSimpleName (); public enum Launchmechanism {DIRECT, NOTIFICATION, URL; } private Launchmechanism mlaunchmechanism = Launchmechanism.direct; public void Setlaunchmechanism (Launchmechanism launchmechanism) {mlaunchmechanism = launchmechanism; } @Override public void OnCreate () {super.oncreate (); Appforegroundstatemanager.getinstance (). AddListener (this); } @Override public void Onappforegroundstatechange (Appforegroundstatemanager.appforegroundstate newstate) {i F (AppForegroundStateManager.AppForegroundState.IN_FOREGROUND.equals (newstate)) {//APP just entered the Foreg Round. LOG.I (TAG, "App Just entered the Foreground with launch mechanism of:" + mlaunchmechanism); } else {//App just entered the background. Set Our launch mode back to the default of direct. Mlaunchmechanism = Launchmechanism.direct; } }}
Setting the startup mechanism
Now we can print the mechanism when the app starts, but we don't have to set it up. So the next step is to write it down when the user launches the app via a link or notification. If not set in either way, the user is launched by clicking on the app icon.
Track Link Click events
To keep track of the user clicking the link to open the app, you need to find the place where the link is handled in the code and add the following code to track the startup mechanism. Make sure that the code is called before the activity's OnStart () function. Where to add the following code depends on your app architecture.
Getapplication (). Setlaunchmechanism (Launchmechanism.url);
Tracking Notification Events
Unfortunately the tracking notification click requires more tricks, and when the notification is displayed, clicking on it will open a pendingintent that was previously bound, and the trick here is to add an identity to all pendingintents of the notification indicating that it was issued by the notification.
For example, when creating pendingintent for notifications, add the following code for each intent:
public static final String extra_handling_notification = "notification.extra_handling_notification";//Put a EXTRA so we Know when an activity launches if it was a from a Notificationintent.putextra (Extra_handling_notification, true);
What we need to do in this step is to check this identity in each activity (unified in baseactivity). When this identity is recognized, it is initiated from the notification, and the startup mechanism can be set to pass notification. This step should be handled in oncreate so that it is set up before the app starts to the foreground (it will trigger the startup mechanism to print).
@Overridepublic void OnCreate (Bundle savedinstancestate) { super.oncreate (savedinstancestate); Intent Intent = Getintent (); if (intent! = null && Intent.getextras ()! = null) { //Detect If the activity is launched by the user ClickIn G on a notification if (Intent.getextras (). Getboolean (Extra_handling_notification, False)) { //Notify the Activity was opened by the user clicking on a notification. Getapplication (). Setlaunchmechanism (Launchmechanism.notification);}}
This article is nearing the end, and here you should have mastered how to detect when the app started and shut down, and how it started.
Android to determine when the app is open or closed technology research