這篇文章爲你們系統的梳理一下 Android 權限相關的知識,在平常開發中,咱們都用過權限,可是對於權限的一些細節咱們可能掌握的還不夠全面,這篇文章會全面的爲你們介紹權限相關的知識。固然,本篇文章依然是參考了 Google 的官方文檔:應用權限。php
Android 系統設置權限的目的是保護 Android 用戶的隱私。對於用戶的敏感數據 Android 應用程序必須向用戶申請受權後才能訪問(如聯繫人和短信),另外還包括某些系統功能(如攝像頭、麥克風)的權限。根據功能的不一樣,系統可能會自動授予權限或提示用戶批准請求。Android 安全架構的一個核心設計要點是,在默認狀況下,沒有應用程序能夠執行任何可能對其餘應用程序、操做系統或用戶形成不利影響的操做。這包括讀取或寫入用戶的私人數據(如聯繫人或電子郵件)、讀取或寫入另外一個應用程序的文件、執行網絡訪問等等。java
權限分爲幾個保護級別。保護級別影響是否須要運行時權限請求:android
須要咱們瞭解的是正常權限和危險權限。安全
1.正常權限網絡
正常的權限覆蓋了應用程序須要訪問沙箱以外的數據或資源的區域,但這些區域對用戶隱私或其餘應用程序的操做幾乎沒有風險。例如,設置時區的權限是正常的權限。 若是應用程序在它的清單中聲明它須要一個正常的權限,系統會在安裝時自動授予該權限。系統不提示用戶授予正常權限,用戶也不能撤銷這些權限。架構
2.危險權限併發
危險權限包括應用程序須要涉及用戶私人信息的數據或資源的區域,也包括可能影響用戶存儲的數據或其餘應用程序的操做的區域。例如,讀取用戶的聯繫人是一種危險的權限。若是一個應用程序聲明它須要一個危險的權限,用戶必須顯式地授予該應用程序權限。在用戶批准該權限以前,應用程序不能提供依賴於該權限的功能。 要使用危險的權限,應用程序必須在運行時提示用戶授予權限。app
應用程序必須經過在清單文件(AndroidManifest.xml
)中使用 <uses-permission>
標記來公佈它須要的權限。例如聲明網絡訪問權限:async
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapp">
//聲明網絡訪問權限
<uses-permission android:name="android.permission.INTERNET"/>
<application ...>
...
</application>
</manifest>
複製代碼
若是在應用程序的清單文件中列出的是正常的權限(即不會對用戶隱私或設備操做形成太大風險的權限),系統會自動將這些權限授予給應用程序。ide
若是在應用程序的清單中列出的是危險的權限(便可能影響用戶隱私或設備正常操做的權限),則必須通過用戶的贊成才能受權相應的權限。
Android 請求用戶授予危險權限的方式取決於用戶設備上運行的 Android 版本,以及咱們在應用中設置的 targetSdkVersion
。主要有兩種處理方式:
若是手機 Android 系統的版本是 6.0 (API級別23) 或者更高,而應用程序的 targetSdkVersion
是 23 或者更高,用戶在安裝時不會收到任何應用程序權限通知。應用程序必需要求用戶在運行時授予危險的權限。當應用程序請求權限時,用戶會看到一個系統對話框,告訴用戶應用程序試圖訪問哪一個權限組。對話框包含一個拒絕和容許按鈕。
若是用戶拒絕權限請求,那麼下一次應用程序請求該權限時,對話框將包含一個複選框,選中該複選框後,用戶不會再收到權限申請提示。
下面經過申請拍照權限爲例:
假如用戶選擇了「容許」,也不能表示應用就會一直擁有該權限。用戶還能夠進入系統設置頁面,將以前那授予的權限關閉掉,所以,咱們在開發中必須在運行時去檢查和申請相應的權限,以防止在運行時出現 SecurityException
的錯誤,致使應用奔潰。
若是手機 Android 系統的版本是 5.1.1 (API級別22) 或者更低,而應用程序的 targetSdkVersion
是 22 或者更低,系統會自動要求用戶在安裝時爲應用程序授予全部危險的權限。
Accept
,應用程序請求的全部權限都將被授予。若是用戶拒絕權限請求,系統將取消應用程序的安裝。若是應用程序更新須要額外的權限,用戶在更新應用程序以前會被提示接受這些新的權限。
有兩個權限的行爲不像正常權限和危險權限:SYSTEM_ALERT_WINDOW
和 WRITE_SETTINGS
這是兩個特別敏感的權限,因此大多數應用程序不該該使用它們。若是應用程序須要這些權限之一,它必須在清單中聲明該權限,併發送一個意圖請求用戶的受權。系統經過向用戶顯示詳細的管理屏幕來響應這個意圖。
以申請 SYSTEM_ALERT_WINDOW
爲例:
step 1:首先在清單文件中聲明權限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
複製代碼
step 2:申請權限(6.0 及其以上版本)
//在 6.0 之前的系統版本,懸浮窗權限是默認開啓的,直接使用便可。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(context)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
startActivity(intent);
return;
}
}
複製代碼
Android 系統對全部的危險權限進行了分組,稱爲 權限組
。
權限組 | 權限 |
---|---|
CALENDAR | READ_CALENDAR WRITE_CALENDAR |
CAMERA | CAMERA |
CONTACTS | READ_CONTACTS WRITE_CONTACTS GET_ACCOUNTS |
LOCATION | ACCESS_FINE_LOCATION ACCESS_COARSE_LOCATION |
MICROPHONE | RECORD_AUDIO |
PHONE | READ_PHONE_STATE CALL_PHONE READ_CALL_LOG WRITE_CALL_LOG ADD_VOICEMAIL USE_SIP PROCESS_OUTGOING_CALLS |
SENSORS | BODY_SENSORS |
SMS | SEND_SMS RECEIVE_SMS READ_SMS RECEIVE_WAP_PUSH RECEIVE_MMS |
STORAGE | READ_EXTERNAL_STORAGE WRITE_EXTERNAL_STORAGE |
若是手機 Android 系統的版本是 6.0 (API級別23) 或者更高,而應用程序的 targetSdkVersion
是 23 或者更高,則當應用程序請求危險權限時,系統會有以下行爲:
若是應用程序當前在權限組中沒有任何權限,則系統向描述應用程序但願訪問的權限組的用戶顯示權限請求對話框。對話框沒有描述該組中的特定權限。例如,若是一個應用程序請求READ_CONTACTS權限,系統對話框只告訴應用程序須要訪問設備的聯繫人。若是用戶給予批准,系統只會給應用程序它所請求的權限。 若是應用程序已經在同一權限組中被授予了另外一個危險權限,系統會當即授予該權限,而不與用戶進行任何交互。例如,若是一個應用程序以前請求並被授予了READ_CONTACTS權限,而後它請求WRITE_CONTACTS,系統當即授予該權限,而不向用戶顯示權限對話框。
上面是官方的原話,簡而言之就是:屬於同一組的危險權限將自動合併授予,用戶授予應用某個權限組的權限,則應用將得到該權限組下的全部權限(前提是相關權限在 AndroidManifest.xml 中有聲明)。
然而事實真的如此嗎?咱們來試驗一下屬於同一個權限組下的 READ_CONTACTS
權限和 WRITE_CONTACTS
權限。按照官方的說法,若是我先受權了 READ_CONTACTS
權限,那麼 WRITE_CONTACTS
權限會被自動授予,咱們來看看實際運行的效果:
READ_CONTACTS
權限後,再去申請
WRITE_CONTACTS
權限時,依舊彈出了對話框讓用戶受權,這明顯和官方文檔說明的不一致。可是同時官方建議咱們,
不要將應用程序的邏輯創建在這些權限組的結構上,由於在將來的版本中,可能會將一個特定的權限從一個組移動到另外一個組,所以,咱們的代碼邏輯不該該依賴權限組,而是應該顯式地請求它須要的每一個權限,即便用戶已經在同一組中授予了另外一個權限。
每款 Android 應用都在訪問受限的沙盒中運行。若是應用須要使用其本身的沙盒外的資源或信息,則必須請求相應權限。 要聲明應用須要某項權限,能夠在應用清單中列出該權限,而後在運行時請求用戶批准每項權限(適用於 Android 6.0 及更高版本)。
不管應用須要什麼權限,都須要在清單文件中對權限進行聲明。系統會根據聲明權限的敏感程度採起不一樣的操做。有些權限被視爲「常規」權限,系統會在安裝應用時當即授予這些權限。還有些則被視爲「危險」權限,須要用戶明確授予相應訪問權限。
若是應用須要一項危險權限,那麼每次執行須要該權限的操做時,都必須檢查本身是否具備該權限。從 Android 6.0(API 級別 23)開始,用戶可隨時從任何應用撤消權限,即便應用以較低的 API 級別爲目標平臺也是如此。所以,即便應用昨天使用了相機,也不能認爲它今天仍具備該權限。
要檢查應用是否具備某項權限,請調用 ContextCompat.checkSelfPermission()
方法。例如,如下代碼段展現瞭如何檢查 Activity 是否具備向日歷寫入數據的權限:
if (ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.WRITE_CALENDAR)
!= PackageManager.PERMISSION_GRANTED) {
// Permission is not granted
}
複製代碼
若是應用具備此權限,該方法將返回 PERMISSION_GRANTED
,而且應用能夠繼續操做。若是應用不具有此權限,該方法將返回 PERMISSION_DENIED
,且應用必須明確要求用戶授予權限。
當應用從 checkSelfPermission()
收到 PERMISSION_DENIED
時,須要提示用戶授予該權限。Android 提供了幾種可用來請求權限的方法(如 requestPermissions()),以下面的代碼段所示。調用這些方法時,會顯示一個沒法自定義的標準 Android 對話框。
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// Permission is not granted
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) {
// Show an explanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
} else {
// No explanation needed; request the permission
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
// app-defined int constant. The callback method gets the
// result of the request.
}
} else {
// Permission has already been granted
}
複製代碼
在某些狀況下,須要幫助用戶理解爲何應用須要某項權限。例如,若是用戶啓動一款攝影應用,用戶或許不會對該應用請求使用相機的權限感到驚訝,但用戶可能不理解爲何該應用想要訪問用戶的位置或聯繫人。在應用請求權限以前,能夠向用戶提供解釋。一種比較好的作法是在用戶以前拒絕過該權限請求的狀況下提供解釋。咱們經過調用 shouldShowRequestPermissionRationale()
方法來實現。若是用戶以前拒絕了該請求,該方法將返回 true。若是用戶以前拒絕了該權限而且選中了權限請求對話框中的再也不詢問選項,或者若是設備政策禁止該權限,該方法將返回 false(注意,若是用戶拒絕了該權限,而且勾選了「再也不詢問」,即便在返回false的邏輯中調用了requestPermissions方法,系統也不會再彈出選擇框)。
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the
// contacts-related task you need to do.
} else {
// permission denied, boo! Disable the
// functionality that depends on this permission.
}
return;
}
// other 'case' lines to check for other
// permissions this app might request.
}
}
複製代碼
Android 是一個特權分離的操做系統,其中每一個應用程序都使用一個惟一的系統標識(Linux 用戶 ID 和 組 ID)運行。系統也被稱不一樣的部分,每一個部分都有本身的標識。所以,Linux 將應用程序彼此隔離,並與系統隔離。應用程序能夠自定義權限來提供給其餘應用程序訪問本身的功能。
要自定義權限,能夠在 AndroidManifest.xml
中使用 <permission>
標籤來聲明。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapp" >
<permission android:name="com.example.myapp.permission.DEADLY_ACTIVITY" android:label="@string/permlab_deadlyActivity" android:description="@string/permdesc_deadlyActivity" android:permissionGroup="android.permission-group.COST_MONEY" android:protectionLevel="dangerous" />
...
</manifest>
複製代碼
屬性解釋:
下面咱們來寫一個具體的例子:咱們在進程1中定義一個 Activity,併爲該 Activity 設置訪問權限,而後讓進程2來訪問它。
進程1:
AndroidManifest.xml 文件
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.chenyouyu.permissiondemo">
//自定義的權限,權限級別爲 normal
<permission android:name="com.example.myapp.permission.SECOND_ACTIVITY" android:label="abc" android:description="@string/permdesc_SecondActivity" android:permissionGroup="android.permission-group.COST_MONEY" android:protectionLevel="normal" />
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme">
//爲SecondActivity加上android:permission="com.example.myapp.permission.SECOND_ACTIVITY"
<activity android:name=".SecondActivity" android:exported="true" android:permission="com.example.myapp.permission.SECOND_ACTIVITY">
<intent-filter>
<action android:name="com.cyy.jump" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
......
</manifest>
複製代碼
進程2:
AndroidManifest.xml 文件
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.chenyouyu.permissiondemo2">
//在AndroidManifest中聲明權限
<uses-permission android:name="com.example.myapp.permission.SECOND_ACTIVITY"/>
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
複製代碼
MainActivity.java
Intent intent = new Intent();
intent.setAction("com.cyy.jump");
intent.addCategory(Intent.CATEGORY_DEFAULT);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}
複製代碼
首先運行進程1,而後運行進程2。
Android 不容許兩個不一樣的應用定義一個相同名字的權限(除非這兩個應用擁有相同的簽名),因此在命名的時候,須要特別注意。擁有相同自定義權限的軟件必須使用一樣的簽名,不然後一個程序沒法安裝。
場景:App A中聲明瞭權限PermissionA,App B中使用了權限PermissionA。
狀況一:PermissionA的保護級別是normal或者dangerous App B先安裝,App A後安裝,此時App B沒法獲取PermissionA的權限,從App B打開App A會報權限錯誤。 App A先安裝,App B後安裝,從App B打開App A一切正常。
狀況二:PermissionA的保護級別是signature或者signatureOrSystem App B先安裝,App A後安裝,若是App A和App B是相同的簽名,那麼App B能夠獲取到PermissionA的權限。若是App A和App B的簽名不一樣,則App B獲取不到PermissionA權限。即,對於相同簽名的app來講,不論安裝前後,只要是聲明瞭權限,請求該權限的app就會得到該權限。
這也說明了對於具備相同簽名的系統app來講,安裝過程不會考慮權限依賴的狀況。安裝系統app時,按照某個順序(例如名字排序,目錄位置排序等)安裝便可,等全部app安裝完了,全部使用權限的app都會得到權限。
Android6.0引入了動態權限,這個你們都知道了。前面說到的自定義的權限的安全級別android:protectionLevel會影響權限在Android6.0+系統的使用
android:protectionLevel="normal",不須要動態申請 android:protectionLevel="dangerous",須要動態申請
參考文章: