---
埋点之应用前后台判断
---

#### 目录

1. 思维导图
2. 六种实现方式
   - RunningTask
   - RunningProcess
   - ActivityLifecucleCallbacks
   - UsageStatsManager
   - 无障碍服务
   - 读取 /proc 目录下的信息

#### 思维导图

#### ---- RnnningTask 方式

```java
    public static boolean getRunningTask(Context context, String packageName) {
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        ComponentName cn = am.getRecentTasks(1, ActivityManager.RECENT_WITH_EXCLUDED).get(0).topActivity;
        return !TextUtils.isEmpty(packageName) && packageName.equals(cn.getPackageName());
    }
```

主要功能在于 ActivityManager#getRecentTasks 方法。

原理：

是当一个 APP 处于前台的时候，会处于 RunningTask 的栈顶，所以我们可以取出栈顶的任务进程，和我们传入的包名对比来确定 APP 是否位于前台。

缺点：

该方法在 API 21 已经被废弃，只会返回自己和系统的一些不敏感的 Task，不再返回其他应用的从该方法的注释信息可以了解到，它返回用户最近启动的任务列表，此方法仅用于调试和显示任务管理用户界面，**绝对不能**用在应用程序中，因为未来可能会崩溃。

#### ---- RunningProcess 方式

```java
    public static boolean getRunningAppProcess(Context context, String packageName) {
        ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> appProcessInfos = activityManager.getRunningAppProcesses();
        if (appProcessInfos == null) {
            return false;
        }
        for (ActivityManager.RunningAppProcessInfo info : appProcessInfos) {
            if (info.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
                    && info.processName.equals(packageName)) {
                return true;
            }
        }
        return false;
    }
```

原理：

通过 runningProcess 获取到一个当前正在运行的进程的 List，遍历这个 List 的每一个进程，判断这个进程的 importance 属性是否是前台进程，并且包名一致，从而判断是否是前台应用。

缺点：

在社交类型的 APP 中，常常需要常驻后台来不间断的获取服务器消息，这就需要我们把 Service 设置为 START_STICKY，保证 Service 常驻后台。如果设置了这个属性，这个 APP 的进程就会判断为前台，appProcess.importance 的值永远是 ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND，这样就永远无法判断到底哪个是前台应用了。

#### ---- ActivityLifecycleCallbacks 方式

```java
public class MyApplication extends Application {
    private int appCount = 0;

    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
            }

            @Override
            public void onActivityStarted(Activity activity) {
                appCount++;
            }

            @Override
            public void onActivityResumed(Activity activity) {
            }

            @Override
            public void onActivityPaused(Activity activity) {
            }

            @Override
            public void onActivityStopped(Activity activity) {
                appCount--;
            }

            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

            }

            @Override
            public void onActivityDestroyed(Activity activity) {

            }
        });
    }

    public int getAppCount() {
        return appCount;
    }

    public void setAppCount(int appCount) {
        this.appCount = appCount;
    }
}
```

即使当 Application 因为内存不足被 kill 掉时，这个方法仍然可以使用，虽然全局变量的值会丢失，但是再次进入 App 时会重新统计。

需要注意这些方法的执行是在 Activity 的生命周期方法之前执行。

#### ---- UsageStatsManager 方式

原理：

通过使用 UsageStatsManager 获取，此方法是 Android 5.0 之后提供的 API，可以获取一个时间段内的应用统计信息，但是必须需用户授权：

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

手机设置 -> 查看应用使用情况

#### ---- 无障碍服务

```java
public class DetectService extends AccessibilityService {


    private static String mForegroundPackageName;
    private static volatile DetectService mInstance = null;

    public static DetectService getInstance() {
        if (mInstance == null) {
            synchronized (DetectService.class) {
                if (mInstance == null) {
                    mInstance = new DetectService();
                }
            }
        }
        return mInstance;
    }

    public static String getmForegroundPackageName() {
        return mForegroundPackageName;
    }

    /**
     * 判断当前应用的辅助功能是否开启
     */
    public static boolean isAccessibilitySettingOn(Context context) {
        int accessibilityEnable = 0;
        try {
            accessibilityEnable = Settings.Secure.getInt(context.getContentResolver(),
                    Settings.Secure.ACCESSIBILITY_ENABLED);
        } catch (Settings.SettingNotFoundException e) {
            e.printStackTrace();
        }
        if (accessibilityEnable == 1) {
            String services = Settings.Secure.getString(context.getContentResolver(),
                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
            if (services != null) {
                return services.toLowerCase().contains(context.getPackageName());
            }
        }
        return false;
    }

    /**
     * 监听窗口焦点，并且获取焦点窗口的包名
     *
     * @param event
     */
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
            mForegroundPackageName = event.getPackageName().toString();
        }
    }

    @Override
    public void onInterrupt() {

    }
```

使用：

```java
public static boolean getFromAccessibilityService(Context context,String packageName){
    if (DetectService.isAccessibilitySettingOn(context)){
        DetectService detectService=DetectService.getInstance();
        String foreground=detectService.getmForegroundPackageName();
        return foreground.equals(packageName);
    }else {
        Intent intent=new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
        return false;
    }
}
```

原理：

Android 辅助功能（ AccessibilityService ）为我们提供了一系例的事件回调，帮助我们指示一些用户界面的状态变化。我们可以派生辅助功能类，进而对不同的 AccessibilityEvent 进行处理，同样的，这个服务就可以判断当前的前台应用。可以用来判断任意应用甚至 Activity、PopopWindow、Dialog 对象是否处于前台。

#### —— 读取 /proc 文件

已失效。