Andoid系統從Android5.0開始對獲取前臺進程接口進行相關限制。本文爲對突破Android接口限制進行的一系列研究的總結。目前所有獲取前臺進程的接口有如下7種方式:
接下來將對每一種方案進行詳細的闡述。
1. 通過RunningTask
1.1. 實現原理
當一個App處於前臺的時候,會處於RunningTask的這個棧的棧頂,所以我們可以取出RunningTask的棧頂的任務進程,看他與我們的想要判斷的App的包名是否相同,來達到效果。
代碼實現如下:
1.2. 方案缺點
getRunningTask方法在Android5.0以上已經被廢棄,只會返回自己和系統的一些不敏感的task,不再返回其他應用的task,用此方法來判斷自身App是否處於後臺,仍然是有效的,但是無法判斷其他應用是否位於前臺,因爲不再能獲取信息
2. 通過RunningProcess
2.1. 實現原理
通過runningProcess獲取到一個當前正在運行的進程的List,我們遍歷這個List中的每一個進程,判斷這個進程的一個importance 屬性是否是前臺進程,並且包名是否與我們判斷的APP的包名一樣,如果這兩個條件都符合,那麼這個App就處於前臺。
代碼實現如下:
2.2. 方案缺點
1、 對於前臺Service(通過setForeground接口設置)的進程會被誤判判斷是前臺進程,代碼上的表現就是appProcess.importance的值永遠是ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND,這樣就會存在誤判的情況出現。
2、 該方案只在Android5.0之前有效,Android5.0以後也被系統廢棄。而且基於Android5.0的小米開發版及部分基於Android5.0的華爲版本該方案也會存在問題。
針對會在部分Android5.0的機型上存在問題的問題,我封裝了一個測試接口用於判斷,如果該接口返回爲true則說明爲問題機型,則RunningProcess方案不適用。測試接口的思想爲:先通過getRunningAppProcesses獲取到進程的List,然後判斷如果進程數量非常少(這裏設定的閾值爲3個),或者進程列表中只有Launcher和應用自身存在,則認爲接口存在問題。測試代碼如下:
3. 通過ActivityLifecycleCallbacks
3.1. 實現原理
AndroidSDK14在Application類裏增加了ActivityLifecycleCallbacks,我們可以通過這個Callback拿到App所有Activity的生命週期回調。
public interface ActivityLifecycleCallbacks {
voidonActivityCreated(Activity activity, Bundle savedInstanceState);
voidonActivityStarted(Activity activity);
voidonActivityResumed(Activity activity);
voidonActivityPaused(Activity activity);
voidonActivityStopped(Activity activity);
voidonActivitySaveInstanceState(Activity activity, Bundle outState);
voidonActivityDestroyed(Activity activity);
}
知道這些信息,我們就可以用更官方的辦法來解決問題,當然還是利用方案二里的Activity生命週期的特性,我們只需要在Application的onCreate()裏去註冊上述接口,然後由Activity回調回來運行狀態即可。
3.2. 方案特點
1、 在Android應用開發中一般認爲back鍵是可以捕獲的,而Home鍵是不能捕獲的(除非修改framework),雖然這兩種方式的Activity生命週期並不相同,但是二者都會執行onStop();所以並不關心到底是觸發了哪個鍵切入後臺的。另外,Application是否被銷燬,都不會影響判斷的正確性。
2、 該方案除了用於判斷當前應用內那個Activity位於前臺外,還可以用於作爲實現“進程完全退出”的一種很好的技術方案。
3、 該方案需要在Application中進行註冊相關Activity生命週期的回調,主要代碼如下:
在用該接口判斷應用是否在前臺時,只需要對activityCount計數進行判斷即可:
4. 通過UsageStatsManager獲取
4.1. 實現原理
通過使用UsageStatsManager獲取,此方法是Android5.0之後提供的新API,可以獲取一個時間段內的應用統計信息,但是必須滿足一下要求
使用前提
1、此方法只在android5.0以上有效
2、AndroidManifest中加入此權限
3、手動開啓權限:打開手機設置,點擊安全-高級,在有權查看使用情況的應用中,爲這個App打上勾
該方案的實現代碼如下:
4.2. 方案特點
1、 該方案最大的缺點是需要用戶手動授權,因此在使用時要結合場景做適當引導。
2、 該方案爲Android5.0以後Google官方比較推薦的獲取進程信息的方式,是最符合Google意圖的方式,不過在使用時會有一些延時需要小心處理。
4.3. 相關輔助接口
1、跳轉到“查看應用使用權限”界面的跳轉代碼如下:
2、判斷“查看應用使用權限”是否開啓的接口:
5. 通過AccessibilityService獲取
5.1. 技術原理
Android 輔助功能(AccessibilityService) 爲我們提供了一系列的事件回調,幫助我們指示一些用戶界面的狀態變化。我們可以派生輔助功能類,進而對不同的AccessibilityEvent進行處理。同樣的,這個服務就可以用來判斷當前的前臺應用
5.2. 方案特點
1. AccessibilityService有非常廣泛的 ROM 覆蓋,特別是非國產手機,從 API Level 8(Android 2.2) 到API Level 23(Android 6.0)。
2. AccessibilityService不再需要輪詢的判斷當前的應用是不是在前臺,系統會在窗口狀態發生變化的時候主動回調,耗時和資源消耗都極小。
3. 不需要權限請求
4. 它是一個穩定的方法,並非利用 Android 一些設計上的漏洞,可以長期使用的可能很大。
5. 可以用來判斷任意應用甚至 Activity,PopupWindow, Dialog 對象是否處於前臺。
5.3. 方案缺點
1、 需要用戶手動開啓輔助功能。
2、 輔助功能會伴隨應用被“強行停止”或第三方管理工具通過Root而剝奪,而且進程重啓後需要對用戶進行重新引導開啓。
3、 部分廠商可能對輔助功能進行限制,如已發現的vivo的部分機型
5.4. 方案實現
1、派生 ACCESSIBILITY SERVICE,創建窗口狀態探測服務:
4、 創建 ACCESSIBILITY SERVICE INFO 屬性文件:
5、 在AndroidManifes.xml中註冊第1步定義的Service:
6、 使用服務判斷應用是否在前臺:
5.5. 相關輔助接口
1、 跳轉到“輔助功能”設置界面的代碼如下:
2、判斷輔助功能是否開啓的代碼如下:
6. 自解析/process獲取
6.1. 技術原理
Linux系統內核會把process進程信息保存在/proc目錄下,通過JNI封裝接口,在JNI中去訪問並解析相關進程的設備節點,再根據進程的屬性判斷是否爲前臺
6.2. 方案特點
1、不需要任何權限。
2、可以判斷任意一個應用是否在前臺,而不侷限在自身應用。
3、當/proc下文件夾過多時,此方法是耗時操作。
4、該方案存在能耗問題。
5、在Android6.0.1以上版本或部分廠商版本受限於SEAndroid,只能獲取到第三方進程的信息。
6.3. 代碼實現
在Android層我們平時使用較多的進程屬性包括:pid、uid、processName、packageName、importance等,在自解析方案中只對這些屬性進行解析就可以滿足大部分需求。
該方案中需要進行解析的設備節點包括:
6.4. 代碼缺點
1、 該方案在6.0手機適配運行OK,但在最新的小米、華爲6.0.1手機中發現受SELinux的限制,無法讀取系統應用的設備節點進行解析,只能解析第三方應用設備節點。
2、 可能存在能耗問題。
6.5. 能耗問題解決
1、 Java層對象緩存:對調用比較頻繁的Java層對象在JNI中建立全局緩存,這就避免了每次調用時都需要通過JNI接口獲取。
對一些判斷是需要的場景在初始化時有Java層傳入Jni層,並建立全局緩存:
2、 過來爲Android進程:將pid小於1000的Native進程過濾掉。
3、 只解析發生變化的進程:在每次輪詢解析/proc節點時先判斷進程的pid在緩存中是否存在,如果存在只需要更新進程的優先級信息,其他信息不會發生變化;如果進程之前不存在則需要全新解析:
命中緩存時的解析代碼如下:
未命中緩存時,則進全新解析:
4、 在解析進程時,過來父進程爲zygote的進程:Android中所有應用進程的父進程都是zyote。
5、 在Java層對調用做緩存處理:對於調用比較頻繁的情況,如果當次Native調用沒有完成,則之前返回之前的值,不需要堵塞等待。
6、 對於只關心前臺進程的場景進行特殊處理:
通過優化後,適配方案的能耗與系統接口基本保持一致:
7. 作爲系統進程的獲取方式
7.1. 技術方案
雖然getRunningTask從Android5.0開始被系統廢棄,但是作爲系統應用時,該接口依然是可用的。在用戶取得Root權限,或者應用跟廠商進行合作時,應用本身可能會被內置的系統目錄,即:/system/app或/system/private-app目錄。因此對於這種情況,使用getRunningTask獲取依然是一種方便的實現。
7.2. 方案實現
1、 需要判斷應用是否爲系統應用:
2、 在AndroidManifest.xml中需要聲明如下權限: