1. 程式人生 > >Android 開發者必知必會的許可權管理知識

Android 開發者必知必會的許可權管理知識

導語

本文主要講解了Android 許可權管理方面幾個點:

  1. Android 許可權背景知識;
  2. 許可權檢查及許可權相容;
  3. 跳轉到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檢查許可權 即可 ,一般檢查流程 如下:

  1. 判斷是否有對應許可權
    (ContextCompat::checkSelfPermisson)

  2. 判斷是否需要解釋對應許可權用途(ActivityCompat::shouldShowRequestPermissionRationale)
    如果需要解釋,則現實自定義許可權介面即可

  3. 不需要解釋的話,直接請求對應許可權
    (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 官方作業系統,鵝廠的工程師都在使用,快來加入我們吧!