引用出处:
作者: wenmingvs
https://blog.csdn.net/yzwfeng/article/details/124584900
Github: https://github.com/wenmingvs/AndroidProcess
方法 | 原理 | 需要权限 | 可以判断其他应用位于前台 | 特点 |
---|---|---|---|---|
方法一 | RunningTask | 否 | Android4可以,5.0以上不行 | 5.0此方法被废弃 |
方法二 | RunningProcess | 否 | 当App存在后台常驻的Service时失效 | 无 |
方法三 | ActivityLifecycleCallbacks | 否 | 否 | 简单有效,代码最少 |
方法四 | UsageStatsManager | 是 | 是 | 需要用户手动授权 |
方法五 | 通过Android无障碍功能实现 | 否 | 是 | 需要用户手动授权 |
方法六 | 读取/proc目录下的信息 | 否 | 是 | 当proc目录下文件夹过多时,过多的IO操作会引起耗时 |
方法七 | 使用 ProcessLifecycleOwner 监听app的生命周期 | 否 | 是 | 使用Jetpack组件 |
package com.tecsun.self.utils.processutil;
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.Service;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import com.tecsun.self.MyApp;
import com.tecsun.self.utils.DetectService;
//import com.tecsun.self.utils.processutil.models.AndroidAppProcess;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* Created by wenmingvs on 2016/1/14.
*/
public class BackgroundUtil {
public static final int BKGMETHOD_GETRUNNING_TASK = 0;
public static final int BKGMETHOD_GETRUNNING_PROCESS = 1;
public static final int BKGMETHOD_GETAPPLICATION_VALUE = 2;
public static final int BKGMETHOD_GETUSAGESTATS = 3;
public static final int BKGMETHOD_GETACCESSIBILITYSERVICE = 4;
public static final int BKGMETHOD_GETLINUXPROCESS = 5;
/**
* 自动根据参数选择判断前后台的方法
*
* @param context 上下文参数
* @param packageName 需要检查是否位于栈顶的App的包名
* @return
*/
public static boolean isForeground(Context context, int methodID, String packageName) {
switch (methodID) {
case BKGMETHOD_GETRUNNING_TASK:
return getRunningTask(context, packageName);
case BKGMETHOD_GETRUNNING_PROCESS:
return getRunningAppProcesses(context, packageName);
case BKGMETHOD_GETAPPLICATION_VALUE:
return getApplicationValue((MyApp) ((Service) context).getApplication());
case BKGMETHOD_GETUSAGESTATS:
return queryUsageStats(context, packageName);
case BKGMETHOD_GETACCESSIBILITYSERVICE:
return getFromAccessibilityService(context, packageName);
case BKGMETHOD_GETLINUXPROCESS:
// return getLinuxCoreInfo(context, packageName);
default:
return false;
}
}
/**
* 方法1:通过getRunningTasks判断App是否位于前台,此方法在5.0以上失效
*
* @param context 上下文参数
* @param packageName 需要检查是否位于栈顶的App的包名
* @return
*/
public static boolean getRunningTask(Context context, String packageName) {
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
ComponentName cn = am.getRunningTasks(1).get(0).topActivity;
return !TextUtils.isEmpty(packageName) && packageName.equals(cn.getPackageName());
}
/**
* 方法2:通过runningProcess获取到一个当前正在运行的进程的List,通过getRunningAppProcesses的IMPORTANCE_FOREGROUND属性判断是否位于前台,当service需要常驻后台时候,此方法失效 。
* 缺点:在聊天类型的App中,常常需要常驻后台来不间断的获取服务器的消息,这就需要我们把Service设置成START_STICKY,kill 后会被重启(等待5秒左右)来保证Service常驻后台。如果Service设置了这个属性,这个App的进程就会被判断是前台,代码上的表现就是appProcess.importance的值永远是 ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,这样就永远无法判断出到底哪个是前台了。
* 在小米 Note上此方法无效,在Nexus上正常
*
* @param context 上下文参数
* @param packageName 需要检查是否位于栈顶的App的包名
* @return
*/
public static boolean getRunningAppProcesses(Context context, String packageName) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
if (appProcesses == null) {
return false;
}
for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND && appProcess.processName.equals(packageName)) {
return true;
}
}
return false;
}
/**
* 方法3:通过ActivityLifecycleCallbacks来批量统计Activity的生命周期,来做判断,此方法在API 14以上均有效,但是需要在Application中注册此回调接口
* 必须:
* 1. 自定义Application并且注册ActivityLifecycleCallbacks接口
* 2. AndroidManifest.xml中更改默认的Application为自定义
* 3. 当Application因为内存不足而被Kill掉时,这个方法仍然能正常使用。虽然全局变量的值会因此丢失,但是再次进入App时候会重新统计一次的
* 4. 无论用back键切到后台还是用Home键切到后台,都会执行onStop,因此都不会影响该方法判断的正确性
* @param myApplication
* @return
*/
public static boolean getApplicationValue(MyApp myApplication) {
return myApplication.getAppCount() > 0;
}
/**
* 方法4:通过使用UsageStatsManager获取,此方法是ndroid5.0A之后提供的API
* 必须:
* 1. 此方法只在android5.0以上有效
* 2. AndroidManifest中加入此权限<uses-permission xmlns:tools="http://schemas.android.com/tools" android:name="android.permission.PACKAGE_USAGE_STATS"
* tools:ignore="ProtectedPermissions" />
* 3. 打开手机设置,点击安全-高级,在有权查看使用情况的应用中,为这个App打上勾
*
* @param context 上下文参数
* @param packageName 需要检查是否位于栈顶的App的包名
* @return
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static boolean queryUsageStats(Context context, String packageName) {
class RecentUseComparator implements Comparator<UsageStats> {
@Override
public int compare(UsageStats lhs, UsageStats rhs) {
return (lhs.getLastTimeUsed() > rhs.getLastTimeUsed()) ? -1 : (lhs.getLastTimeUsed() == rhs.getLastTimeUsed()) ? 0 : 1;
}
}
RecentUseComparator mRecentComp = new RecentUseComparator();
long ts = System.currentTimeMillis();
UsageStatsManager mUsageStatsManager = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
List<UsageStats> usageStats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST, ts - 1000 * 10, ts);
if (usageStats == null || usageStats.size() == 0) {
if (HavaPermissionForTest(context) == false) {
Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
Toast.makeText(context, "权限不够\n请打开手机设置,点击安全-高级,在有权查看使用情况的应用中,为这个App打上勾", Toast.LENGTH_SHORT).show();
}
return false;
}
Collections.sort(usageStats, mRecentComp);
String currentTopPackage = usageStats.get(0).getPackageName();
if (currentTopPackage.equals(packageName)) {
return true;
} else {
return false;
}
}
/**
* 判断是否有用权限
* @param context 上下文参数
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
private static boolean HavaPermissionForTest(Context context) {
try {
PackageManager packageManager = context.getPackageManager();
ApplicationInfo applicationInfo = packageManager.getApplicationInfo(context.getPackageName(), 0);
AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
int mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, applicationInfo.uid, applicationInfo.packageName);
return (mode == AppOpsManager.MODE_ALLOWED);
} catch (PackageManager.NameNotFoundException e) {
return true;
}
}
/**
* 方法5:通过Android自带的无障碍功能,监控窗口焦点的变化,进而拿到当前焦点窗口对应的包名
* 必须:
* 1. 创建ACCESSIBILITY SERVICE INFO 属性文件
* 2. 注册 DETECTION SERVICE 到 ANDROIDMANIFEST.XML
* 优点:
* 1、AccessibilityService 不再需要轮询的判断当前的应用是不是在前台,系统会在窗口状态发生变化的时候主动回调,耗时和资源消耗都极小。
* 2、不需要权限请求
* 3、它是一个稳定的方法,与 “方法6”读取 /proc 目录不同,它并非利用 Android 一些设计上的漏洞,可以长期使用的可能很大。
* 4、可以用来判断任意应用甚至 Activity, PopupWindow, Dialog 对象是否处于前台
* 缺点:1、需要要用户开启辅助功能。2、辅助功能会伴随应用被“强行停止”而剥夺
* @param context
* @param packageName
* @return
*/
public static boolean getFromAccessibilityService(Context context, String packageName) {
if (DetectService.isAccessibilitySettingsOn(context) == true) {
DetectService detectService = DetectService.getInstance();
String foreground = detectService.getForegroundPackage();
Log.d("wenming", "**方法五** 当前窗口焦点对应的包名为: =" + foreground);
return packageName.equals(foreground);
} else {
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
Toast.makeText(context, "accessbiliityNo", Toast.LENGTH_SHORT).show();
return false;
}
}
/**
* 方法6:无意中看到乌云上有人提的一个漏洞,Linux系统内核会把process进程信息保存在/proc目录下,使用Shell命令去获取的他,再根据进程的属性判断是否为前台。
* 优点:1、不需要任何权限。2、可以判断任意一个应用是否在前台,而不局限在自身应用。
* 缺点:当/proc下文件夹过多时,此方法是耗时操作。
*
* @param packageName 需要检查是否位于栈顶的App的包名
*/
// public static boolean getLinuxCoreInfo(Context context, String packageName) {
//
// List<AndroidAppProcess> processes = ProcessManager.getRunningForegroundApps(context);
// for (AndroidAppProcess appProcess : processes) {
// if (appProcess.getPackageName().equals(packageName) && appProcess.foreground) {
// return true;
// }
// }
// return false;
//
// }
}
DetectService代码如下:
package com.tecsun.self.utils;
import android.accessibilityservice.AccessibilityService;
import android.content.Context;
import android.provider.Settings;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
/**
* Created by wenmingvs on 16/2/10.
*/
public class DetectService extends AccessibilityService {
private static String mForegroundPackageName;
private static DetectService mInstance = null;
public DetectService() {
}
public static DetectService getInstance() {
if (mInstance == null) {
synchronized (DetectService.class) {
if (mInstance == null) {
mInstance = new DetectService();
}
}
}
return mInstance;
}
/**
* 监听窗口焦点,并且获取焦点窗口的包名
*
* @param event
*/
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
mForegroundPackageName = event.getPackageName().toString();
}
}
@Override
public void onInterrupt() {
}
public String getForegroundPackage() {
return mForegroundPackageName;
}
/**
* 此方法用来判断当前应用的辅助功能服务是否开启
*
* @param context
* @return
*/
public static boolean isAccessibilitySettingsOn(Context context) {
int accessibilityEnabled = 0;
try {
accessibilityEnabled = Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED);
} catch (Settings.SettingNotFoundException e) {
Log.d("wenming", 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;
}
}
```