簡介
工程鏈接:AndoridProcess
方法 | 原理 | 需要權限 | 其他應用是否有效 | 特點 |
---|---|---|---|---|
方法一 | RunningTask |
否 | Andorid 5.0 之后只能獲取自身信息 | 5.0之后廢除 |
方法二 | RunningProcess |
否 | App存在后臺常駐的Service時失效 | 無 |
方法三 | ActivityLifecycleCallbaks | 否 | 否 | 簡單有效 |
方法四 | UsageStatsManager |
是 | 是 | 需要用戶手動授權 |
方法五 | 通過Android無障礙功能實現(xiàn) | 否 | 是 | 需要用戶手動授權 |
方法六 | 讀取Linux內核下/proc目錄信息 | 否 | 是 | 一些版本沒有系統(tǒng)級應用信息,一些 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中,這個方法已不再允許第三方應用使用:它可能向調用者泄露用戶隱私信息。為了兼容性,該方法依然會返回一小部分數(shù)據,至少會有調用者自己的任務,有可能會包含一些其他的例如桌面這種不太敏感的數(shù)據。
介紹:
返回一組最近運行任務的列表,按時間排序,最近使用排在最前。注意“運行”并不代表該任務的任何代碼或者界面正在運行當中——它們可能被系統(tǒng)凍結,日后也許會重啟到之前的狀態(tài),恢復至前端。
注意:該方法僅準備為調試和任務展示的用戶接口使用該方法不應作為應用的核心功能,例如通過這里的信息來決定不同的行為之類。這種的使用方式是不支持的,而且很有可能在未來不生效。例如,若支持多應用同時允許的情況下,該方法返回值就不正確。
相關介紹:
axNum: int, 返回數(shù)據列表的大小。實際的返回數(shù)量也許會更小,這取決于用戶運行過的任務數(shù)量。
ActivityManager.RunningTaskInfo: 某個于系統(tǒng)中剛剛“運行”的任務所能得到的信息。注意運行中的任務并不代表它正在前臺運行當中,它僅僅表示用戶曾經運行過并且沒有關掉,但是系統(tǒng)可能殺掉該進程來釋放內存,僅保留退出狀態(tài)的一些信息以便恢復。
字段類型 | 字段描述 |
---|---|
public static final Creator<ActivityManager.RunningTaskInfo> |
CREATOR |
public ComponentName |
baseActivity 任務中執(zhí)行的第一個activity組件 |
public CharSequence |
description 任務當前狀態(tài)的描述 |
public int |
id 任務的唯一標識符 |
public int |
numActivities 任務中activity的數(shù)量 |
public int |
public int 任務中當前正在運行的activity的數(shù)量(不停止,持續(xù)) |
public Bitmap |
thumbnail 表示任務當前狀態(tài)的縮略圖 |
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 常量:該進程包含持續(xù)運行的 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 |
系統(tǒng)為該進程賦予的相對重要等級 |
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)優(yōu)先級 |
public int |
pid |
本進程 ID。空為0 |
public String[] |
pkgList |
加載到本進程內的所有包名 |
public String |
processName |
本進程的名稱 |
public int |
uid |
本進程的用戶id |
通過 ActivityLifecycleCallbacks 來批量統(tǒng)計 Activity 的生命周期,用此作為判斷,在API 14 以上均有效,需要在 Application 中注冊此回調接口。
條件:
- 自定義 Application 并注冊 ActivityLifecycleCallbacks 接口。
- 于 AndroidManifest.xml 中修改默認 Application 為自定義。
- 當 Application 因內存不足而被 kill 時,此方法依然有效。雖然全局變量的值會因此丟失,但是再次進入 App 后會重新統(tǒng)計一次。
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
提供設備使用歷史和統(tǒng)計數(shù)據。使用數(shù)據是按照時間劃分的:日,周,月和年。當請求某特定時間的使用數(shù)據時,請求類似下面這種:
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
,是系統(tǒng)級權限,并且不會授予第三方應用。然而聲明權限可以讓用戶在設置中為應用手動授予該權限。
常量
類型 | 名稱 | 描述 |
---|---|---|
int |
INTERVAL_BEST |
一種時間間隔類型,使用給定時間段的最適合時間間隔 |
int |
INTERVAL_DAILY |
一種跨越日的時間間隔類型 |
int |
INTERVAL_MONTHLY |
一種跨越月的時間間隔類型 |
int |
INTERVAL_WEEKLY |
一種跨越周的時間間隔類型 |
int |
INTERVAL_YEAR |
一種跨越年的時間間隔類型 |
公共方法
類型 | 名稱 | 描述 |
---|---|---|
boolean |
isAppInactive(String packageName) |
返回特定的應用當前是否處于閑置狀態(tài) |
Map<String, UsageStats> |
queryAndAggregateUsageStats(long beginTime, long endTime) |
一個便捷方法,獲取給定時間段內所有狀態(tài)的(使用INTERVAL_BEST )應用,集成在一個數(shù)據集合中,按包名排序 |
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
包含了應用包的一段特定時間內的使用統(tǒng)計信息。
變量
類型 | 名稱 | 描述 |
---|---|---|
public static final Creator<UsageStats> |
CREATOR |
nothing |
方法
類型 | 名稱 | 描述 |
---|---|---|
void |
add(UsageStats right) |
添加統(tǒng)計,從右至左 |
int |
describeContents() |
描述這個 Parcelable 對象中包含的特殊對象類型 |
long |
getFirstTimeStamp() |
獲取這個 UsageStats 描述的時間段的開始時間戳 |
long |
getLastTimeStamp() |
獲取這個 UsageStats 描述的時間段的結束時間戳 |
long |
getLastTimeUsed() |
獲取上一次這個包被使用的時間戳 |
String |
getPackageName() |
獲取包名 |
long |
getTotalTimeInForeground() |
獲取此包在前臺的時間總長,毫秒級 |
ConfigurationStats
描述設備 Configuration
的一段時間內使用統(tǒng)計數(shù)據。
方法
類型 | 名稱 | 描述 |
---|---|---|
int |
getActivationCount() |
獲取這個配置激活的次數(shù) |
long |
getFirstTimeStamp() |
獲取時間段的開始時間戳 |
long |
getLastTimeActive() |
獲取上一次激活的時間戳 |
long |
getLastTimeStamp() |
獲取時間段結束的時間戳 |
long |
getTotalTimeActive() |
獲取總共的激活時間 |
通過 Android 自帶無障礙功能,監(jiān)控窗口焦點的變化,拿到焦點窗口對應包名
條件:
- 創(chuàng)建 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 系統(tǒng)內核會把 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;
}
優(yōu)點:
- 不需要任何權限。
- 可以判斷任意一個應用是否在前臺,不局限自身。
缺點:
- 當 /proc 下文件較多時,此方法耗時。
- 一些 Android 版本中系統(tǒng)級應用并不能顯示,因為它們包含更高級的 SELinux 環(huán)境。
- 不能完全取代
getRunningAppProcesses()
。這個庫也不能提供進程的pkgList
、lru
、importance
。 - 這個方法目前在 N 的開發(fā)預覽版中不支持。
相關介紹:
對應工程在此處:AndroidProcesses
,這個庫的主要目的是為了提供一種自5.0之后的獲取運行中應用的方法。
由前面的介紹可知,5.0之前可以使用 ActivityManager#getRunningTasks(int)
和 ActivityManager#getRunningAppProcesses()
兩個方法來獲取正在運行的進程信息,
而判斷程序在前端后端的方法一、二也正對應了這兩個方法的應用。在5.0之后為了保護用戶隱私,這兩個方法都被廢除,能做的事也被大幅度削弱。
當然也可以采用第四種方案,使用 UsageStatsManager
來獲取使用信息,但是這需要保證應用權限的開啟。還有一些 OEM 并不支持這個設定。