前言
從Android6.0開始,Android系統對權限的處理產生了很大的變化。如果APP運行的設備系統版本為Android6.0或更高,並且target在23或更高,那么dangerious級別的權限將由之前的安裝時授予變成運行時動態申請。這樣一來,當運用到系統權限相關的功能時,就需要手動處理好權限申請的用戶交互問題。本文將結合官網中的介紹,來全方位了解權限相關知識點,並介紹一些實際工作中可能用到的技巧。
本文的主要內容如下:
一、為什么要引入“權限”
Android系統引入權限的目的是為了保護Android用戶的隱私。Android安全架構設計中一個明確點,默認情況下App是沒有權限對其他App、操作系統和用戶進行有害的操作。這些有害操作包括讀寫用戶的隱私數據(如通訊錄、電子郵件等)、讀寫其它App的文件、調起硬件設備(如藍牙、Wifi、相機等),訪問網絡等。App如果想要進行這些操作,就需要申請相應的權限,如下網址提供了Android系統定義的所有權限【https://developer.android.google.cn/reference/android/Manifest.permission】,均以常量的形式供開發者使用,開發者可以通過Manifest.permission.X的方式調用。
二、Android權限與官網
官網(中文版:https://developer.android.google.cn)中對Andrid權限有非常全面的描述和使用指導,依次通過 首頁 》 文檔 》 指南,可以看到如下界面,和權限相關的知識點主要都在這里。咱們先了解一下里面都有些什么內容吧!
1、Overview
宏觀上介紹了權限相關聯的知識點,整體內容如下圖所示:
(1)Permission approval
該部分主要從用戶交互角度介紹了當前原聲機上申請權限的交互形式。比如對話框顯示怎樣的內容等。
(2)Permissions for optional hardware feature
這部分主要講手機硬件特征和與之相關的權限問題。比如手機沒有相機時,是否允許安裝需要申請相機權限的app等。
(3)Permission enforcement
這部分主要講Permission在四大組件中的其它用法。Permission不僅僅只能用於請求系統功能,還可以通過自定義權限,來限制誰有權限啟動/調用或訪問指定的組件/數據等。Activity、Service、Broadcast Receiver和Content Provider這四大組件可以結合Permission這方面的功能,來防止被任意訪問或修改。
(4)Automatic permission adjustments
這部分主要講權限與版本前后兼容的問題。比如在高版本中才定義的新權限,在低版本中如何表現的問題等。
(5)Protection levels
App不同的操作,所可能產生的風險也是不一樣的。比如獲取當前的網絡狀態,最多也只會讓App探測到周圍的網絡情況,而讀寫用戶的通訊錄則不同,用戶的信息則有被篡改和道竊的風險。所以,根據可能產生的風險程度,系統將“權限”分為了不同的保護等級。對於第三方app而言,有三個等級,嚴重程度有輕到重依次為:普通權限(Normal Permission)、簽名權限(Signature Permission)和危險權限(Dangerous Permission)。
1)普通權限
普通權限的覆蓋區域為,app需要訪問“沙盒”以外的數據以及資源的權限,這些對用戶隱私和對其它app的操作產生的風險微乎其微。比如,修改系統時區、獲取網絡狀態等。這部分還列出了一些常用的普通權限.
2)簽名權限
系統會在安裝app的時候授予該類權限,但是只有當這個試圖使用這個權限的app和定義這個權限的app被相同的證書簽名的時候,才能生效。有些簽名權限就不用於第三方app的。該部分還列出了一些常見的可以供第三方app使用的簽名權限。
3)危險權限
危險權限覆蓋了如下情形,當用戶需要的數據和資源涉及到用戶隱私信息,或者可能影響到用戶的存儲數據或其它app的操作,比如讀取用戶的聯系人的能力就是一個危險權限。該部分還列出了一些常見的危險權限。
4)特殊權限
除了上述的3中主要的權限分類外,還有兩個特殊的權限:SYSTEM_ALART_WINDOW和WRITE_SETTINGS。至於其特殊性,咱們在后面的章節單獨來介紹。
(6)Permission groups
該部分介紹了權限組相關的知識點。
(7)View an app`s permissions
該部分介紹了兩個adb命令 ,一個用於查看app的權限申請情況,另外一個用於給app授予所有的權限。
(8)Additional resources
提供了一些鏈接,用於了解更多關於Android權限相關的知識。
2、Request app permission
這篇文章主要講解如何在代碼中實現權限檢查,權限請求,以及如何處理拒絕/同樣授權后的代碼邏輯。這里面提供了Android API中的接口,具體的代碼示例等。
3、App permissions best practices
這邊文章主要是從用於體驗的角度來指導開發者,如何設計交互,如何做好測試,以及需要注意的原則等。
4、Define custom permissions
這篇文章主要指導開發者如何自定義權限。
三、特殊權限
上一節中簡單提到了特殊權限,其實就是兩個權限:SYSTEM_ALART_WINDOW和WRITE_SETTINGS。Android6.0開始,除了危險權限需要動態申請外,這兩個特殊權限也是一樣需要動態申請。他們尤其敏感,行為也和其他的權限不一樣,所以對於大多數app來說一般不應該使用它們。如果app需要其中一個權限,必須在manifest文件中申明,並發送一個intent請求用戶授權。系統會顯示一個詳細的管理界面給用戶,讓用戶決定是否來授權。如下截圖是以“微信”為例,在設置中可以看到如下界面,“高級”模塊中顯示的兩項,就是設置這兩項權限的入口。
1、WRITE_SETTINGS
在Android6.0及以后,該權限的保護等級已經由原來的dangerious升級為signature,這意味着我們的APP需要用系統簽名或者成為系統預裝軟件才能夠申請該權限,並且還需要提示用戶跳轉到修改系統的設置界面去授予此權限。這就意味着,要想申請該權限,apk必須要簽名而且打包,debug模式將無法申請該權限。
(1)官網描述
官網【https://developer.android.google.cn/reference/android/Manifest.permission.html#WRITE_SETTINGS】對該權限的描述如下所示:
(2)權限設置界面
點擊“高級”中的“修改系統設置”項,可以進入到該權限的授予界面,如下圖所示:
(3)權限申請使用示例
在第四節中將會講解危險權限檢測和申請的具體實例,但是WRITE_SETTINGS權限不能通過這種方式來處理。如果使用checkPermissions()檢測該權限,無論你是否已經授權,它都會返回false。如下展示了具體處理該權限的實例:
首先,在AndroidManifest.xml文件中申請該權限
1 <uses-permission android:name="android.permission.WRITE_SETTINGS" />
然后,根據官網中的描述,先通過API中的canWrite()方法判斷是否允許修改,再通過指定的action跳轉到設置界面。
1 /** 2 * 申請權限 3 */ 4 private void requestWriteSettings() 5 { 6 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) 7 { 8 //大於等於23 請求權限 9 if ( !Settings.System.canWrite(getApplicationContext())) 10 { 11 Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS); 12 intent.setData(Uri.parse("package:" + getPackageName())); 13 startActivityForResult(intent, REQUEST_CODE_WRITE_SETTINGS ); 14 } 15 }else{ 16 //小於23直接設置 17 } 18 }
最后,根據回調方法和requestCode來處理授權/拒絕邏輯。
1 @Override 2 protected void onActivityResult(int requestCode, int resultCode, Intent data) 3 { 4 super.onActivityResult(requestCode, resultCode, data); 5 if (requestCode == REQUEST_CODE_WRITE_SETTINGS) 6 { 7 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) 8 { 9 //Settings.System.canWrite方法檢測授權結果 10 if (Settings.System.canWrite(getApplicationContext())) 11 { 12 //獲取了權限 13 }else{ 14 //拒絕了權限 15 } 16 } 17 } 18 19 }
2、SYSTEM_ALART_WINDOW
(1)官網描述
官網【https://developer.android.google.cn/reference/android/Manifest.permission.html#SYSTEM_ALERT_WINDOW】的描述如下所示:
(2)權限設置界面
點擊“高級”中的“顯示在其它應用的上層”項,可以進入到該權限的授予界面,如下圖所示:
(3)權限申請示例
該權限的申請和WRITE_SETTINGS類似,咱們這里參照前面,就不給出全部代碼,僅提供一些關鍵函數:
在清單文件中聲明
1 <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
結合官網中的說明,使用系統提供的API來判斷和跳轉授權設置界面。
1 //檢查是否已經授予權限 2 if (!Settings.canDrawOverlays(this)) { 3 //若未授權則請求權限 4 } 5 ...... 6 //前往設置界面授予權限 7 private void getOverlayPermission() { 8 Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); 9 intent.setData(Uri.parse("package:" + getPackageName())); 10 startActivityForResult(intent, 0); 11 } 12 ...... 13 @Override 14 protected void onActivityResult(int requestCode, int resultCode, Intent data){ 15 //根據requestCode來處理授權/拒絕邏輯。 16 }
從官網上的說明可以看到,這兩個特殊權限的權限級別都是“signature”,可見這兩個權限並不是獨立的權限級別。
四、動態申請權限示例代碼記錄
如下代碼是筆者平時工作當中經常使用的一個實例,方便實用,無需再引入第三方jar包。數組變量permissionRequest中定義了一個相機權限作為實例,讀者可以根據需要拓展為多個。一般來說,申請權限是在進入app時第一個Activity中,或者在需要用到該權限的地方進行,所以checkAndRequestPermissions()一般在第一個Activity的onCreate()方法中調用,或者在需要使用該權限的地方調用。其他地方代碼邏輯比較簡單,就不贅述了。
1 private String[] permissionRequest = new String[]{ 2 Manifest.permission.CAMERA 3 }; 4 private boolean isNeedRequestPermission = false; 5 private static final int REQUEST_PERMISSION = 1; 6 ...... 7 public void checkAndRequestPermissions() { 8 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 9 for (String permissionStr : permissionRequest) { 10 if (ActivityCompat.checkSelfPermission(this, permissionStr) != PackageManager.PERMISSION_GRANTED) { 11 isNeedRequestPermission = true; 12 break; 13 } else { 14 isNeedRequestPermission = false; 15 } 16 } 17 if (isNeedRequestPermission) { 18 requestPermissions(permissionRequest, REQUEST_PERMISSION); 19 } else { 20 //do what you want 21 } 22 } 23 } 24 ...... 25 @Override 26 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 27 super.onRequestPermissionsResult(requestCode, permissions, grantResults); 28 for (int index = 0; index < grantResults.length; index++) { 29 if (PackageManager.PERMISSION_DENIED == grantResults[index]) { 30 finish(); 31 return; 32 } 33 } 34 //do what you want 35 }
五、一種在Service中申請權限的方案
Android中申請權限的函數requestPermissions()和回調函數onRequestPermissionsResult()都是Acitivity.java中提供的,所以在Activity或Fragment中很容易調用這兩方法來處理權限問題。但是在Service中就不容易處理了,因為Service中Android API沒有提供類似的接口。而且Android中有不少應用是通過IPC方式,用Service向其它APP提供功能,沒有Activity或Fragment,這樣就無法直接請求權限了。這里介紹一種采用間接的方式處理權限的方案:需要申請權限時,跳轉到一個透明的Activity中來完成權限的申請。
PermissionDemoActivity直接繼承自Activity,在清單文件中如下設置theme,這樣就成了一個透明的Activity。在onRusume()方法中調用finish()方法,否則會報錯。
1 <activity 2 android:name=".PermissionDemoActivity" 3 android:theme="@android:style/Theme.NoDisplay" />
結合上一節中的代碼示例,在PermissionDemoActivity的onCreate()方法中申請權限,這樣就間接地通過一個透明的Acitivity來實現權限的申請了。
六、常用的與Permission相關的adb命令
android工具adb提供了一些命令,可以方便查看、授權、取消應用的權限,可以為調試程序帶來不少的方便,下面簡單介紹幾個常見的命令。
1、查看指定app中權限申請情況
命令:adb shell dumpsys package [包名]
用途:該命令用於獲取該app的package信息,Permission信息只是其中的一部分。
命令使用示例:
1 adb shell dumpsys package cn.aaa.bbb
如下下截圖為該命令中關於權限的部分信息:
該圖顯示了4部分權限:
(1)declared permissions。該應用自己聲明(即自定義)的權限,這里顯示了權限名,權限等級,以及在什么時候獲取該權限(INSTALLED 表示安裝的時候就會授予該權限)。
(2)requested permissions。這里列出的是AndroidManifest.xml文件中所有request的權限,可以看出這里面包含了動態申請的權限和安裝時申請的權限。
(3)install permissions:安裝的時候就賦予的權限。可以和requested permissions對比一下,這里面少了一"android.permission.CAMERA"權限,該權限為動態申請權限。該列表中還展示了權限對應的授予情況,如granted所示,true表示已經被授予了權限。
(4)runtime permissions。這里顯示的是運行時才需要申請的權限,即dangerous permission。
2、查看權限的聲明者和使用者
命令:adb shell dumpsys package permission <權限名>
用途:該命令可以查看指定權限是誰聲明的,有哪些應用申請了該權限。
命令使用示例:
1 adb shell dumpsys package permission cn.aaa.bbb.TEST_PERMISSION
如下節選了該權限的定義信息和其中一個使用該權限的應用的關鍵信息:
1 Permissions: 2 Permission [cn.aaa.bbb.TEST_PERMISSION] (d4d8316): 3 sourcePackage=cn.aaa.bbb 4 uid=10078 gids=null type=0 prot=signature|privileged 5 perm=Permission{f5b497 cn.aaa.bbb.TEST_PERMISSION} 6 packageSetting=PackageSetting{96e1684 cn.aaa.bbb/10078} 7 8 Packages: 9 Package [cn.xxx.xxx] (5d0f51b): 10 ...... 11 declared permissions: 12 requested permissions: 13 install permissions: 14 cn.aaa.bbb.TEST_PERMISSION: granted=true 15 16 ......
3、移除指定權限
命令:adb shell pm revoke [packageName] [permissionName]
用途:移除packageName應用的permissionName權限(可以同時移除多項權限)。
命令使用示例(如下為刪除包名為cn.aaa.bbb 的相機權限):
1 adb shell pm revoke cn.aaa.bbb android.permission.CAMERA
執行完該命令后,用前文提到的命令“adb shell dumpsys package cn.aaa.bbb”查看該權限的信息如下:
通過實驗發現,該命令對runtime permissions有效,卻對install permissions無效,如以下異常信息所示:
4、授予指定權限
命令:adb shell pm grant [packageName] [permissionName]
用途:為packageName應用授予permissionName權限(可以同時授予多項權限)。該命令和上一條移除命令相對應。
參照上一條命令的實例,實驗結果如下:
5、查看系統定義的所有權限
命令:adb shell pm list permissions -s[option] 不加-s會顯系統中定義的所有權限名列表,加了-s會顯示對這些權限的用途說明。
下面截圖分別展示了命令不加-s和加了-s后的顯示結果(重定向到文本中查看),其中不加-s的截圖中,一共顯示了571條權限,這里截取了一部分,其中可以看到不少自定義的權限。
6、按組查看權限
命令:adb shell pm list permissions -d -g
用途:查看權限的分組情況。這部分是上面一條命令的補充,參數可以根據自己的需要選擇。
參考:【https://developer.android.google.cn/training/permissions/usage-notes#testing】
下列截圖為結果的一部分。
7、授予所有權限
命令:adb shell install -g MyApp.apk
用途:當安裝MyApp.apk到模擬器或測試機上時,如果加上-g,可以自動授予所有權限。這一點筆者沒有實驗過,讀者可以自行測試。
參考:該處和第4點一樣參考官網說明。
推薦閱讀
【Android權限的一些細節https://blog.csdn.net/u013553529/article/details/53167072】
結語
Anroid權限知識點其實沒有太多需要理解的邏輯問題,一般都是一些需要記住的知識點。本文的一些實際操作技巧,很多都是筆者工作中用到的,具有較強的實踐參考作用。由於筆者的經驗和水平有限,如果有描述不妥當或不准確的地方,請不吝賜教,謝謝!