判斷應用程序在前臺、后臺的方法。

簡介

工程鏈接: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 中注冊此回調接口。

條件:

  1. 自定義 Application 并注冊 ActivityLifecycleCallbacks 接口。
  2. 于 AndroidManifest.xml 中修改默認 Application 為自定義。
  3. 當 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

條件:

  1. 僅在 android 5.0 以上有效
  2. 于 AndroidManifest.xml 中加入 <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
  3. 打開手機設置,點擊安全-高級-有權查看使用情況的應用,選擇開啟這個應用
@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)控窗口焦點的變化,拿到焦點窗口對應包名

條件:

  1. 創(chuàng)建 ACCESSIBILITY SERVICE INFO 屬性文件
  2. 注冊 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)點:

  1. 不需要任何權限。
  2. 可以判斷任意一個應用是否在前臺,不局限自身。

缺點:

  1. 當 /proc 下文件較多時,此方法耗時。
  2. 一些 Android 版本中系統(tǒng)級應用并不能顯示,因為它們包含更高級的 SELinux 環(huán)境。
  3. 不能完全取代 getRunningAppProcesses()。這個庫也不能提供進程的pkgListlruimportance
  4. 這個方法目前在 N 的開發(fā)預覽版中不支持。

相關介紹:

對應工程在此處:AndroidProcesses,這個庫的主要目的是為了提供一種自5.0之后的獲取運行中應用的方法。
由前面的介紹可知,5.0之前可以使用 ActivityManager#getRunningTasks(int)ActivityManager#getRunningAppProcesses() 兩個方法來獲取正在運行的進程信息,
而判斷程序在前端后端的方法一、二也正對應了這兩個方法的應用。在5.0之后為了保護用戶隱私,這兩個方法都被廢除,能做的事也被大幅度削弱。
當然也可以采用第四種方案,使用 UsageStatsManager 來獲取使用信息,但是這需要保證應用權限的開啟。還有一些 OEM 并不支持這個設定。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,466評論 25 708
  • 如何能讓我們的應用能夠在系統(tǒng)后臺持續(xù)地運行是一個自Android從娘胎里出來時就議論不停的話題,而且這似乎成了一個...
    駿駿的簡書閱讀 1,132評論 1 19
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,973評論 19 139
  • 和新昌來場說走就走的初夏約會,也是佛緣。新昌隸屬紹興,跟著課本游百草園,學孔乙己喝碗溫熱黃酒,看看魯迅故居,也許是...
    冷酷的貓閱讀 338評論 0 1
  • 忘記了母親的乳汁是多么甜美, 但我始終記得母親的微笑, 陽光般地照徹朦朧的黑夜, 一雙眼睛, 默默地看著我走向遠方...
    悅者閱讀 412評論 1 6