app前台判断那点事

“喜欢,是一切付出的前提。只有真心地喜欢了,你才会去投入,才不会抱怨这些投入,无论是时间、精力还是感情。 ”

在开发中,我们会遇到一些常见的需求,比如判断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权限被收紧,这个所谓漏洞已经不能被用了,请查看说明,所以此方法也失效。

选择最佳方法

其实实现还有其它方法,但是要兼容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显示效果不一样,大多数是你采用了系统的而没有自己定制,再接着,出来几个奇葩分辨率的平板通话手机,测试测出来了,你还得乖乖去适配。
与此同时,随着android权限的收紧,我们开发时要避开已经被废弃的方法,不然可能的后果你懂的。

打个广告:以后我所有的文章将会同步在我的博客简书CSDN

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容