Android中判断app何时是打开或者关闭的技术研究
2016-09-04 09:55
411 查看
存在的问题
Android开发中不可避免的会遇到需要检查app何时进入前台,何时被用户关闭。奇怪的是,要达到这个目的并不容易。检查app第一次启动并不难,但要判断它何时重新打开和关闭就没有那么简单了。这篇文章将介绍一种判断app打开,重新打开和关闭的技术。
让我们开始吧
判断一个app打开和关闭的关键在于判断它的activities是否正在前台显示。让我们先从简单的例子开始,一个只有一个activity的app,而且不支持水平模式。这样想要判断app是打开还是关闭只需要检查activity的onStart和onStop方法即可:/**
*Thisclassisresponsiblefortrackingallcurrentlyopenactivities.
*Bydoingsothisclasscandetectwhentheapplicationisintheforeground
*andwhenitisrunninginthebackground.
*/
publicclassAppForegroundStateManager{
privatestaticfinalStringTAG=AppForegroundStateManager.class.getSimpleName();
privatestaticfinalintMESSAGE_NOTIFY_LISTENERS=1;
publicstaticfinallongAPP_CLOSED_VALIDATION_TIME_IN_MS=
30*DateUtils.SECOND_IN_MILLIS;
//30Seconds
privateReference<Activity>mForegroundActivity;
privateSet<OnAppForegroundStateChangeListener>mListeners=
new
HashSet<>();
privateAppForegroundStatemAppForegroundState=
AppForegroundState.NOT_IN_FOREGROUND;
privateNotifyListenersHandlermHandler;
//Makethisclassathreadsafesingleton
privatestaticclassSingletonHolder{
publicstaticfinalAppForegroundStateManagerINSTANCE=
new
AppForegroundStateManager();
}
publicstaticAppForegroundStateManagergetInstance(){
return
SingletonHolder.INSTANCE;
}
privateAppForegroundStateManager(){
//Createthehandleronthemainthread
mHandler=
new
NotifyListenersHandler(Looper.getMainLooper());
}
publicenumAppForegroundState{
IN_FOREGROUND,
NOT_IN_FOREGROUND
}
publicinterfaceOnAppForegroundStateChangeListener{
/**Calledwhentheforegroundstateoftheappchanges*/
publicvoidonAppForegroundStateChange(AppForegroundStatenewState);
}
/**Anactivityshouldcallthiswhenitbecomesvisible*/
publicvoidonActivityVisible(Activityactivity){
if
(mForegroundActivity!=
null
)mForegroundActivity.clear();
mForegroundActivity=
new
WeakReference<>(activity);
determineAppForegroundState();
}
/**Anactivityshouldcallthiswhenitisnolongervisible*/
publicvoidonActivityNotVisible(Activityactivity){
/*
*Theforegroundactivitymayhavebeenreplacedwithanew
*
foregroundactivityinourapp.
*SoonlycleartheforegroundActivityifthenewactivitymatches
*
theforegroundactivity.
*/
if
(mForegroundActivity!=
null
){
Activityref=mForegroundActivity.get();
if
(activity==ref){
//Thisistheactivitythatisgoingaway,clearthereference
mForegroundActivity.clear();
mForegroundActivity=
null
;
}
}
determineAppForegroundState();
}
/**Usetodetermineifthisappisintheforeground*/
publicBooleanisAppInForeground(){
return
mAppForegroundState==AppForegroundState.IN_FOREGROUND;
}
/**
*Calltodeterminethecurrentstate,updatethetrackingglobal,and
*
notifysubscribersifthestatehaschanged.
*/
privatevoiddetermineAppForegroundState(){
/*Getthecurrentstate*/
AppForegroundStateoldState=mAppForegroundState;
/*Determinewhatthenewstateshouldbe*/
finalbooleanisInForeground=mForegroundActivity!=
null
&&mForegroundActivity.get()!=
null
;
mAppForegroundState=isInForeground?AppForegroundState.IN_FOREGROUND
:AppForegroundState.NOT_IN_FOREGROUND;
/*Ifthenewstateisdifferentthentheoldstatethenotifysubscribers
*
ofthestatechange
*/
if
(mAppForegroundState!=oldState){
validateThenNotifyListeners();
}
}
/**
*Addalistenertobenotifiedofappforegroundstatechangeevents.
*
*@paramlistener
*/
publicvoidaddListener(@NonNullOnAppForegroundStateChangeListenerlistener){
mListeners.add(listener);
}
/**
*Removealistenerfrombeingnotifiedofappforegroundstatechangeevents.
*
*@paramlistener
*/
publicvoidremoveListener(OnAppForegroundStateChangeListenerlistener){
mListeners.remove(listener);
}
/**Notifyalllistenerstheappforegroundstatehaschanged*/
privatevoidnotifyListeners(AppForegroundStatenewState){
android.util.Log.i(TAG,
"Notifyingsubscribersthatappjustenteredstate:"
+newState);
for
(OnAppForegroundStateChangeListenerlistener:mListeners){
listener.onAppForegroundStateChange(newState);
}
}
/**
*Thismethodwillnotifysubscribesthattheforegroundstatehaschangedwhen
*
andifappropriate.
*<br><br>
*Wedonotwanttojustnotifylistenersrightawaywhentheappentersof
*
leavestheforeground.Whenchangingorientationsoropeningand
*closingtheappquicklywebrieflypassthroughaNOT_IN_FOREGROUNDstatethat
*
mustbeignored.Toaccomplishthisadelayedmessagewillbe
*Sentwhenwedetectachange.Wewillnotnotifythataforegroundchange*happeneduntilthedelaytimehasbeenreached.Ifasecond
*foregroundchangeisdetectedduringthedelayperiodthenthenotification
*
willbecanceled.
*/
privatevoidvalidateThenNotifyListeners(){
//Iftheapphasanypendingnotificationsthenthrowouttheeventasthe
//
statechangehasfailedvalidation
if
(mHandler.hasMessages(MESSAGE_NOTIFY_LISTENERS)){
android.util.Log.v(TAG,
"ValidationFailed:Throwingoutappforeground
statechangenotification"
);
mHandler.removeMessages(MESSAGE_NOTIFY_LISTENERS);
}
else
{
if
(mAppForegroundState==AppForegroundState.IN_FOREGROUND){
//Iftheappenteredtheforegroundthennotifylisteners
//
rightaway;thereisnovalidationtimeforthis
mHandler.sendEmptyMessage(MESSAGE_NOTIFY_LISTENERS);
}
else
{
//Weneedtovalidatethattheappenteredthebackground.Adelayis
//usedtoallowfortimewhentheapplicationwentintothe
//backgroundbutwedonotwanttoconsidertheappbeingbackgrounded
//suchasforinapppurchasingflowandfullscreenads.
mHandler.sendEmptyMessageDelayed(MESSAGE_NOTIFY_LISTENERS,
APP_CLOSED_VALIDATION_TIME_IN_MS);
}
}
}
privateclassNotifyListenersHandlerextendsHandler{
privateNotifyListenersHandler(Looperlooper){
super
(looper);
}
@Override
publicvoidhandleMessage(MessageinputMessage){
switch
(inputMessage.what){
//Thedecodingisdone
case
MESSAGE_NOTIFY_LISTENERS:
/*Notifysubscribersofthestatechange*/
android.util.Log.v(TAG,
"Appjustchangedforegroundstateto:"
+mAppForegroundState);
notifyListeners(mAppForegroundState);
break
;
default
:
super
.handleMessage(inputMessage);
}
}
}
}2)Activities在可见性改变的需要发送通知app中所有activities都要增加下面的代码,用于可见性改变时通知管理类。最好的实现方式是把这段代码加到工程的BaseActivity中。
进一步的思考
有一些细节需要进一步讨论,下面讨论的几点针对具体的应用可以做微调。
校验时间
校验定时器检查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何时启动,以及如何启动。
设置启动机制
现在我们可以打印app何时启动的机制,但我们没有设置它。因此下一步就是在用户通过链接或者通知启动app时我们记下它。如果没有通过这两种方式设置过,说明用户是通过点击app图标启动的。
跟踪链接点击事件
为了跟踪用户点击链接打开app,你需要找到代码中处理链接的地方,并加入下面的代码来跟踪启动机制。要确保这些代码在activity的onStart()函数之前调用。在哪些地方加入下面的代码取决于你的app架构了。getApplication().setLaunchMechanism(LaunchMechanism.URL);
跟踪通知事件
不幸的是跟踪通知点击需要更多技巧,通知显示后,点击它将会打开之前绑定好的一个PendingIntent,这里的技巧是为通知的所有PendingIntents添加一个标识表明是由通知发出的。例如当为通知创建PendingIntent时为每个intent添加如下代码:publicstaticfinalStringEXTRA_HANDLING_NOTIFICATION=
"Notification.EXTRA_HANDLING_NOTIFICATION"
;
//Putanextrasoweknowwhenanactivitylaunchesif
//
itisafromanotification
intent.putExtra(EXTRA_HANDLING_NOTIFICATION,
true
);到这一步我们需要做的就是在每个activity(统一在BaseActivity中添加)中检查这个标识。当识别到这个标识时说明是从通知启动的,这时可以把启动机制设置为通过通知。这一步应该在onCreate中处理,这样在app启动到前台之前就设置好了(会触发启动机制的打印)。
、“阿里”、“腾讯”有惊喜!!!关注后可用入微信群。群里都是来自百度阿里腾讯的大牛。欢迎关注我们,一起讨论技术,扫描和长按下方的二维码可快速关注我们。或搜索微信公众号:JANiubility。
相关文章推荐
- Android 判断app何时是打开或者关闭的技术研究
- Android 判断app何时是打开或者关闭的技术研究
- Android中判断app何时启动和关闭的技术研究
- Android中判断app何时启动和关闭的技术研究
- Android中判断app何时启动和关闭的技术研究
- Android中判断app何时启动和关闭的技术研究
- android 判断是否有闪光灯,打开或者关闭闪光灯
- Android 4.4(KK)中利用APP打开关闭数据流量
- Android系统使用global key 一键启动指定APP或者打开WiFi/蓝牙等系统设置界面
- Android中监听系统网络连接打开或者关闭的实现代码
- Android通过包名或类名启动APP或者一个Activity 以及 判断APP的运行状态
- Android中自己实现App一打开判断是否有更新,并通过依赖AutoInstaller实现自动更新
- Android打开或者关闭GPS
- Android在APP存活或者被杀死情况下,点击通知栏打开指定Activity
- Android 判断软件app是否运行在bluestacks模拟器上(或者其他模拟器)
- js判断设备,跳转app应用、android市场或者AppStore
- Android 监听 Android中监听系统网络连接打开或者关闭的实现代码
- Android开发过程遇到的安装好的APP打开程序崩溃,或者安装后应用列表里没有的问题及解决方案
- android 判断APP是否第一次打开
- 判断android app或者service是否存在