Android应用前后台状态判断

开发中遇到了一个需求,如果应用在非前台的状态超过一定时间,就需要用户重新去登录。如果用户不进行登录,就回到主页。这种类似的需求我在银行类App中见到过。技术点就是如何判断App当前的状态。

但是这里面状态有很多,并不只是前后台。还有App在前台,但是用户锁屏放置的情况,这种情况,也是需要算作用户未使用App的,所以需要判断的状态如下。

应用状态:

  • 从后台切换到前台
  • 从前台切换到后台
  • 用户锁屏
  • 用户解锁手机

总结出需要判断的内容之后,可以开始根据不同状态编写代码了。

判断前后台切换

首先使用ActivityLifecycleCallbacks接口来接收每一个Activity生命周期的回调,因为我们不知道用户会在哪个Activity中切换App状态,所以需要这个回调来统一处理。

这里解释两个回调方法:

  • onActivityResumed
    这里是当App到onResume时统一回调,当应用从后台返回前台时候也会走这个方法。
  • onActivityStopped
    这里是当App到onStop时统一回调,当应用从前台进入时候也会走这个方法。!!注意:这个方法不一定会执行(比如切换最近使用APP列表时),所以需要配合onTrimMemory一起使用。

onTrimMemory是个很神奇的方法,他实际上是App关于内存优化的一个回调。用于让应用程序在不同的状态下进行内存释放,从而避免被系统杀掉进程。本来与前后台切换并没有什么关系,但是当阅读文档时,发现他在这种状态下是会被回调的:
TRIM_MEMORY_UI_HIDDEN
它表示应用程序的所有UI界面被隐藏了,即用户点击了Home键或者Back键导致应用的UI界面不可见.这时候应该释放一些资源。
那么我们可以通过这个状态来判断用户是否切换到后台,与onStop方法配合使用。
补充:TRIM_MEMORY_BACKGROUND也是应用进入后台的回调,不同的手机厂商可能会使用这两个不同的Flag。最好都进行一下判断。

需要用于判断的回调都准备好了,下面来准备一下需要的常量和变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//APP状态常量
//正常状态
public static final int STATE_NORMAL = 0;
//从后台回到前台或从锁屏状态返回
public static final int STATE_BACK_TO_FRONT = 1;
//从前台进入后台或进入锁屏状态
public static final int STATE_FRONT_TO_BACK = 2;
//App状态
public static int sAppState = STATE_NORMAL;

//标记程序是否进入后台(onStop回调)
private boolean isBackFlag = false;
//标记程序是否已进入后台(依据onTrimMemory回调)
private boolean background = false;
//记录从前台进入后台的时间
private static long frontToBackTime;

常量准备好后,现在开始编写判断部分的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//在onCreate中注册此部分
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
//便于观察,这里删去了暂时不需要的方法
@Override
public void onActivityResumed(Activity activity) {
if (isBackFlag || background) {
//执行到这里说明App是从后台返回的
//状态从后台切换为前台
isBackFlag = false;
background = false;
sAppState = STATE_BACK_TO_FRONT;
//TODO:在这里可以处理App从后台返回的逻辑
//通过frontToBackTime与现在时间的计算,判断用户是否超时
} else {
//否则是默认状态
sAppState = STATE_NORMAL;
}
}

@Override
public void onActivityStopped(Activity activity) {
//判断当前activity是否处于前台
if (!DeviceUtils.isCurAppTop(activity)) {
//从前台进入后台
sAppState = STATE_FRONT_TO_BACK;
//记录从前台进入后台的时间
frontToBackTime = System.currentTimeMillis();
isBackFlag = true;
}
}
});

@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
// TRIM_MEMORY_UI_HIDDEN是UI不可见的回调, 通常程序进入后台后都会触发此回调,大部分手机多是回调这个参数
// TRIM_MEMORY_BACKGROUND也是程序进入后台的回调, 不同厂商不太一样, 魅族手机就是回调这个参数
if (level == Application.TRIM_MEMORY_UI_HIDDEN || level == TRIM_MEMORY_BACKGROUND) {
background = true;
//TRIM_MEMORY_COMPLETE表示内存已经很低,系统可能会杀掉我们的应用
} else if (level == Application.TRIM_MEMORY_COMPLETE) {
background = !DeviceUtils.isCurAppTop(this);
}
if (background) {
//记录时间
frontToBackTime = System.currentTimeMillis();
sAppState = STATE_FRONT_TO_BACK;
} else {
sAppState = STATE_NORMAL;
}
}

isCurAppTop是用来判断程序是否是前台进程的工具类,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 判断当前程序是否前台进程
*
* @param context
* @return
*/
public static boolean isCurAppTop(Context context) {
if (context == null) {
return false;
}
String curPackageName = context.getPackageName();
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> list = am.getRunningTasks(1);
if (list != null && list.size() > 0) {
ActivityManager.RunningTaskInfo info = list.get(0);
String topPackageName = info.topActivity.getPackageName();
String basePackageName = info.baseActivity.getPackageName();
if (topPackageName.equals(curPackageName) && basePackageName.equals(curPackageName)) {
return true;
}
}
return false;
}

判断用户是否进行锁屏解锁

上面的判断方法虽然已经很完整了,但是当用户将App放置前台并锁屏之后,不会触发上面的流程,那么需要对用户锁屏与解锁的状态进行判断了。

这时候我们需要在onCreate注册一个广播接收器,来接收系统锁屏开屏的广播。
这里简单介绍一下相关的三个状态:

  • Intent.ACTION_SCREEN_ON
    用户点亮屏幕
  • Intent.ACTION_SCREEN_OFF
    用户锁屏操作
  • Intent.ACTION_USER_PRESENT
    用户解锁屏幕,这时可以看到App了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private class ScreenBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_SCREEN_ON.equals(action)) {
// 开屏
} else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
// 锁屏
if (sAppState != STATE_FRONT_TO_BACK){
//当应用处于后台时,仍会收到广播。所以在后台时,注意不要刷新从前台进入后台的时间。
isBackFlag = true;
sAppState = STATE_FRONT_TO_BACK;
frontToBackTime = System.currentTimeMillis();
}
} else if (Intent.ACTION_USER_PRESENT.equals(action)) {
// 解锁
// TODO:在这里可以判断是否超时,从锁屏回到App的状态处理
}
}
}

//在onCreate中注册广播接收器
private void startScreenBroadcastReceiver() {
ScreenBroadcastReceiver mScreenReceiver = new ScreenBroadcastReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_USER_PRESENT);
this.registerReceiver(mScreenReceiver, filter);
}

用户如果不进行登录,返回主页

在App类中,创建管理Activity栈的对象。这里面需要一个工具类。ApplicationActivitiesQueue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import android.app.Activity;
import java.util.Stack;

/**
* 管理 Activity 的 视图栈
* From: https://www.jianshu.com/p/7adf0890e6b9
*/
public class ApplicationActivitiesQueue {
private ApplicationActivitiesQueue() {
}

private static ApplicationActivitiesQueue queue = new ApplicationActivitiesQueue();

public static ApplicationActivitiesQueue ShareActivityQueue() {
return queue;
}

private Stack<Activity> activityStack = new Stack<Activity>();

/**
* 获取当前的activity,不做任何操作
*/
public Activity currentActivity() {
return activityStack.lastElement();
}

/**
* 只有这俩方法 操作 activityStack不能手动调用(都是自动添加删除的)
* addActivity 添加Activity到堆栈
* popCurrentActivity 结束当前Activity
* currentActivity
*/
public void addActivity(Activity activity) {
activityStack.push(activity);
}

public void popCurrentActivity(Activity activity) {
activityStack.remove(activity);
}

/**
* 下面的这些方法都是辅助方法 (注意防止当前activity结束当前的导致crash)
* 获取当前Activity(堆栈中最后一个压入的)
*/
// 结束指定的Activity
public void finishOneActivity(Activity activity) {
if (activity != null) {
if (!activity.isFinishing()) {
activity.finish();
}
}
}

// 结束指定类名的Activity
public void finishOneActivity(Class<?> cls) {
for (Activity activity : activityStack) {
if (!activity.getClass().equals(cls)) continue;
finishOneActivity(activity);
return;
}
}

/**
* 结束至指定类名Activity(不包括该类名)
*/
public void finishToActiovity(Class<?> cls) {
while (!activityStack.lastElement().getClass().equals(cls)) {
activityStack.pop().finish();
if (activityStack.size() == 0) return;
}
}

/**
* 结束除指定类名的所有Activity
*/
public void finishExcludeActivityAllActivity(Class<?> cls) {
for (Activity activity : activityStack) {
if (activity == null) continue;
if (activity.getClass().equals(cls)) continue;
finishOneActivity(activity);
}
}

/**
* 结束所有Activity
*/
public void finishAllActivity() {
for (Activity activity : activityStack) {
if (activity == null) continue;
finishOneActivity(activity);
}
}

}

使用方法:
registerActivityLifecycleCallbacks中的onActivityCreatedonActivityDestroyed将当前Activity添加入视图栈中,之后按需调用视图栈的方法即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
//Activity队列
private ApplicationActivitiesQueue activitiesQueue = ApplicationActivitiesQueue.ShareActivityQueue();

registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
activitiesQueue.addActivity(activity);//创建
}
@Override
public void onActivityDestroyed(Activity activity) {
activitiesQueue.popCurrentActivity(activity);//退出
}
});

参考

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×