(6.0及以上)執行時獲取危險許可權(Dangerous Permission)
普通許可權(Normal Permission)與危險許可權(Dangerous Permission)
Android系統中,許可權被明確分成了普通許可權(Normal Permission)與危險許可權(Dangerous Permission)兩種。Android應用的許多功能都需要獲得相應的許可權才能生效(讀取聯絡人資訊、呼叫手機攝像頭等)。在Android 6.0之前,所有的許可權只需要在Manifest檔案中使用<uses-permission>元素宣告即可,普通許可權直接會被系統批准,而危險許可權會在安裝時向用戶請求。如果請求得不到允許,應用的安裝過程將被撤銷。而自Android 6.0始,普通許可權可以使用傳統的方法靜態獲取,而危險許可權必須在應用執行時動態獲取。
區別普通許可權與危險許可權的關鍵因素是看該許可權是否會侵犯到使用者的隱私。像設定系統時區(time zone)這樣的許可權基本不會涉及到使用者的隱私,因此它屬於普通許可權;而讀取聯絡人資訊這樣的許可權明顯會觸及到使用者的私密資料,因此它屬於危險許可權。
危險許可權及許可權組(Permission Group)
為了便於管理,Android系統將功能相似的許可權,如 READ_EXTERNAL_STORAGE 與 WRITE_EXTERNAL_STORAGE等編成了數個許可權組(Permission Group)。同一個組內的所有許可權會被同時授予。具體的情況有以下兩種:
- 如果應用申請一個已在Manifest檔案中宣告的危險許可權,而此時應用還未獲得該危險許可權所屬的許可權組中的任何一個許可權,那麼系統會彈出一個對話方塊,要求使用者同意授予該許可權組內的許可權(不會指明是組內哪一個許可權)。
- 如果應用申請一個已在Manifest檔案中宣告的危險許可權,而此時應用已經獲得了該危險許可權所屬的許可權組中的任意一個許可權,那麼系統會立即批准該請求,而不會和使用者發生任何互動。
危險許可權組列表如下:
(1)CALENDAR:
- READ_CALENDAR
- WRITE_CALENDAR
(2)CAMERA:
- CAMERA
(3)CONTACTS:
- READ_CONTACTS
- WRITE_CONTACTS
- GET_ACCOUNTS
(4)LOCATION
- ACCESS_FINE_LOCATION
- ACCESS_COARSE_LOCATION
(5)MICROPHONE
- RECORD_AUDIO
(6)PHONE
- READ_PHONE_STATE
- CALL_PHONE
- READ_CALL_LOG
- WRITE_CALL_LOG
- ADD_VOICEMAIL
- USE_SIP
- PROCESS_OUTGOING_CALLS
(7)SENSORS
- BODY_SENSORS
(8)SMS
- SEND_SMS
- RECEIVE_SMS
- READ_SMS
- RECEIVE_WAP_PUSH
- RECEIVE_MMS
(9) STORAGE
- READ_EXTERNAL_STORAGE
- WRITE_EXTERNAL_STORAGE
執行時獲取危險許可權
下面介紹在執行時獲取危險許可權的方法。示例程式中使用了android.support.v4庫,它提供了一系列版本相容的方法,可以避免程式碼中出現大量的檢測SDK版本的語句。
在Manifest中宣告許可權
想要在執行時獲取某個危險許可權,首先必須在Manifest檔案中宣告,雖然該許可權無法在安裝完畢後就獲得。
檢查許可權是否已經獲得
如果應用的某些操作需要危險許可權,那麼你必須在每次執行這些操作前檢查許可權是否已經獲得。使用者擁有隨時撤銷危險許可權的授予的能力,即使應用先前已經獲得了該許可權,也不能保證許可權現在仍舊可用。
可以使用ContextCompat.checkSelfPermission()檢查指定許可權是否獲得:
// Assume thisActivity is the current activity
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.WRITE_CALENDAR);
如果應用擁有該許可權,方法會返回PackageManager.PERMISSION_GRANTED,操作可以正常繼續;如果應用尚未獲得該許可權,方法會返回PackageManager.PERMISSION_DENIED,此時就必須要向用戶提出申請。
申請需要的許可權
申請許可權主要分以下三步:
(1)(可選)呼叫shouldShowRequestPermissionRationale (Activity activity, String permission)方法判斷是否需要向用戶說明申請許可權的原因。
(2)呼叫requestPermissions (Activity activity, String[] permissions, int requestCode)申請指定的許可權。activity是需要許可權的activity;permissions是所需要的許可權,值取Manifest.permission內的常量;requestCode是一個自定義的申請碼,用於在(3)中的回撥方法中進行匹配。該方法被呼叫後,使用者會看到一個標準的對話方塊,用於讓使用者判斷是否要同意權限的授予。
(3)覆蓋onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) 方法,執行那些需要許可權的操作。該方法會在使用者在(2)的對話方塊中進行選擇後被呼叫。requestCode用於和(2)中進行匹配,permissions記錄了申請的許可權,grantResults記錄了許可權申請的結果,與permissions陣列匹配,成功獲得的話值會是PackageManager.PERMISSION_GRANTED。該方法是OnRequestPermissionsResultCallback介面的方法, AppCompatActivity與FragmentActivity都實現了該介面。
下面是一個完整的申請READ_CONTACTS許可權的例子(來自Android Developer):
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_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, we can 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.
}
}
@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
}
}