简介
工程链接:AndoridProcess
方法 | 原理 | 需要权限 | 其他应用是否有效 | 特点 |
---|---|---|---|---|
方法一 | RunningTask |
否 | Andorid 5.0 之后只能获取自身信息 | 5.0之后废除 |
方法二 | RunningProcess |
否 | App存在后台常驻的Service时失效 | 无 |
方法三 | ActivityLifecycleCallbaks | 否 | 否 | 简单有效 |
方法四 | UsageStatsManager |
是 | 是 | 需要用户手动授权 |
方法五 | 通过Android无障碍功能实现 | 否 | 是 | 需要用户手动授权 |
方法六 | 读取Linux内核下/proc目录信息 | 否 | 是 | 一些版本没有系统级应用信息,一些 OEM 无效,黑科技。 |
通过 getRunningTasks 判断 App 是否位于前台, 此方法在5.0以上失效。
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());
}
ActivityManager#getRunningTasks
List<ActivityManager.RunningTaskInfo> getRunningTasks (int maxNum)
I
注意:
该方法在API level 21之后已弃用。
在LOLLIPOP中,这个方法已不再允许第三方应用使用:它可能向调用者泄露用户隐私信息。为了兼容性,该方法依然会返回一小部分数据,至少会有调用者自己的任务,有可能会包含一些其他的例如桌面这种不太敏感的数据。
介绍:
返回一组最近运行任务的列表,按时间排序,最近使用排在最前。注意“运行”并不代表该任务的任何代码或者界面正在运行当中——它们可能被系统冻结,日后也许会重启到之前的状态,恢复至前端。
注意:该方法仅准备为调试和任务展示的用户接口使用该方法不应作为应用的核心功能,例如通过这里的信息来决定不同的行为之类。这种的使用方式是不支持的,而且很有可能在未来不生效。例如,若支持多应用同时允许的情况下,该方法返回值就不正确。
相关介绍:
axNum: int, 返回数据列表的大小。实际的返回数量也许会更小,这取决于用户运行过的任务数量。
ActivityManager.RunningTaskInfo: 某个于系统中刚刚“运行”的任务所能得到的信息。注意运行中的任务并不代表它正在前台运行当中,它仅仅表示用户曾经运行过并且没有关掉,但是系统可能杀掉该进程来释放内存,仅保留退出状态的一些信息以便恢复。
字段类型 | 字段描述 |
---|---|
public static final Creator<ActivityManager.RunningTaskInfo> |
CREATOR |
public ComponentName |
baseActivity 任务中执行的第一个activity组件 |
public CharSequence |
description 任务当前状态的描述 |
public int |
id 任务的唯一标识符 |
public int |
numActivities 任务中activity的数量 |
public int |
public int 任务中当前正在运行的activity的数量(不停止,持续) |
public Bitmap |
thumbnail 表示任务当前状态的缩略图 |
public ComponentName |
topActivity 任务栈中顶部的activity组件 |
通过 getRunningAppProcesses 的 IMPORTANCE_FOREGROUND 属性判断是否位于前台,当 service 需要常驻后台时候,此方法在小米 Note 上此方法无效,在 Nexus 上正常
public static boolean getRunningAppProcesses(Context context, String packageName) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERICE);
List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
if (appProcess == null) {
return false;
}
for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FORGROUND &&
appProcess.processName.equals(packageName)) {
return true;
}
}
return false;
}
ActivityManager#getRunningAppProcesses
List<ActivityManager.RunningAppProcessInfo> getRunningAppProcesses()
介绍:
返回一组在设备上运行的应用进程信息列表。顺序不定。
注意:该方法仅用于测试或构建一个面向用户的进程管理UI
相关介绍:
ActivityManager.RunningAppProcessInfo
可以获得的运行中进程信息。
常量
类型 | 名称 | 描述 |
---|---|---|
int |
IMPORTANCE_BACKGROUND |
importance 常量:该进程包含后台运行代码 |
int |
IMPORTANCE_mZONE |
importance 常量:该进程不存在 |
int |
IMPORTANCE_PERCEPTIBLE |
importance 常量:该进程并不是用户可以直接感知到的,而是从某种角度、某种程度可见的 |
int |
IMPORTANCE_SERVICE |
importance 常量:该进程包含持续运行的 service |
int |
IMPORTANCE_TOP_SLEEPING |
importance 常量:该进程在最前端的 UI 展示,但是设备正在睡眠中所以对用户不可见 |
int |
IMPORTANCE_VISIBLE |
importance 常量:该进程包含确实对用户可见的内容,即使不是在当前的最前端 |
int |
REASON_PROVIDER_IN_USE |
importanceReasonCode 常量:应用的某一个 content provider 正在被其他进程使用 |
int |
REASON_SERVICE_IN_USE |
importanceReasonCode 常量:应用的某一个 content provider 正在被其他进程使用 |
int |
REASON_UNKNOWN |
importanceReasonCode 常量:这个等级没什么特别的原因 |
变量
类型 | 名称 | 描述 |
---|---|---|
public static final Creator<ActivityManager.RUnningAppProcessInfo> |
CREATOR |
nothing |
public int |
importance |
系统为该进程赋予的相对重要等级 |
public int |
importanceReasonCode |
进程重要等级的原因,如果有的话 |
public ComponentName |
importanceReasonComponent |
对于特定值的importanceReasonCode ,表示正在被使用的组件名称 |
public int |
importanceReasonPid |
对于特定值的值的importanceReasonCode ,表示访问本进程的进程 ID |
public int |
lastTrimLevel |
上一次内存释放报告给进程的进程等级,依赖方法ComponentCallbacks2.onTrimMemory(int) 提供 |
public int |
lru |
额外增加的importance 特征排序信息,提供本进程LRU(Leaset Recently Used)优先级 |
public int |
pid |
本进程 ID。空为0 |
public String[] |
pkgList |
加载到本进程内的所有包名 |
public String |
processName |
本进程的名称 |
public int |
uid |
本进程的用户id |
通过 ActivityLifecycleCallbacks 来批量统计 Activity 的生命周期,用此作为判断,在API 14 以上均有效,需要在 Application 中注册此回调接口。
条件:
- 自定义 Application 并注册 ActivityLifecycleCallbacks 接口。
- 于 AndroidManifest.xml 中修改默认 Application 为自定义。
- 当 Application 因内存不足而被 kill 时,此方法依然有效。虽然全局变量的值会因此丢失,但是再次进入 App 后会重新统计一次。
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) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
}
public int getAppCount() {
return appCount;
}
}
public static boolean isApplicationForground(MyApplication myApplication) {
return myApplication.getAppCount() > 0;
}
通过使用UsageStatsManager获取,此方法为 Android5.0 之后提供的API
条件:
- 仅在 android 5.0 以上有效
- 于 AndroidManifest.xml 中加入
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
- 打开手机设置,点击安全-高级-有权查看使用情况的应用,选择开启这个应用
@TargetApi(Build.VERSION_CODES.LOLLIPOP_MR1)
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 (!havePermissionForTest(context)) {
Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
Toast.makeText(context, "权限不够\n请打开手机设置,点击安全-高级-有权查看使用情况的应用,开启这个应用的权限",Toast.LENGTH_LONG).show();
}
return false;
}
Collections.sort(usageStats, mRecentComp);
String currentTopPackage = usageStats.get(0).getPackageName();
return currentTopPackage.equals(packageName);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static boolean havePermissionForTest(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_STAGS, applicationInfo.uid, applicationInfo.packageName);
return mode == AppOpsManager.MODE_ALLOWED;
} catch (PackageManager.NameNotFoundException e) {
return true;
}
}
相关介绍:
UsageStatsManager
提供设备使用历史和统计数据。使用数据是按照时间划分的:日,周,月和年。当请求某特定时间的使用数据时,请求类似下面这种:
PAST REQUEST_TIME TODAY FUTURE
————————————————————————————||———————————————————————————¦-----------------------|
YEAR || ¦ |
————————————————————————————||———————————————————————————¦-----------------------|
MONTH | || MONTH ¦ |
——————————————————|—————————||———————————————————————————¦-----------------------|
| WEEK | WEEK|| | WEEK | WE¦EK | WEEK |
————————————————————————————||———————————————————|———————¦-----------------------|
|| |DAY|DAY|DAY|DAY¦DAY|DAY|DAY|DAY|DAY|DAY|
————————————————————————————||———————————————————————————¦-----------------------|
上图是包含了请求时间间隔的一段请求时间间隔。(好绕)
注意:本 API 需要权限 android_permission.PACKAGE_USAGE_STATS
,是系统级权限,并且不会授予第三方应用。然而声明权限可以让用户在设置中为应用手动授予该权限。
常量
类型 | 名称 | 描述 |
---|---|---|
int |
INTERVAL_BEST |
一种时间间隔类型,使用给定时间段的最适合时间间隔 |
int |
INTERVAL_DAILY |
一种跨越日的时间间隔类型 |
int |
INTERVAL_MONTHLY |
一种跨越月的时间间隔类型 |
int |
INTERVAL_WEEKLY |
一种跨越周的时间间隔类型 |
int |
INTERVAL_YEAR |
一种跨越年的时间间隔类型 |
公共方法
类型 | 名称 | 描述 |
---|---|---|
boolean |
isAppInactive(String packageName) |
返回特定的应用当前是否处于闲置状态 |
Map<String, UsageStats> |
queryAndAggregateUsageStats(long beginTime, long endTime) |
一个便捷方法,获取给定时间段内所有状态的(使用INTERVAL_BEST )应用,集成在一个数据集合中,按包名排序 |
List<ConfigurationStats> |
aueryConfigurations(int intervalType, long beginTime, long endTime) |
获取设备一段时间间隔内的硬件设置信息 |
UsageEvents |
queryEvents(long beginTime, long endTime) |
给定时间间隔内的时间 |
List<UsageStats> |
queryUsageStats(int intervalType, long beginTime, long endTime) |
获取一段时间内、特定时间间隔类型的应用使用信息 |
UsageStats
包含了应用包的一段特定时间内的使用统计信息。
变量
类型 | 名称 | 描述 |
---|---|---|
public static final Creator<UsageStats> |
CREATOR |
nothing |
方法
类型 | 名称 | 描述 |
---|---|---|
void |
add(UsageStats right) |
添加统计,从右至左 |
int |
describeContents() |
描述这个 Parcelable 对象中包含的特殊对象类型 |
long |
getFirstTimeStamp() |
获取这个 UsageStats 描述的时间段的开始时间戳 |
long |
getLastTimeStamp() |
获取这个 UsageStats 描述的时间段的结束时间戳 |
long |
getLastTimeUsed() |
获取上一次这个包被使用的时间戳 |
String |
getPackageName() |
获取包名 |
long |
getTotalTimeInForeground() |
获取此包在前台的时间总长,毫秒级 |
ConfigurationStats
描述设备 Configuration
的一段时间内使用统计数据。
方法
类型 | 名称 | 描述 |
---|---|---|
int |
getActivationCount() |
获取这个配置激活的次数 |
long |
getFirstTimeStamp() |
获取时间段的开始时间戳 |
long |
getLastTimeActive() |
获取上一次激活的时间戳 |
long |
getLastTimeStamp() |
获取时间段结束的时间戳 |
long |
getTotalTimeActive() |
获取总共的激活时间 |
通过 Android 自带无障碍功能,监控窗口焦点的变化,拿到焦点窗口对应包名
条件:
- 创建 ACCESSIBILITY SERVICE INFO 属性文件
- 注册 DETECTION SERVICE 到 AndroidManifest.xml
public static boolean getFromAccessibilityService(Context context, String packageName) {
if (DetectService.isAccessibilitySettingsOn(context)) {
DetectService detectService = DetectService.getInstance();
String foreground = detectService.getForegroundPackage();
Log.d(DEBUG, "当前窗口焦点对应包名为:" + foreground);
return packageName.equals(foreground);
} else {
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY+SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
Toask.makeTexxt(context, "请为App打开辅助功能开关", Toast.LENGTH_SHORT).show();
return false;
}
}
public class DetectService extends AccessibilityService {
private static String mForegroundPackageName;
private static DetectService mInstatnce = null;
private DetectService {}
public static DetectService getInstance() {
if (mInstance == null) {
synchronized (DetectService.class) {
if (mInstance == null) {
mInstance = new DetectService();
}
}
}
return mInstance;
}
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
mForegroundPackageName = event.getPackageName().toString();
}
}
@Override
public void onInterrupt() {
}
public String getForegroundPackageName() {
return mForegroundPackageName;
}
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.d(DEBUG, e.getMessage());
}
if (accessibilityEnabled == 1) {
String services = Settings.Secure.getString(context.getContentResolver(),
Setting.Secure.ENABLED_ACCESSIBILITY_SERICES);
if (services != null) {
return services.toLowerCase().contains(context.getPackageName().toLowerCase());
}
}
return false;
}
}
相关介绍:
Developing an Accessibility Service
AccessibilityService API
Settings
Settings API
Linux 系统内核会把 process 进程信息保存在 /proc 目录下,使用 Shell 命令去获取,再根据进程属性判断是否为前台
public static boolean getLinuxCoreInfo(Context context, String packageName) {
List<AndroidAppProcess> processes = ProcessManager.getRunningForegroundApps(context);
for (AndroidAppProcess appProcess : processes) {
if (appProcesse.getPackageName().euqals(packageName) && appProcess.foreground) {
return true;
}
}
return false;
}
优点:
- 不需要任何权限。
- 可以判断任意一个应用是否在前台,不局限自身。
缺点:
- 当 /proc 下文件较多时,此方法耗时。
- 一些 Android 版本中系统级应用并不能显示,因为它们包含更高级的 SELinux 环境。
- 不能完全取代
getRunningAppProcesses()
。这个库也不能提供进程的pkgList
、lru
、importance
。 - 这个方法目前在 N 的开发预览版中不支持。
相关介绍:
对应工程在此处:AndroidProcesses
,这个库的主要目的是为了提供一种自5.0之后的获取运行中应用的方法。
由前面的介绍可知,5.0之前可以使用 ActivityManager#getRunningTasks(int)
和 ActivityManager#getRunningAppProcesses()
两个方法来获取正在运行的进程信息,
而判断程序在前端后端的方法一、二也正对应了这两个方法的应用。在5.0之后为了保护用户隐私,这两个方法都被废除,能做的事也被大幅度削弱。
当然也可以采用第四种方案,使用 UsageStatsManager
来获取使用信息,但是这需要保证应用权限的开启。还有一些 OEM 并不支持这个设定。