“喜欢,是一切付出的前提。只有真心的喜欢了,你才会去投入,才不会抱怨这些投入,无论是时间、精力还是感情。 ”
在开发中,我们会遇到一些常见的需求,比如判断app从后台进入前台,要开启手势密码,开启通知等。本文会举几种常见的方法,并且谈一下坑,从而选择最合理的方法,使我们的代码看起来更加优雅。
代码仓库地址在文末会给出地址。
判断的方法
流传的有6种方法,但并不是所有方法都适用的。
方法 | 判断原理 | 是否需要权限 | 特点 |
---|---|---|---|
1 | getRunningTasks | 否 | 5.0以后此方法被废弃 |
2 | getRunningAppProcesses | 否 | 部分机型不支持 |
3 | ActivityLifecycleCallbacks | 否 | 简单有效 |
4 | UsageStatsManager | 是 | 用户手动授权 |
5 | Android无障碍功能实现 | 否 | 用户手动授权 |
6 | 读取/proc目录 | 否 | 当proc目录下文件夹过多时,过多的IO操作会引起耗时 |
方法一
通过getRunningTasks判断
private boolean isRunningForeground() {
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
ComponentName cn = am.getRunningTasks(1).get(0).topActivity;
String currentPackageName = cn.getPackageName();
if (currentPackageName != null && currentPackageName.equals(getPackageName())) {
return true;
}
return false;
}
原理:当app处于前台的时候,会处于栈顶,取出栈顶的进程来判断与当前运行的的app包名是否相同。
此方法在android5.0以后已经被废弃,经过测试从4.0到7.0依然有效。
为了以后的兼容,不推荐使用。
方法二
通过getRunningAppProcesses判断
public boolean isRunningForeground() {
ActivityManager activityManager = (ActivityManager)
getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
String packageName = getApplicationContext().getPackageName();
List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager
.getRunningAppProcesses();
if (appProcesses == null)
return false;
for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
if (appProcess.processName.equals(packageName)
&& appProcess.importance == ActivityManager
.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
return true;
}
}
return false;
}
先介绍一下importance的值
IMPORTANCE_BACKGROUND = 400//后台
IMPORTANCE_EMPTY = 500//空进程
MPORTANCE_FOREGROUND = 100//在屏幕最前端,可获取焦点
IMPORTANCE_SERVICE = 300//在服务中
IMPORTANCE_VISIBLE = 200//在屏幕前端、获取不到焦点
原理:获取到当前所有的进程,然后去遍历每一个进程,如果进程的包名和当前的包名一样且importance这个值为 100,那么app处于前台进程。
此方法在某些机型(国产机)上某些情况下会出现很诡异的问题,判断结果和预期相反,而且通过循环遍历,效率不佳。不推荐使用。
方法三
通过ActivityLifecycleCallbacks判断
public void isRunningForeground() {
if (Build.VERSION.SDK_INT >= 14) {
registerActivityLifecycleCallbacks(
new Application.ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(
Activity activity, Bundle bundle) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
System.out.println("app 在前台运行");
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
System.out.println("断app 不在前台运行");
}
@Override
public void onActivitySaveInstanceState(
Activity activity, Bundle bundle) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
}
}
方法四
通过UsageStatsManager判断
public boolean isRunningForeground5() {
if(Build.VERSION.SDK_INT >= 21){
long ts = System.currentTimeMillis();
UsageStatsManager usageStatsManager = (UsageStatsManager)
getApplicationContext().getSystemService(Context.USAGE_STATS_SERVICE);
List<UsageStats> queryUsageStats = usageStatsManager.queryUsageStats(
UsageStatsManager.INTERVAL_BEST, ts - 2000, ts);
if (queryUsageStats == null || queryUsageStats.isEmpty()) {
return false;
}
UsageStats recentStats = null;
for (UsageStats usageStats : queryUsageStats) {
if (recentStats == null ||
recentStats.getLastTimeUsed() < usageStats.getLastTimeUsed()){
recentStats = usageStats;
}
}
if(getPackageName().equals(recentStats.getPackageName())){
return true;
}
}
return false;
}
原理:获取一个时间段内的应用统计信息
这个方法只在android5.0以上生效,且需要加入“android.permission.PACKAGE_USAGE_STATS”权限,会出现警告说这个权限只应该是系统应用使用,同时,应用也要判断用是否已经获取到UsageState的权限,所以此方法也不行。
注意:必须去安全->高级->有权查看使用情况的应用中选中应用
方法五
具体看下面这篇博文
通过android辅助功能检测是否在前台界面
缺点:需要要用户开启辅助功能,辅助功能会伴随应用被“强行停止”而剥夺,所以此方法也不行。
方法六
通过某个漏洞通过读取Linux系统内核保存在/proc目录下的process进程信息
具体参考jaredrummler的github项目,github上介绍的相当清楚。
public boolean isRunningForeground4() {
if (AndroidProcesses.isMyProcessInTheForeground()) {
return true;
}
return false;
}
原理:Linux系统内核会把process进程信息保存在/proc目录下,Shell命令去获取的进程,再根据进程的属性判断是否为前台。由于在android7.0权限被收紧,这个所谓漏洞已经不能被用了,[请查看说明](https://code.google.com/p/android/issues/detail?id=205565
),所以此方法也失效。
选择最佳方法
其实实现还有其它方法,但是要兼容4.0到7.0,那就比较困难了,这里就不多说了。
从上面的分析来看,最合适的方法就是ActivityLifecycleCallbacks,下面来解释一下ActivityLifecycleCallbacks的各个生命周期。
void onActivityCreated(Activity activity, Bundle savedInstanceState);
void onActivityStarted(Activity activity);
void onActivityResumed(Activity activity);
void onActivityPaused(Activity activity);
void onActivityStopped(Activity activity);
void onActivitySaveInstanceState(Activity activity, Bundle outState);
void onActivityDestroyed(Activity activity);
已锁屏为例,当app按下返回键或者home键回到桌面(进程杀死),程序执行onActivityPaused–>onActivityStopped–>onActivityDestroyed,当重新打开程序时,执行onActivityCreated–>onActivityStarted–>onActivityResumed。
或者进入到其它app再回来,onActivityPaused和onActivityResumed这两个方法肯定会执行,因此我们可以在这两个函数之间设置一个flag来判断程序进入前台或者后台(测试没出过问题)。
注意一点:registerActivityLifecycleCallbacks应该写在你应用的Application中,而不应该在你的基类BaseActivity中,不然生命周期会执行多次。
本篇博客所涉及到的代码仓库github。
后记
由于国内的android生态过于混乱,各种定制系统,对于android开发者来说真是苦不堪言。同样的代码在不同的机型上会出现不同的结果,甚至在有的机型上会ANR,当然大多数情况是NullPointerException,其次UI显示效果不一样,大多数是你采用了系统的而没有自己定制,再接着,出来几个奇葩分辨率的平板通话手机,测试测出来了,你还得乖乖去适配。
与此同时,随着androdi权限的收紧,我们开发时要避开已经被废弃的方法,不然可能的后果你懂的。