Android 開發者必知必會的許可權管理知識
導語
本文主要講解了Android 許可權管理方面幾個點:
- Android 許可權背景知識;
- 許可權檢查及許可權相容;
- 跳轉到app管理許可權頁面
一、Android 許可權背景知識
提到Android 許可權管理,業內人士都知道Google 在Android 6.0時提出了執行時許可權管理機制,在Android 6.0之前,所申請的許可權只需要在AndroidManifest.xml列舉就可以,從而容易導致一些安全隱患,因此,在Android 6.0 時,Google 為了更好的保護使用者隱私提出了新的許可權管理機制(官網 :Working with System Permissions
(1)Normal Permissions
Normal Permissions 一般不涉及使用者隱私,是不需要使用者進行授權的,比如手機震動、訪問網路等;
(2)Dangerous Permission
Dangerous Permission一般是涉及到使用者隱私的,需要使用者進行授權(動態申請),比如讀取SIM卡狀態、訪問通訊錄、SD卡讀寫等。
通過 adb shell pm list permissions -d -g 可以檢視 Dangerous Permission (以許可權組形式)
Dangerous Permission group
如上圖所示 :Dangerous Permission 一般以 Permission group 形式存在,只要 Permission group中某一個 permission 被Granted,則整個Permission group下的許可權均被Granted (目前是這樣,以後規則說不定會變)。
二、許可權檢查及許可權相容
本節主要介紹介紹如何進行許可權檢查及許可權相容,主要分為以下幾類:
(1)targetSdkVersion>=23,終端裝置是6.0(api 23)以上系統;
安裝的時候不會獲得許可權,在執行時向用戶申請對應許可權。這部分許可權檢查比較簡單,不涉及許可權相容,使用官方方案就可以 ,使用 Context::checkSelfPermisson ,建議使用ContextCompat::checkSelfPermisson檢查許可權 即可 ,一般檢查流程 如下:
判斷是否有對應許可權
(ContextCompat::checkSelfPermisson)判斷是否需要解釋對應許可權用途(ActivityCompat::shouldShowRequestPermissionRationale)
如果需要解釋,則現實自定義許可權介面即可不需要解釋的話,直接請求對應許可權
(ActivityCompat::requestPermissions)
上述情況較為簡單,在此不再贅述。
(2)targetSdkVersion<23,終端裝置是6.0(api 23)以上系統;
使用的是老的許可權機制,在app 安裝時會詢問AndroidManifest.xml檔案中的許可權,但是使用者可以在設定列表中關閉相關許可權,這種情況可能會對app正常執行造成一定影響。
(3) 終端裝置系統小於6.0(api 23)
大家可能要問,終端裝置系統小於6.0情況還需要考慮嗎,肯定是用的老的許可權管理機制,在app 安裝時會詢問AndroidManifest.xml檔案中的許可權,使用者關閉不了,真的是這樣嗎 ?
答案是否定的,在實測中發現,目前有不少國產Rom 手機在6.0之前就有關閉許可權的開關。這種情況也是我們相容的物件。
下面將會以自己開發過程中遇到的問題進行展開 ,目前企鵝FM支援免流了,需要使用READ_PHONE_STATE許可權 (讀取SIM卡狀態),由於之前未對改許可權是否關閉沒有進行相關判斷,因此收到了很多例因為上述許可權關閉,導致免流失敗的情況。
適配過程如下 :
(1)使用 try catch 來檢查許可權是否關閉
想法很簡單,如果改許可權被使用者禁止了,那肯定會異常,因此可以在catch 中做文章,結果發現這一招根本沒有用,為啥了 ?因為使用 READ_PHONE_STATE 許可權的方法內部已經try catch ,外面無法捕獲,因此該方法失效。
(2)ContextCompat::checkSelfPermisson
既然在6.0 可以使用Context::checkSelfPermisson進行許可權檢查,那能否使用support v4 中的ContextCompat::checkSelfPermisson 方法了,試一下,發現在api 23 以下失效,為了探究原因,查看了ActivityCompat::requestPermissons 內部實現,如下
ActivityCompat::requestPermissons
內部許可權檢查方法在api 23 以下,使用的是 PackageManager::checkPermission,再去檢視PackageManager::checkPermission方法,如下:發現只要許可權在AndroidManifest.xml中註冊過,均會認為該許可權granted ,因此上述方法在api 23 以下也失效。
PackageManager::checkPermission
查閱相關資料和請教組內同事,發現Support V4 下面有一個專門檢查許可權的工具類PermissionChecker。
(3)PermissionChecker
檢視PermissionChecker原始碼發現 ,PermissionChecker內部實際上使用的是AppOpsManagerCompt,而AppOpsManager是在api 19 加入進入的(AppOpsManager後面介紹)
PermissionChecker::checkPermission
進而檢視AppOpsManagerCompat 內部實現
AppOpsManagerCompat::permissionToOp
IMPL實現如下:
IMPL實現
從上圖可以看出:在api 23以下, AppOpsManagerImpl::permissionToOp 直接返回為null ,這直接導致api 23以下許可權檢查將會返回 granted ,因此,該方法在api 23 下,許可權檢查方法也會失效。
(4)AppOpsManager
API 19以上 ,Google 官方提供了 AppOpsManager 類來檢查許可權,看到這個api 時,腦海浮現出 “天無絕人之路啊”,裡面有兩個比較重要的方法 :AppOpsManager::checkOp(int op ,int uid ,String packageName) (hide方法)和AppOpsManager::checkOp(String op,int uid ,String packageName)(public 方法 ,api 23 以上可用),不經思考,直接寫出瞭如下兩個方法
1)AppOpsManager::checkOp(int op ,int uid ,String packageName)
需要使用反射 :
AppOpsManager::checkOp(int op ,int uid ,String packageName)
2)AppOpsManager::checkOp(String op,int uid ,String packageName)
API >= 23 才可以使用 :
AppOpsManager::checkOp(String op,int uid ,String packageName)
在實測中發現,api 低於23時 ,OP_READ_PHONE_STATE =51 找不到,導致反射失敗。
仔細察看了一下 6.0 (API 23 )_NUM_OP = 62,如下,為何找不到51 了
6.0 (API 23 )_NUM_OP = 62
難不成 每個版本還不一樣,檢視其他版本,驗證了這個想法:
5.1.1 (API 22 )_NUM_OP = 48
5.1.1
Op個數限制 (需要找到對應原始碼說明**)詳情 參看 :
此時,OP_READ_PHONE_STATE = 51 在6.0(API 23)以下,通過反射是找不到的,因此對於READ_PHONE_STATE許可權檢查僅限於6.0及6.0以上。
同時也仔細看了一下AppOpsManager 類介紹,並不是為開發者設計的,不過其他許可權相容可以使用這種方法,前提是 要看OP_*是在什麼版本才有的,需做相容方案 。
public class AppOpsManager
extends Object
API for interacting with “application operation” tracking.
This API is not generally intended for third party application developers; most features are only available to system applications. Obtain an instance of it through Context.getSystemService with Context.APP_OPS_SERVICE.
(5)最後查看了幾個第三方許可權庫(暫未看完)
三、跳轉到app管理許可權頁面
既然在這裡講解跳轉到app 管理許可權頁面的方法,可想而之,事情絕對不太簡單。Android 碎片化不僅在存在於ui適配 ,同樣也存在於這裡,導致我們無法使用同一種方式跳轉到app管理許可權頁面(適配,Android 開發永遠的痛)。
那有沒有辦法可以簡化適配工作,減少開發量,方法當然有,不過需要我們自己去總結和探索的,目前已有方法:
(1)直接跳轉到系統設定頁
Intent intent =newIntent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package",getPackageName(), null));
try{
startActivity(intent);
}catch(Exception exception) {
exception.printStackTrace();
}
記得要新增上 try catch ,不加可能會crash。這種方式就不需要適配各個廠商的不同版本rom,缺點是,使用者只能跳轉到系統設定頁,然後去找對應app 的許可權管理(總會有一些使用者找不到)
(2)站在前人的肩上
那是不是前人經驗一定對了,那就不一定了,在當時可能是對的,在現在可能就行不通了,現在以MIUI跳轉到app 許可權管理頁面為例進行說明。
1)MIUI 6/7
Intent localIntent = new Intent("miui.intent.action.APP_PERM_EDITOR");
localIntent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity");
localIntent.putExtra("extra_pkgname", context.getPackageName());
try{
startActivity(intent);
}catch(Exception exception) {
exception.printStackTrace();
}
2)MIUI 8
Intent localIntent = new Intent("miui.intent.action.APP_PERM_EDITOR");
localIntent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.PermissionsEditorActivity");
localIntent.putExtra("extra_pkgname", context.getPackageName());
try{
startActivity(intent);
}catch(Exception exception) {
exception.printStackTrace();
}
對比1)和2)發現,在MIUI 6/7 和MIUI 8 上面,許可權管理頁面的activity名字不一樣了,因此使用MIUI6/7的方法在MIUI8上就會失效,如果沒有加上try catch ,就會直接crash。
對於上述變化,作為一個開發者一般都是不知道的,即便通過反饋發現了這個問題,也有可能不知道對應的activity是什麼,此刻要麼搜尋網上有沒有類似解決方案,要麼求助於對應rom 開發廠商的開發者論壇 (有時解決迴應速度相當慢),那有沒有更好的辦法了,方法詳見(3)部分。
(3)檢視某個ROM的某個版本的許可權管理頁面的activity
這裡以華為p8為例簡要說明,詳細步驟如下:
1)通過設定找到對應app的許可權管理頁面,如下:
企鵝fm在華為p8上的許可權管理頁面
2)找到對應頁面的activity
方法一:通過add 工具檢視棧頂Activity
adb shell dumpsys activity | grep "mFocusedActivity"
企鵝fm在華為p8上的許可權管理頁面對應的activity
更為詳細的堆疊資訊
詳細的堆疊資訊
方法二:使用Activity Tracer工具
使用Activity Tracer檢視許可權管理頁面對應的activity
更多精彩內容歡迎關注騰訊 Bugly的微信公眾賬號:
騰訊 Bugly是一款專為移動開發者打造的質量監控工具,幫助開發者快速,便捷的定位線上應用崩潰的情況以及解決方案。智慧合併功能幫助開發同學把每天上報的數千條 Crash 根據根因合併分類,每日日報會列出影響使用者數最多的崩潰,精準定位功能幫助開發同學定位到出問題的程式碼行,實時上報可以在釋出後快速的瞭解應用的質量情況,適配最新的 iOS, Android 官方作業系統,鵝廠的工程師都在使用,快來加入我們吧!