您的位置:首页 > 移动开发

Android中判断app何时启动和关闭的技术研究

2015-05-08 16:12 295 查看
只有两种东西能让一个团队团结,恐惧或忠诚。—《速度与激情7》

——欢迎转载,请注明出处 http://blog.csdn.net/asce1885 ,未经本人同意请勿用于商业用途,谢谢——

原文链接:http://engineering.meetme.com/2015/04/android-determine-when-app-is-opened-or-closed/


存在的问题

Android开发中不可避免的会遇到需要检查app何时进入前台,何时被用户关闭。奇怪的是,要达到这个目的并不容易。检查app第一次启动并不难,但要判断它何时重新打开和关闭就没有那么简单了。

这篇文章将介绍一种判断app打开,重新打开和关闭的技术。


让我们开始吧

判断一个app打开和关闭的关键在于判断它的activities是否正在前台显示。让我们先从简单的例子开始,一个只有一个activity的app,而且不支持水平模式。这样想要判断app是打开还是关闭只需要检查activity的onStart和onStop方法即可:
<code class="language-java hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">protected</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onStart</span>() {
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span>.onStart();
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// The Application has been opened!</span>
}

<span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">protected</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onStop</span>() {
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span>.onStop();
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// The Application has been closed!</span>
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li></ul>


上面例子的问题在于当需要支持水平模式时该方法就失效了。当我们旋转设备时activity将会重建,onStart方法将被再次调用,这时将会错误的判断为app第二次被打开。

为了处理设备旋转的情况,我们需要增加一个校验步骤。当activity退出时启动一个定时器,用于判断短时间内app的这个activity是否又被启动,如果没有,说明用户真的退出了这个app,如果重新启动了这个activity,说明用户还逗留在这个app中。

这种校验方式也适用于拥有多个activities的app,因为从app的一个activity跳转到另一个activity也可以用这种校验方式来处理。

使用这个技术我创建了一个管理类,所有的activities在可见和不可见时都会通知这个管理类。这个管理类为每个activity处理上述的校验步骤,从而避免错误的检测。它也提供了发布订阅(观察者)模式,任何对app启动和关闭感兴趣的模块都可以通过它来得到对应的通知。

这个管理类的使用分为三个步骤:

1)把它添加到你的工程中
<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">import</span> android.app.Activity;
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">import</span> android.os.Handler;
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">import</span> android.os.Looper;
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">import</span> android.os.Message;
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">import</span> android.support.annotation.NonNull;
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">import</span> android.text.format.DateUtils;

<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">import</span> java.lang.ref.Reference;
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">import</span> java.lang.ref.WeakReference;
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">import</span> java.util.HashSet;
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">import</span> java.util.Set;

<span class="hljs-javadoc" style="color: rgb(136, 0, 0); box-sizing: border-box;">/**
* This class is responsible for tracking all currently open activities.
* By doing so this class can detect when the application is in the foreground
* and when it is running in the background.
*/</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">AppForegroundStateManager</span> {</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> String TAG = AppForegroundStateManager.class.getSimpleName();
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> MESSAGE_NOTIFY_LISTENERS = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>;
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">long</span> APP_CLOSED_VALIDATION_TIME_IN_MS = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">30</span> * DateUtils.SECOND_IN_MILLIS; <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// 30 Seconds</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> Reference<Activity> mForegroundActivity;
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> Set<OnAppForegroundStateChangeListener> mListeners = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> HashSet<>();
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> AppForegroundState mAppForegroundState = AppForegroundState.NOT_IN_FOREGROUND;
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> NotifyListenersHandler mHandler;

<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// Make this class a thread safe singleton</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">SingletonHolder</span> {</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> AppForegroundStateManager INSTANCE = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> AppForegroundStateManager();
}

<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> AppForegroundStateManager <span class="hljs-title" style="box-sizing: border-box;">getInstance</span>() {
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> SingletonHolder.INSTANCE;
}

<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> <span class="hljs-title" style="box-sizing: border-box;">AppForegroundStateManager</span>() {
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// Create the handler on the main thread</span>
mHandler = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> NotifyListenersHandler(Looper.getMainLooper());
}

<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">enum</span> AppForegroundState {
IN_FOREGROUND,
NOT_IN_FOREGROUND
}

<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">interface</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">OnAppForegroundStateChangeListener</span> {</span>
<span class="hljs-javadoc" style="color: rgb(136, 0, 0); box-sizing: border-box;">/** Called when the foreground state of the app changes */</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onAppForegroundStateChange</span>(AppForegroundState newState);
}

<span class="hljs-javadoc" style="color: rgb(136, 0, 0); box-sizing: border-box;">/** An activity should call this when it becomes visible */</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onActivityVisible</span>(Activity activity) {
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (mForegroundActivity != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) mForegroundActivity.clear();
mForegroundActivity = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> WeakReference<>(activity);
determineAppForegroundState();
}

<span class="hljs-javadoc" style="color: rgb(136, 0, 0); box-sizing: border-box;">/** An activity should call this when it is no longer visible */</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onActivityNotVisible</span>(Activity activity) {
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/*
* The foreground activity may have been replaced with a new foreground activity in our app.
* So only clear the foregroundActivity if the new activity matches the foreground activity.
*/</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (mForegroundActivity != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) {
Activity ref = mForegroundActivity.get();

<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (activity == ref) {
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// This is the activity that is going away, clear the reference</span>
mForegroundActivity.clear();
mForegroundActivity = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>;
}
}

determineAppForegroundState();
}

<span class="hljs-javadoc" style="color: rgb(136, 0, 0); box-sizing: border-box;">/** Use to determine if this app is in the foreground */</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> Boolean <span class="hljs-title" style="box-sizing: border-box;">isAppInForeground</span>() {
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> mAppForegroundState == AppForegroundState.IN_FOREGROUND;
}

<span class="hljs-javadoc" style="color: rgb(136, 0, 0); box-sizing: border-box;">/**
* Call to determine the current state, update the tracking global, and notify subscribers if the state has changed.
*/</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">determineAppForegroundState</span>() {
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* Get the current state */</span>
AppForegroundState oldState = mAppForegroundState;

<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* Determine what the new state should be */</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">boolean</span> isInForeground = mForegroundActivity != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span> && mForegroundActivity.get() != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>;
mAppForegroundState = isInForeground ? AppForegroundState.IN_FOREGROUND : AppForegroundState.NOT_IN_FOREGROUND;

<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* If the new state is different then the old state the notify subscribers of the state change */</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (mAppForegroundState != oldState) {
validateThenNotifyListeners();
}
}

<span class="hljs-javadoc" style="color: rgb(136, 0, 0); box-sizing: border-box;">/**
* Add a listener to be notified of app foreground state change events.
*
*<span class="hljs-javadoctag" style="color: rgb(102, 0, 102); box-sizing: border-box;"> @param</span> listener
*/</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">addListener</span>(@NonNull OnAppForegroundStateChangeListener listener) {
mListeners.add(listener);
}

<span class="hljs-javadoc" style="color: rgb(136, 0, 0); box-sizing: border-box;">/**
* Remove a listener from being notified of app foreground state change events.
*
*<span class="hljs-javadoctag" style="color: rgb(102, 0, 102); box-sizing: border-box;"> @param</span> listener
*/</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">removeListener</span>(OnAppForegroundStateChangeListener listener) {
mListeners.remove(listener);
}

<span class="hljs-javadoc" style="color: rgb(136, 0, 0); box-sizing: border-box;">/** Notify all listeners the app foreground state has changed */</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">notifyListeners</span>(AppForegroundState newState) {
android.util.Log.i(TAG, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"Notifying subscribers that app just entered state: "</span> + newState);

<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> (OnAppForegroundStateChangeListener listener : mListeners) {
listener.onAppForegroundStateChange(newState);
}
}

<span class="hljs-javadoc" style="color: rgb(136, 0, 0); box-sizing: border-box;">/**
* This method will notify subscribes that the foreground state has changed when and if appropriate.
* <br><br>
* We do not want to just notify listeners right away when the app enters of leaves the foreground. When changing orientations or opening and
* closing the app quickly we briefly pass through a NOT_IN_FOREGROUND state that must be ignored. To accomplish this a delayed message will be
* Sent when we detect a change. We will not notify that a foreground change happened until the delay time has been reached. If a second
* foreground change is detected during the delay period then the notification will be canceled.
*/</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">validateThenNotifyListeners</span>() {
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// If the app has any pending notifications then throw out the event as the state change has failed validation</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (mHandler.hasMessages(MESSAGE_NOTIFY_LISTENERS)) {
android.util.Log.v(TAG, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"Validation Failed: Throwing out app foreground state change notification"</span>);
mHandler.removeMessages(MESSAGE_NOTIFY_LISTENERS);
} <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> {
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (mAppForegroundState == AppForegroundState.IN_FOREGROUND) {
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// If the app entered the foreground then notify listeners right away; there is no validation time for this</span>
mHandler.sendEmptyMessage(MESSAGE_NOTIFY_LISTENERS);
} <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> {
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// We need to validate that the app entered the background. A delay is used to allow for time when the application went into the</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// background but we do not want to consider the app being backgrounded such as for in app purchasing flow and full screen ads.</span>
mHandler.sendEmptyMessageDelayed(MESSAGE_NOTIFY_LISTENERS, APP_CLOSED_VALIDATION_TIME_IN_MS);
}
}
}

<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">NotifyListenersHandler</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">extends</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">Handler</span> {</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> <span class="hljs-title" style="box-sizing: border-box;">NotifyListenersHandler</span>(Looper looper) {
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span>(looper);
}

<span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">handleMessage</span>(Message inputMessage) {
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">switch</span> (inputMessage.what) {
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// The decoding is done</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">case</span> MESSAGE_NOTIFY_LISTENERS:
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">/* Notify subscribers of the state change */</span>
android.util.Log.v(TAG, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"App just changed foreground state to: "</span> + mAppForegroundState);
notifyListeners(mAppForegroundState);
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">break</span>;
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">default</span>:
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span>.handleMessage(inputMessage);
}
}
}
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li><li style="box-sizing: border-box; padding: 0px 5px;">38</li><li style="box-sizing: border-box; padding: 0px 5px;">39</li><li style="box-sizing: border-box; padding: 0px 5px;">40</li><li style="box-sizing: border-box; padding: 0px 5px;">41</li><li style="box-sizing: border-box; padding: 0px 5px;">42</li><li style="box-sizing: border-box; padding: 0px 5px;">43</li><li style="box-sizing: border-box; padding: 0px 5px;">44</li><li style="box-sizing: border-box; padding: 0px 5px;">45</li><li style="box-sizing: border-box; padding: 0px 5px;">46</li><li style="box-sizing: border-box; padding: 0px 5px;">47</li><li style="box-sizing: border-box; padding: 0px 5px;">48</li><li style="box-sizing: border-box; padding: 0px 5px;">49</li><li style="box-sizing: border-box; padding: 0px 5px;">50</li><li style="box-sizing: border-box; padding: 0px 5px;">51</li><li style="box-sizing: border-box; padding: 0px 5px;">52</li><li style="box-sizing: border-box; padding: 0px 5px;">53</li><li style="box-sizing: border-box; padding: 0px 5px;">54</li><li style="box-sizing: border-box; padding: 0px 5px;">55</li><li style="box-sizing: border-box; padding: 0px 5px;">56</li><li style="box-sizing: border-box; padding: 0px 5px;">57</li><li style="box-sizing: border-box; padding: 0px 5px;">58</li><li style="box-sizing: border-box; padding: 0px 5px;">59</li><li style="box-sizing: border-box; padding: 0px 5px;">60</li><li style="box-sizing: border-box; padding: 0px 5px;">61</li><li style="box-sizing: border-box; padding: 0px 5px;">62</li><li style="box-sizing: border-box; padding: 0px 5px;">63</li><li style="box-sizing: border-box; padding: 0px 5px;">64</li><li style="box-sizing: border-box; padding: 0px 5px;">65</li><li style="box-sizing: border-box; padding: 0px 5px;">66</li><li style="box-sizing: border-box; padding: 0px 5px;">67</li><li style="box-sizing: border-box; padding: 0px 5px;">68</li><li style="box-sizing: border-box; padding: 0px 5px;">69</li><li style="box-sizing: border-box; padding: 0px 5px;">70</li><li style="box-sizing: border-box; padding: 0px 5px;">71</li><li style="box-sizing: border-box; padding: 0px 5px;">72</li><li style="box-sizing: border-box; padding: 0px 5px;">73</li><li style="box-sizing: border-box; padding: 0px 5px;">74</li><li style="box-sizing: border-box; padding: 0px 5px;">75</li><li style="box-sizing: border-box; padding: 0px 5px;">76</li><li style="box-sizing: border-box; padding: 0px 5px;">77</li><li style="box-sizing: border-box; padding: 0px 5px;">78</li><li style="box-sizing: border-box; padding: 0px 5px;">79</li><li style="box-sizing: border-box; padding: 0px 5px;">80</li><li style="box-sizing: border-box; padding: 0px 5px;">81</li><li style="box-sizing: border-box; padding: 0px 5px;">82</li><li style="box-sizing: border-box; padding: 0px 5px;">83</li><li style="box-sizing: border-box; padding: 0px 5px;">84</li><li style="box-sizing: border-box; padding: 0px 5px;">85</li><li style="box-sizing: border-box; padding: 0px 5px;">86</li><li style="box-sizing: border-box; padding: 0px 5px;">87</li><li style="box-sizing: border-box; padding: 0px 5px;">88</li><li style="box-sizing: border-box; padding: 0px 5px;">89</li><li style="box-sizing: border-box; padding: 0px 5px;">90</li><li style="box-sizing: border-box; padding: 0px 5px;">91</li><li style="box-sizing: border-box; padding: 0px 5px;">92</li><li style="box-sizing: border-box; padding: 0px 5px;">93</li><li style="box-sizing: border-box; padding: 0px 5px;">94</li><li style="box-sizing: border-box; padding: 0px 5px;">95</li><li style="box-sizing: border-box; padding: 0px 5px;">96</li><li style="box-sizing: border-box; padding: 0px 5px;">97</li><li style="box-sizing: border-box; padding: 0px 5px;">98</li><li style="box-sizing: border-box; padding: 0px 5px;">99</li><li style="box-sizing: border-box; padding: 0px 5px;">100</li><li style="box-sizing: border-box; padding: 0px 5px;">101</li><li style="box-sizing: border-box; padding: 0px 5px;">102</li><li style="box-sizing: border-box; padding: 0px 5px;">103</li><li style="box-sizing: border-box; padding: 0px 5px;">104</li><li style="box-sizing: border-box; padding: 0px 5px;">105</li><li style="box-sizing: border-box; padding: 0px 5px;">106</li><li style="box-sizing: border-box; padding: 0px 5px;">107</li><li style="box-sizing: border-box; padding: 0px 5px;">108</li><li style="box-sizing: border-box; padding: 0px 5px;">109</li><li style="box-sizing: border-box; padding: 0px 5px;">110</li><li style="box-sizing: border-box; padding: 0px 5px;">111</li><li style="box-sizing: border-box; padding: 0px 5px;">112</li><li style="box-sizing: border-box; padding: 0px 5px;">113</li><li style="box-sizing: border-box; padding: 0px 5px;">114</li><li style="box-sizing: border-box; padding: 0px 5px;">115</li><li style="box-sizing: border-box; padding: 0px 5px;">116</li><li style="box-sizing: border-box; padding: 0px 5px;">117</li><li style="box-sizing: border-box; padding: 0px 5px;">118</li><li style="box-sizing: border-box; padding: 0px 5px;">119</li><li style="box-sizing: border-box; padding: 0px 5px;">120</li><li style="box-sizing: border-box; padding: 0px 5px;">121</li><li style="box-sizing: border-box; padding: 0px 5px;">122</li><li style="box-sizing: border-box; padding: 0px 5px;">123</li><li style="box-sizing: border-box; padding: 0px 5px;">124</li><li style="box-sizing: border-box; padding: 0px 5px;">125</li><li style="box-sizing: border-box; padding: 0px 5px;">126</li><li style="box-sizing: border-box; padding: 0px 5px;">127</li><li style="box-sizing: border-box; padding: 0px 5px;">128</li><li style="box-sizing: border-box; padding: 0px 5px;">129</li><li style="box-sizing: border-box; padding: 0px 5px;">130</li><li style="box-sizing: border-box; padding: 0px 5px;">131</li><li style="box-sizing: border-box; padding: 0px 5px;">132</li><li style="box-sizing: border-box; padding: 0px 5px;">133</li><li style="box-sizing: border-box; padding: 0px 5px;">134</li><li style="box-sizing: border-box; padding: 0px 5px;">135</li><li style="box-sizing: border-box; padding: 0px 5px;">136</li><li style="box-sizing: border-box; padding: 0px 5px;">137</li><li style="box-sizing: border-box; padding: 0px 5px;">138</li><li style="box-sizing: border-box; padding: 0px 5px;">139</li><li style="box-sizing: border-box; padding: 0px 5px;">140</li><li style="box-sizing: border-box; padding: 0px 5px;">141</li><li style="box-sizing: border-box; padding: 0px 5px;">142</li><li style="box-sizing: border-box; padding: 0px 5px;">143</li><li style="box-sizing: border-box; padding: 0px 5px;">144</li><li style="box-sizing: border-box; padding: 0px 5px;">145</li><li style="box-sizing: border-box; padding: 0px 5px;">146</li><li style="box-sizing: border-box; padding: 0px 5px;">147</li><li style="box-sizing: border-box; padding: 0px 5px;">148</li><li style="box-sizing: border-box; padding: 0px 5px;">149</li><li style="box-sizing: border-box; padding: 0px 5px;">150</li><li style="box-sizing: border-box; padding: 0px 5px;">151</li><li style="box-sizing: border-box; padding: 0px 5px;">152</li><li style="box-sizing: border-box; padding: 0px 5px;">153</li><li style="box-sizing: border-box; padding: 0px 5px;">154</li><li style="box-sizing: border-box; padding: 0px 5px;">155</li><li style="box-sizing: border-box; padding: 0px 5px;">156</li><li style="box-sizing: border-box; padding: 0px 5px;">157</li><li style="box-sizing: border-box; padding: 0px 5px;">158</li><li style="box-sizing: border-box; padding: 0px 5px;">159</li><li style="box-sizing: border-box; padding: 0px 5px;">160</li><li style="box-sizing: border-box; padding: 0px 5px;">161</li><li style="box-sizing: border-box; padding: 0px 5px;">162</li><li style="box-sizing: border-box; padding: 0px 5px;">163</li><li style="box-sizing: border-box; padding: 0px 5px;">164</li><li style="box-sizing: border-box; padding: 0px 5px;">165</li><li style="box-sizing: border-box; padding: 0px 5px;">166</li><li style="box-sizing: border-box; padding: 0px 5px;">167</li><li style="box-sizing: border-box; padding: 0px 5px;">168</li><li style="box-sizing: border-box; padding: 0px 5px;">169</li><li style="box-sizing: border-box; padding: 0px 5px;">170</li></ul>


2)Activities在可见性改变的需要发送通知

app中所有activities都要增加下面的代码,用于可见性改变时通知管理类。最好的实现方式是把这段代码加到工程的BaseActivity中。
<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">protected</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onStart</span>() {
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span>.onStart();
AppForegroundStateManager.getInstance().onActivityVisible(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>);
}

<span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">protected</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onStop</span>() {
AppForegroundStateManager.getInstance().onActivityNotVisible(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>);
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span>.onStop();
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li></ul>


3)订阅app的前台可见性改变事件

在感兴趣的模块中订阅app前台可见性改变事件,application类的onCreate函数是一个不错的地方,它可以保证每次app启动和关闭,你都能得到通知。
<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">MyApplication</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">extends</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">Application</span> {</span>
<span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onCreate</span>() {
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span>.onCreate();
AppForegroundStateManager.getInstance().addListener(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>);
}

<span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onAppForegroundStateChange</span>(AppForegroundStateManager.AppForegroundState newState) {
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (AppForegroundStateManager.AppForegroundState.IN_FOREGROUND == newState) {
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// App just entered the foreground. Do something here!</span>
} <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> {
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// App just entered the background. Do something here!</span>
}
}

}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li></ul>


进一步的思考

有一些细节需要进一步讨论,下面讨论的几点针对具体的应用可以做微调。


校验时间

校验定时器检查app是否真的进入后台的时间间隔是多少合适呢?上面的代码设置为30秒,原因如下。

当你的app在运行时,可能存在第三方的activities会覆盖全屏幕,一些常见的例子是Google应用内购买和Facebook登录注册页面。这些情况下你的app都会被迫进入后台,前台用于显示这些第三方页面。如果把这种情况当做用户离开了你的app,显然是不对的。30秒超时设置就是用来避免这种情况的。例如当用户在30秒内完成应用内购买,大部分用户都可以做得到,那么就不会当做用户突然离开app了。

如果你的app不存在上述这种情况,我建议可以把你的校验时间设置为4秒,这样对于低配设备当屏幕旋转重新创建activity的时间间隔是合适的。


CPU休眠

可能存在的问题是当用户关闭app或者app仍处于前台时用户锁屏了,这时CPU可能不会等到定时器检测就休眠了。为了保证这种情况下定时器能够正常检测用户退出app,我们需要持有wakelock防止CPU休眠直到app关闭事件被确认。实践中相比使用wakelock,这种情况并不算问题。


判断app是如何启动的

现在我们已经知道如何检测app何时启动和关闭,但我们不知道app是如何启动的。是用户点击通知栏消息?还是点击一个链接?亦或是他们直接通过桌面图标或最近使用启动?


跟踪启动机制

首先我们需要知道在哪里检测app是如何启动的。基于前面一个例子我们可以打印出app何时启动,以及如何启动。
<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">MyApplication</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">extends</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">Application</span> {</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> String TAG = MyApplication.class.getSimpleName();

<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">enum</span> LaunchMechanism {
DIRECT,
NOTIFICATION,
URL;
}

<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> LaunchMechanism mLaunchMechanism = LaunchMechanism.DIRECT;

<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">setLaunchMechanism</span>(LaunchMechanism launchMechanism) {
mLaunchMechanism = launchMechanism;
}

<span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onCreate</span>() {
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span>.onCreate();
AppForegroundStateManager.getInstance().addListener(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">this</span>);
}

<span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onAppForegroundStateChange</span>(AppForegroundStateManager.AppForegroundState newState) {
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (AppForegroundStateManager.AppForegroundState.IN_FOREGROUND.equals(newState)) {
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// App just entered the foreground.</span>
Log.i(TAG, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"App Just Entered the Foreground with launch mechanism of: "</span> + mLaunchMechanism);
} <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> {
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// App just entered the background. Set our launch mode back to the default of direct.</span>
mLaunchMechanism = LaunchMechanism.DIRECT;
}
}
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li></ul>


设置启动机制

现在我们可以打印app何时启动的机制,但我们没有设置它。因此下一步就是在用户通过链接或者通知启动app时我们记下它。如果没有通过这两种方式设置过,说明用户是通过点击app图标启动的。


跟踪链接点击事件

为了跟踪用户点击链接打开app,你需要找到代码中处理链接的地方,并加入下面的代码来跟踪启动机制。要确保这些代码在activity的onStart()函数之前调用。在哪些地方加入下面的代码取决于你的app架构了。
<code class="hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">getApplication()<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setLaunchMechanism</span>(LaunchMechanism<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.URL</span>)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>


跟踪通知事件

不幸的是跟踪通知点击需要更多技巧,通知显示后,点击它将会打开之前绑定好的一个PendingIntent,这里的技巧是为通知的所有PendingIntents添加一个标识表明是由通知发出的。

例如当为通知创建PendingIntent时为每个intent添加如下代码:
<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> String EXTRA_HANDLING_NOTIFICATION = <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"Notification.EXTRA_HANDLING_NOTIFICATION"</span>;

<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// Put an extra so we know when an activity launches if it is a from a notification</span>
intent.putExtra(EXTRA_HANDLING_NOTIFICATION, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>);</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li></ul>


到这一步我们需要做的就是在每个activity(统一在BaseActivity中添加)中检查这个标识。当识别到这个标识时说明是从通知启动的,这时可以把启动机制设置为通过通知。这一步应该在onCreate中处理,这样在app启动到前台之前就设置好了(会触发启动机制的打印)。
<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onCreate</span>(Bundle savedInstanceState) {
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span>.onCreate(savedInstanceState);

Intent intent = getIntent();
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (intent != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span> && intent.getExtras() != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) {
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// Detect if the activity was launched by the user clicking on a notification</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (intent.getExtras().getBoolean(EXTRA_HANDLING_NOTIFICATION, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">false</span>)) {
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// Notify that the activity was opened by the user clicking on a notification.</span>
getApplication().setLaunchMechanism(LaunchMechanism.NOTIFICATION);
}
}
}</code>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: