本文來自於騰訊Bugly公眾號(weixinBugly),未經作者同意,請勿轉載,原文地址:https://mp.weixin.qq.com/s/OQRHEufCUXBA3d3DMZXMKQ
導語
本文主要講解了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個數限制 (需要找到對應源碼說明**)詳情 參看 :
查看Android 對應版本 _NUM_OP (目前最高支持5.1.1版本)
此時,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)站在前人的肩上
引用前人經驗:Android各大手機品牌手機跳轉到權限管理界面 (未一一驗證,畢竟沒那么多手機)
那是不是前人經驗一定對了,那就不一定了,在當時可能是對的,在現在可能就行不通了,現在以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工具
使用方法:可參見我之前的文章 :Android開發—— 小工具,大效率
使用Activity Tracer查看權限管理頁面對應的activity
文中引用鏈接:
- Working with System Permissions
https://developer.android.com/training/permissions/index.html - 查看Android 對應版本 _NUM_OP
http://grepcode.com/search?query=android.app.AppOpsManager&n= - PermissionsDispatcher
https://github.com/hotchemi/PermissionsDispatcher - AndPermission
https://github.com/yanzhenjie/AndPermission - Android各大手機品牌手機跳轉到權限管理界面
http://www.jianshu.com/p/b5c494dba0bc - Android開發—— 小工具,大效率
http://www.jianshu.com/p/672d64fdc486
更多精彩內容歡迎關注騰訊 Bugly的微信公眾賬號:
騰訊 Bugly是一款專為移動開發者打造的質量監控工具,幫助開發者快速,便捷的定位線上應用崩潰的情況以及解決方案。智能合並功能幫助開發同學把每天上報的數千條 Crash 根據根因合並分類,每日日報會列出影響用戶數最多的崩潰,精准定位功能幫助開發同學定位到出問題的代碼行,實時上報可以在發布后快速的了解應用的質量情況,適配最新的 iOS, Android 官方操作系統,鵝廠的工程師都在使用,快來加入我們吧!