很多场景下,都需要判断某个App处于前台还是后台。本文集网上编写的前台判断方案于一体。

目前,有6种方案:

 法

判断原理需要权限可以判断其他应用位于前台特点
RunningTaskAndorid4.0系列可以,5.0以上机器不行Android5.0此方法被废弃
RunningProcess当App存在后台常驻的Service时失效
ActivityLifecycleCallbacks简单有效,代码最少
UsageStatsManager需要用户手动授权
AccessibilityService需要用户手动授权
自解析/process当/proc目录下的文件过多时,过多的IO操作会引起耗时

接下来,就对以上6种方法展开详细说明:

目录

1. RunningTask

1.1 原理

1.2 代码实现

1.3 方案缺点

2. RunningProcess

2.1原理

2.2  代码实现

2.3 方案缺点

3.ActivityLifecycleCallbacks

3.1 原理

3.2 代码实现

3.3 方案特点

4. UsageStatsManager

4.1 原理

4.2 代码实现

4.3 方案特点

5.  AccessibilityService

5.1 原理

5.2 代码实现

5.3 方案特点

6. 自解析/process

6.1 原理

6.2 优点

6.3 用法

6.4 方案特点

6.5 方案缺点

6.6 能耗问题解决

7.作为系统进程的获取方式

7.1 技术方案


1. RunningTask

1.1 原理

当一个App处于前台时,会处于RunningTask这个栈的栈顶,所以可以取出RunningTask栈顶的任务进程,与需要判断的App的包名进行比较,来达到目的。

1.2 代码实现

这种方法不仅能获取到前台进程的包名还能获取到activity名称。

public String getForegroundActivity() {  
    ActivityManager mActivityManager =  
        (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);  
        if (mActivityManager.getRunningTasks(1) == null) {  
            Log.e(TAG, "running task is null, ams is abnormal!!!");  
            return null;  
        }  
        ActivityManager.RunningTaskInfo mRunningTask =  
                    mActivityManager.getRunningTasks(1).get(0);  
        if (mRunningTask == null) {  
            Log.e(TAG, "failed to get RunningTaskInfo");  
            return null;  
        }  
   
        String pkgName = mRunningTask.topActivity.getPackageName();  
        //String activityName =  mRunningTask.topActivity.getClassName();  
        return pkgName;  
}  

1.3 方案缺点

getRunningTask方法在5.0以上已经被废弃,只会返回自己和系统的一些不敏感的task,不再返回其他应用的task,用CI方法来判断自身App是否处于后台仍然有效,但是无法判断其他应用是否位于前台,因为不能再获取信息。

2. RunningProcess

2.1原理

通过runningProcess获取到一个当前正在运行的进程的List,我们遍历这个List中的每一个进程,判断这个进程的一个importance 属性是否是前台进程,并且包名是否与我们判断的APP的包名一样,如果这两个条件都符合,那么这个App就处于前台。

2.2  代码实现

  以下code是判断当前应用是否在前台:

private static boolean isAppForeground(Context context) {
    ActivityManager activityManager =     
                       (ActivityManager)context.getSystemService(Service.ACTIVITY_SERVICE);
    List<ActivityManager.RunningAppProcessInfo> runningAppProcessInfoList =           		    	     
                                            activityManager.getRunningAppProcesses();
    if (runningAppProcessInfoList == null) {
        Log.d(TAG,"runningAppProcessInfoList is null!");
        return false;
    }

   for(ActivityManager.RunningAppProcessInfo processInfo : runningAppProcessInfoList) {
        if (processInfo.processName.equals(context.getPackageName())
                &&(processInfo.importance == 
                         ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND)) { 
                return true;
        }
   } 
   return false;
}

以下code是判断在前台的是哪个应用:

public String getForegroundApp(Context context) {  
    ActivityManager am =  
        (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);  
    List<RunningAppProcesInfo> lr = am.getRunningAppProcesses();  
    if (lr == null) {  
        return null;  
    }  
  
    for (RunningAppProcessInfo ra : lr) {  
        if (ra.importance == RunningAppProcessInfo.IMPORTANCE_VISIBLE  
            || ra.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {  
            return ra.processName;  
        }  
    }  
    return null;  
}  

getRunningAppProcess方法只能获取前台包名。

2.3 方案缺点

Android5.0之后已经被废弃。

例如,在聊天类型的App中,常常需要常驻后台来不间断地获取服务器的消息,就需要把Service设置成START_STICKY,kill后会被重启(等待5s左右)来保证Service常驻后台。如果Service设置了这个属性,这个App的进程就会被判断为前台。代码表现为

appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND

  上述code永远成立,这样就永远无法判断到底那个是前台了。

3.ActivityLifecycleCallbacks

3.1 原理

       AndroidSDK14Application类里增加了ActivityLifecycleCallbacks,我们可以通过这个Callback拿到App所有Activity的生命周期回调。

 public interface 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);
  }

       知道这些信息,我们就可以用更官方的办法来解决问题,只需要在ApplicationonCreate()里去注册上述接口,然后由Activity回调回来运行状态即可。

       Android应用开发中一般认为back键是可以捕获的,而Home键是不能捕获的(除非修改framework,但是上述方法从Activity生命周期着手解决问题,虽然这两种方式的Activity生命周期并不相同,但是二者都会执行onStop();所以并不关心到底是触发了哪个键切入后台的。另外,Application是否被销毁,都不会影响判断的正确性

3.2 代码实现

    (1)AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="mytest.example.com.broadcaststudy">

    <application
        android:name=".TestActivityLifecycleApplcation"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".SendBroadcastActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity android:name=".SecondActivity">
        </activity>
    </application>

</manifest>

(2) TestActivityLifecycleApplication.java

package mytest.example.com.broadcaststudy;

import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import android.util.Log;

/**
 * Created by Maureen on 2018/1/18.
 */
public class TestActivityLifecycleApplcation extends Application {
    private final String TAG = "TestActivityLifecycleApplcation";
    private static TestActivityLifecycleApplcation mTestActivityLifecycleApplcation;
    private int mActivityCount = 0;
    @Override
    public void onCreate() {
        super.onCreate();
        mTestActivityLifecycleApplcation = new TestActivityLifecycleApplcation();

        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                Log.d(TAG,"onActivityCreated");
            }

            @Override
            public void onActivityStarted(Activity activity) {
                Log.d(TAG,"onActivityStarted");
                mActivityCount++;
            }

            @Override
            public void onActivityResumed(Activity activity) {
                Log.d(TAG,"onActivityResumed");
            }

            @Override
            public void onActivityPaused(Activity activity) {
                Log.d(TAG,"onActivityPaused");
            }

            @Override
            public void onActivityStopped(Activity activity) {
                Log.d(TAG,"onActivityStopped");
                mActivityCount--;
            }

            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
                Log.d(TAG,"onActivitySaveInstanceState");
            }

            @Override
            public void onActivityDestroyed(Activity activity) {
                Log.d(TAG,"onActivityDestroyed");
            }
        });
    }

    public static TestActivityLifecycleApplcation getInstance( ) {
        if (null == mTestActivityLifecycleApplcation)
            mTestActivityLifecycleApplcation = new TestActivityLifecycleApplcation();
        return mTestActivityLifecycleApplcation;
    }

    public int getActivityCount( ) {
        return mActivityCount;
    }
}

(3) SendActivity.java

package mytest.example.com.broadcaststudy;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;

/**
 * Created by ATC6111 on 2018/1/18.
 */
public class SecondActivity extends Activity {
    private final String TAG = "SecondActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate");
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG, "onStart");
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(TAG, "onRestart");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG, "onResume");

    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG, "onPause");
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG, "onStop");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy");
    }
}

(4) SendBroadcastActivity.java

package mytest.example.com.broadcaststudy;

import android.app.Activity;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

import java.util.List;

public class SendBroadcastActivity extends Activity {
    private static final String TAG = "SendBroadcastActivity";
    private static final String ACTION_MAUREEN_TEST = "com.maureen.test";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG,"onCreate");
        setContentView(R.layout.activity_main);

        Intent intent = new Intent();
        intent.setAction(ACTION_MAUREEN_TEST);
        //intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
        Log.d(TAG,"Begin to sendBroadcast:");
        sendBroadcast(intent);
        Log.d(TAG,"Send broadcast end!");
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG,"onStart");
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(TAG,"onRestart");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG,"onResume");
        startActivity(new Intent(this, SecondActivity.class));
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG,"onPause");
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG,"onStop");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG,"onDestroy");
    }
}

在讲解为什么不在onActivityResumed( )与onActivityPaused( )中对activity进行计数,而是在onActivityStarted()和onActivityStopped( )中对activity进行计数之前。

先看以下几种情况的activity生命周期:

A、启动App,进入SendBroadcastActivity:

01-01 05:33:56.401 11816-11816/mytest.example.com.broadcaststudy D/TestActivityLifecycleApplcation: onActivityCreated
01-01 05:33:56.401 11816-11816/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onCreate
01-01 05:33:56.427 11816-11816/mytest.example.com.broadcaststudy D/SendBroadcastActivity: Begin to sendBroadcast:
01-01 05:33:56.430 11816-11816/mytest.example.com.broadcaststudy D/SendBroadcastActivity: Send broadcast end!
01-01 05:33:56.431 11816-11816/mytest.example.com.broadcaststudy D/TestActivityLifecycleApplcation: onActivityStarted
01-01 05:33:56.431 11816-11816/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onStart
01-01 05:33:56.432 11816-11816/mytest.example.com.broadcaststudy D/TestActivityLifecycleApplcation: onActivityResumed
01-01 05:33:56.432 11816-11816/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onResume

B、点击back键退出SendBroadcastActivity:

点击back键退出App:
01-01 05:35:37.983 11816-11816/mytest.example.com.broadcaststudy D/TestActivityLifecycleApplcation: onActivityPaused
01-01 05:35:37.983 11816-11816/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onPause
01-01 05:35:38.035 11816-11816/mytest.example.com.broadcaststudy D/TestActivityLifecycleApplcation: onActivityStopped
01-01 05:35:38.036 11816-11816/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onStop
01-01 05:35:38.036 11816-11816/mytest.example.com.broadcaststudy D/TestActivityLifecycleApplcation: onActivityDestroyed
01-01 05:35:38.036 11816-11816/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onDestroy

C、在A情况下点击home键:

01-01 05:37:34.690 11816-11816/mytest.example.com.broadcaststudy D/TestActivityLifecycleApplcation: onActivityPaused
01-01 05:37:34.690 11816-11816/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onPause
01-01 05:37:34.708 11816-11816/mytest.example.com.broadcaststudy D/TestActivityLifecycleApplcation: onActivitySaveInstanceState
01-01 05:37:34.708 11816-11816/mytest.example.com.broadcaststudy D/TestActivityLifecycleApplcation: onActivityStopped
01-01 05:37:34.708 11816-11816/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onStop

D、在A情况下点击recent键kill进程:

从recent中kill进程:
01-01 05:38:17.867 11816-11816/mytest.example.com.broadcaststudy D/TestActivityLifecycleApplcation: onActivityPaused
01-01 05:38:17.867 11816-11816/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onPause
01-01 05:38:17.914 11816-11816/mytest.example.com.broadcaststudy D/TestActivityLifecycleApplcation: onActivitySaveInstanceState
01-01 05:38:17.914 11816-11816/mytest.example.com.broadcaststudy D/TestActivityLifecycleApplcation: onActivityStopped
01-01 05:38:17.914 11816-11816/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onStop

E、新增code在SendBroadcastActivity的onResume( )中启动SecondActivity:

在SendBroadcastActivity的onResume函数中启动SecondActivity:
01-01 05:57:05.262 16836-16836/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onCreate
01-01 05:57:05.286 16836-16836/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onStart
01-01 05:57:05.287 16836-16836/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onResume
01-01 05:57:05.324 16836-16836/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onPause
01-01 05:57:05.367 16836-16836/mytest.example.com.broadcaststudy D/SecondActivity: onCreate
01-01 05:57:05.370 16836-16836/mytest.example.com.broadcaststudy D/SecondActivity: onStart
01-01 05:57:05.370 16836-16836/mytest.example.com.broadcaststudy D/SecondActivity: onResume
01-01 05:57:05.605 16836-16836/mytest.example.com.broadcaststudy D/SendBroadcastActivity: onStop

其中尤其是E,可以看到activity切换时的生命周期。这里暂且称SendBroadcastActivity为A,SecondActivity为B。

如果在A.onResume( )时mActivityCount = 1; A.onPause()时mActivityCount--,

在B.onResume( )时mActivityCount++,此处就会有个短暂的延时,在跳转过程中就会出现mActivityCount = 0,

即判断App在后台,就不正确了。

所以要在onActivityStart( )和onActivityStopped( )中 对activity进行计数。

即在A.onStart()时mActivity = 1; B.onStart( )时mActivity ++; A.onStop()时,mActivity--。

所以,当activity的计数为0时表示应用在后台,否则就在前台。

3.3 方案特点

(1)Android应用开发中,一般认为back键是可以捕获的,而Home键不能捕获(除非修改Framework),虽然这两种方式的Activity生命周期并不相同,但是二者都会执行onStop( );所以并不关心到底是哪个键切入后台的。另外,Application是否销毁,都不会影响判断的正确性;

(2)该方案除了用于判断当前应用内的哪个activity位于前台外,还可用于作为实现“进程完全退出”的一种很好的计数方案;

(3)该方案需要在Application中进行注册相关Activity生命周期的回调,上述code所示。只需要对mActivityCount计数进行判断即可知道是否在前台。

4. UsageStatsManager

4.1 原理

通过使用UsageStatsManager获取,此方法是Android5.0之后提供的新API,可以获取一个时间段内的应用统计信息,但是

必须满足以下要求。

使用前提

  1. 此方法只在android5.0以上有效
  2. AndroidManifest中加入此权限
    <uses-permission  android:name="android.permission.PACKAGE_USAGE_STATS" />
  1. 打开手机设置,点击安全-高级,在有权查看使用情况的应用中,为这个App打上勾

4.2 代码实现

UsageStatsManager mUsageStatsManager = (UsageStatsManager)context.getApplicationContext().getSystemService(Context.USAGE_STATS_SERVICE);
long time = System.currentTimeMillis();
List<UsageStats> stats ;
if (isFirst){
    stats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - TWENTYSECOND, time);
}else {
    stats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - THIRTYSECOND, time);
}
// Sort the stats by the last time used
if(stats != null) {
    TreeMap<Long,UsageStats> mySortedMap = new TreeMap<Long,UsageStats>();
    start=System.currentTimeMillis();
    for (UsageStats usageStats : stats) {
        mySortedMap.put(usageStats.getLastTimeUsed(),usageStats);
    }
    LogUtil.e(TAG,"isFirst="+isFirst+",mySortedMap cost:"+ (System.currentTimeMillis()-start));
    if(mySortedMap != null && !mySortedMap.isEmpty()) {                    
        topPackageName =  mySortedMap.get(mySortedMap.lastKey()).getPackageName();        
        runningTopActivity=new ComponentName(topPackageName,"");
        if (LogUtil.isDebug())LogUtil.d(TAG,topPackageName);
    }
}

跳转到“查看应用使用权限”界面的跳转代码如下:

Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
startActivity(intent);

还要声明权限:

<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />

4.3 方案特点

1、该方案最大的缺点是需要用户手动授权,因此在使用时要结合场景做适当引导;

2、该方案为Android5.0以后Google官方比较推荐的获取进程信息的方式,是最符合Google意图的方式,不过在使用时会有一些延时需要小心处理。

3、使用时可能会遇到通知栏也被计入,具体可参考:4 种获取前台应用的方法(肯定有你不知道的) - 掘金

5.  AccessibilityService

5.1 原理

Android 辅助功能(AccessibilityService) 为我们提供了一系列的事件回调,帮助我们指示一些用户界面的状态变化。

我们可以派生辅助功能类,进而对不同的 AccessibilityEvent 进行处理。同样的,这个服务就可以用来判断当前的前台应用

优势

  • AccessibilityService 有非常广泛的 ROM 覆盖,特别是非国产手机,从 Android API Level 18(Android 2.2) 到 Android Api Level 23(Android 6.0)
  • AccessibilityService 不再需要轮询的判断当前的应用是不是在前台,系统会在窗口状态发生变化的时候主动回调,耗时和资源消耗都极小
  • 不需要权限请求
  • 它是一个稳定的方法,与 “方法5”读取 /proc 目录不同,它并非利用 Android 一些设计上的漏洞,可以长期使用的可能很大
  • 可以用来判断任意应用甚至 Activity, PopupWindow, Dialog 对象是否处于前台

劣势

  • 需要要用户开启辅助功能
  • 辅助功能会伴随应用被“强行停止”而剥夺

5.2 代码实现

参考博客:http://effmx.com/articles/tong-guo-android-fu-zhu-gong-neng-accessibility-service-jian-ce-ren-yi-qian-tai-jie-mian/

步骤:

(1)派生AccessibilityService,创建窗口状态探测服务

创建DetectionService.java

public class DetectionService extends AccessibilityService {

    final static String TAG = "DetectionService";

    static String foregroundPackageName;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return 0; // 根据需要返回不同的语义值
    }

    /**
     * 重载辅助功能事件回调函数,对窗口状态变化事件进行处理
     * @param event
     */
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
            /*
             * 如果 与 DetectionService 相同进程,直接比较 foregroundPackageName 的值即可
             * 如果在不同进程,可以利用 Intent 或 bind service 进行通信
             */
            foregroundPackageName = event.getPackageName().toString();

            /*
             * 基于以下还可以做很多事情,比如判断当前界面是否是 Activity,是否系统应用等,
             * 与主题无关就不再展开。
             */
            ComponentName cName = new ComponentName(event.getPackageName().toString(),
                    event.getClassName().toString());
        }
    }

    @Override
    public void onInterrupt() {
    }

    @Override
    protected  void onServiceConnected() {
        super.onServiceConnected();
    }
}

(2)创建Accessibility Service Info属性文件

创建res/xml/detection_service_config.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- 根据的 Service 的不同功能需要,你可能需要不同的配置 -->
<accessibility-service
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeWindowStateChanged"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagIncludeNotImportantViews" />

(3)注册Detection service到AndroidManifest.xml

在AndroidManifest.xml中添加

<service
    android:name="your_package.DetectionService"
    android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">

    <intent-filter>
        <action android:name="android.accessibilityservice.AccessibilityService"/>
    </intent-filter>

    <meta-data
        android:name="android.accessibilityservice"
        android:resource="@xml/detection_service_config"/>

</service>

(4)使用detection service判断应用是否在前台

创建isForegroundPkgViaDetectionService( )函数

  /**
     * 方法6:使用 Android AccessibilityService 探测窗口变化,跟据系统回传的参数获取 前台对象 的包名与类名
     *
     * @param packageName 需要检查是否位于栈顶的App的包名
     */
    public static boolean isForegroundPkgViaDetectionService(String packageName) {
        return packageName.equals(DetectingService.foregroundPackageName);
    }

去设置里开启辅助功能,就可以通过isForegroundPkgDetectService( )判断应用是否在前台了,只需要传入相应应用的包为参数即可。

当然,也可以参照以下方式引导用户开启辅助功能:

(1)引导用户开启辅助功能

 final static String TAG = "AccessibilityUtil";

    // 此方法用来判断当前应用的辅助功能服务是否开启
    public static boolean isAccessibilitySettingsOn(Context context) {
        int accessibilityEnabled = 0;
        try {
            accessibilityEnabled = Settings.Secure.getInt(context.getContentResolver(),
                    android.provider.Settings.Secure.ACCESSIBILITY_ENABLED);
        } catch (Settings.SettingNotFoundException e) {
            Log.i(TAG, e.getMessage());
        }

        if (accessibilityEnabled == 1) {
            String services = Settings.Secure.getString(context.getContentResolver(),
                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
            if (services != null) {
                return services.toLowerCase().contains(context.getPackageName().toLowerCase());
            }
        }

        return false;
    }

    private void anyMethod() {
        // 判断辅助功能是否开启
        if (!isAccessibilitySettingsOn(getContext())) {
            // 引导至辅助功能设置页面
            startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS));
        } else {
            // 执行辅助功能服务相关操作
        }
    }

效果如下:

5.3 方案特点

1. AccessibilityService有非常广泛的ROM覆盖,特别是非国产手机,从API Level 8 (Android2.2)到API Level 23(Android6.0)

2. AccessibilityService不再需要轮询地判断当前的应用是不是在前台,系统会在窗口状态发生变化的时候主动回调,耗时和资源消耗都极小;

3.不需要权限请求;

4.它是一个稳定的方法,并非利用Android一些设计上的 漏洞,可以长期使用的可能很大;

5.可以用来判断任意应用甚至Activity,PopupWindow,Dialog对象是否处于前台。

5.4 方案缺点

1、需要用户手动开启辅助功能;

2、辅助功能会伴随应用被“强行停止”或第三方管理工具通过Root而剥夺,而且进程重启需要对用户进行重新引导开启;

3、部分厂商可能对辅助功能进行限制,如已知的vivo部分机型。

6. 自解析/process

6.1 原理

无意中看到乌云上有人提的一个漏洞,Linux系统内核会把process进程信息保存在/proc目录下,Shell命令去获取的他,再根据进程的属性判断是否为前台

6.2 优点

  1. 不需要任何权限
  2. 可以判断任意一个应用是否在前台,而不局限在自身应用

6.3 用法


(1)获取一系列正在运行的App的进程

List<AndroidAppProcess> processes = ProcessManager.getRunningAppProcesses();

(2)获取任一正在运行的App进程的详细信息

AndroidAppProcess process = processes.get(location);
String processName = process.name;

Stat stat = process.stat();
int pid = stat.getPid();
int parentProcessId = stat.ppid();
long startTime = stat.stime();
int policy = stat.policy();
char state = stat.state();

Statm statm = process.statm();
long totalSizeOfProcess = statm.getSize();
long residentSetSize = statm.getResidentSetSize();

PackageInfo packageInfo = process.getPackageInfo(context, 0);
String appName = packageInfo.applicationInfo.loadLabel(pm).toString();

(3)判断是否在前台

if (ProcessManager.isMyProcessInTheForeground()) {
  // do stuff
}

(4)获取一系列正在运行的App进程的详细信息

List<ActivityManager.RunningAppProcessInfo> processes = ProcessManager.getRunningAppProcessInfo(ctx);

6.4 方案特点

1、不需要任何权限;

2、可以判断任意一个应用是否在前台,而不局限在自身应用;

3、当/proc下文件夹过多时,此方法是耗时操作;

4、该方案存在能耗问题;

5、在Android6.0.1以上版本或部分厂商版本受限于SEAndroid,只能获取到第3方进程的信息。

6.5 方案缺点

1. 当/proc下文件夹过多时,此方法是耗时操作

2. 该方案再6.0手机适配运行ok,但在最新的小米、华为6.0.1手机中发现受限于SELinux,无法读取系统应用的设备节点进行解析,只能解析第三方应用设备节点。

6.6 能耗问题解决

1、Java层对象缓存:对调用比较频繁的Java层对象在JNI中建立全局缓存,这就避免了每次调用时都需要通过JNI接口获取;

对一些判断是需要的场景在初始化时由Java层传入Jni层,并建立全局缓存。

2、过来的为Android进程:将pid小于1000的Native进程过滤掉;

3、只解析发生变化的进程:在每次轮询解析/proc节点时先判断进程的pid在缓存中是否存在,如果存在只需要更新进程的优先

级信息,其他信息不会发生变化;如果进程之前不存在则需要全新解析:

(1)命中缓存时的解析代码如下

//Code,待补充

(2)未命中缓存时,则进行全新解析

//Code,待补充

4、在解析进程时,过来父进程为zygote的进程:Android中所有应用进程的父进程都是Zygote;

5、在Java层对调用做缓存处理:对于调用比较频繁的情况,如果当次Native调用没有完成,则返回之前的值,不需要阻塞等待;

6、对于只关心前台进程的场景进行特殊处理:

//code,待补充

通过优化,适配方案的能耗与系统接口基本保持一致。

7.作为系统进程的获取方式

7.1 技术方案

虽然getRunningTask从Android5.0开始被系统废弃,但是作为系统应用时,该接口依然是可用的。在用户取得Root权限,或者应用跟厂商合作时,应用本身可能会被内置在系统目录,即:/system/app/或system/private-app/等目录,因此对于这种情况,使用getRunningTask获取依然是一种方便的实现。

1、需要判断应用是否为系统应用:

//Code,待补充。

2、在AndroidManifest.xml中需要声明如下权限:

//Code,待补充。

参考链接:

1.GitHub - wenmingvs/AndroidProcess: 判断App位于前台或者后台的6种方法

2.http://effmx.com/articles/tong-guo-android-fu-zhu-gong-neng-accessibility-service-jian-ce-ren-yi-qian-tai-jie-mian/

3.4 种获取前台应用的方法(肯定有你不知道的) - 掘金

4.http://www.voidcn.com/article/p-aaftfysq-bny.html

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐