Android6.0引入了全新的權(quán)限管理方式,也就是運行時權(quán)限,至于什么是運行時權(quán)限,我們先看一下6.0以前的權(quán)限處理。
6.0以前的權(quán)限
6.0以前的系統(tǒng),我們在安裝一個應(yīng)用的時候會默認賦予所有權(quán)限。

安裝的時候會提示應(yīng)用需要獲取的所有權(quán)限,選擇安裝則會全部獲取,如果要拒絕獲取權(quán)限,只能放棄安裝應(yīng)用。用戶無法選擇獲取或者放棄某些權(quán)限。
6.0的運行時權(quán)限
什么是運行時權(quán)限?舉個栗子,以某個需要拍照的應(yīng)用為例,當(dāng)運行時權(quán)限生效時,其Camera權(quán)限不是在安裝后賦予,而是在應(yīng)用運行的時候進行請求權(quán)限(比如當(dāng)用戶按下”相機拍照“按鈕后)看到的效果則是這樣的,提示用戶需要權(quán)限,用戶選擇允許,才能獲取到該權(quán)限。

一個問題:我們必須要支持運行時權(quán)限嗎?
如果我們不想啟用運行時權(quán)限其實很簡單,我們只要,把targetSdkVersion設(shè)置為設(shè)置低于23就可以了,系統(tǒng)會認為我們的應(yīng)用還不支持新特性,會按照棉花糖以前的版本進行處理。這樣的處理不會有任何的問題,但有一點,棉花糖對每一個應(yīng)用都有一個權(quán)限管理界面,是這樣

如果用戶手動關(guān)閉了我們應(yīng)用的某些權(quán)限,問題就出現(xiàn)了,運行應(yīng)用時可能會出現(xiàn)崩潰。下面這個例子
TelephonyManager telephonyManager = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
String deviceId = telephonyManager.getDeviceId();
if (deviceId.equals(mLastDeviceId)) {//This may cause NPE
//do something
}
如果用戶撤消了獲取DeviceId的權(quán)限,那么再次運行時,deviceId就是null,如果程序后續(xù)處理不當(dāng),就會出現(xiàn)崩潰。所以說該來的還是要來的,我們需要處理好運行時權(quán)限問題。
權(quán)限分類
android系統(tǒng)的權(quán)限很多但不是所有的權(quán)限都是敏感權(quán)限,棉花糖將android系統(tǒng)權(quán)限分為四類。
1.正常權(quán)限(Normal Protection)
2.危險權(quán)限(Dangerous)
3.特殊權(quán)限(Particular)
4.其他權(quán)限(幾乎使用不到)
1.正常權(quán)限
這一類權(quán)限是對用戶隱私影響較小,沒有什么安全問題,這類權(quán)限會像6.0以前的系統(tǒng)一樣,安裝就獲取到這些權(quán)限,沒有用戶提醒,也不能被取消。下面是正常權(quán)限列表。
ACCESS_LOCATION_EXTRA_COMMANDS
ACCESS_NETWORK_STATE
ACCESS_NOTIFICATION_POLICY
ACCESS_WIFI_STATE
BLUETOOTH
BLUETOOTH_ADMIN
BROADCAST_STICKY
CHANGE_NETWORK_STATE
CHANGE_WIFI_MULTICAST_STATE
CHANGE_WIFI_STATE
DISABLE_KEYGUARD
EXPAND_STATUS_BAR
GET_PACKAGE_SIZE
INTERNET
KILL_BACKGROUND_PROCESSES
MODIFY_AUDIO_SETTINGS
NFC
READ_SYNC_SETTINGS
READ_SYNC_STATS
RECEIVE_BOOT_COMPLETED
REORDER_TASKS
REQUEST_INSTALL_PACKAGES
SET_TIME_ZONE
SET_WALLPAPER
SET_WALLPAPER_HINTS
TRANSMIT_IR
USE_FINGERPRINT
VIBRATE
WAKE_LOCK
WRITE_SYNC_SETTINGS
SET_ALARM
INSTALL_SHORTCUT
UNINSTALL_SHORTCUT
對于這些權(quán)限,我們只需要在Manifest中指定,應(yīng)用安裝就會獲取。
2.危險權(quán)限
危險權(quán)限才是運行時權(quán)限的主要處理對象,這些權(quán)限可能會有隱私問題,或者影響其他應(yīng)用的運行,危險權(quán)限可以分為以下幾組:
- CALENDAR
- CAMERA
- CONTACTS
- LOCATION
- MICROPHONE
- PHONE
- SENSORS
- SMS
- STORAGE
對于各組權(quán)限對應(yīng)的具體權(quán)限如下:

關(guān)于權(quán)限我們需要下面幾個API
- int checkSelfPermission(String permission) 用來檢測應(yīng)用是否已經(jīng)具有權(quán)限
- void requestPermissions(String[] permissions, int requestCode) 進行請求單個或多個權(quán)限
- void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
請求Camera的權(quán)限
private static final int REQUEST_PERMISSION_CAMERA_CODE = 1;
@Override
public void onClick(View v) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!(checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED)) {
if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
Toast.makeText(this, "Please grant the permission this time", Toast.LENGTH_LONG).show();
}
requestCameraPermission();
}
}
}
private void requestCameraPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_PERMISSION_CAMERA_CODE);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_PERMISSION_CAMERA_CODE) {
int grantResult = grantResults[0];
boolean granted = grantResult == PackageManager.PERMISSION_GRANTED;
Log.i(LOGTAG, "onRequestPermissionsResult granted=" + granted);
}
}
通常情況下,我們會得到這樣的一個對話框

我們可以在onRequestPermissionsResult中獲取用戶的選擇情況進行相應(yīng)的處理。但如果用戶選擇了否,我們再次申請的時候就會多一個checkbox

如果用戶選擇了不在詢問,然后拒絕,我們的應(yīng)用基本上就獲取不到這個權(quán)限了,shouldShowRequestPermissionRationale這個API可以幫我們判斷接下來的對話框是否包含”不再詢問“選擇框,我們可以這樣使用。這樣如果我們第一次申請權(quán)限失敗后,在申請權(quán)限的時候就會彈出提示Toast,這個使用一定要向用戶說明我們?yōu)槭裁匆暾堖@個權(quán)限,來做什么。
if (!(checkSelfPermission(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED)) {
if (shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) {
Toast.makeText(this, "Please grant the permission this time", Toast.LENGTH_LONG).show();
}
requestReadContactsPermission();
} else {
Log.i(LOGTAG, "onClick granted");
}
對于同時申請多個權(quán)限我們可以
String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_PHONE_STATE};
requestPermissions(permissions, REQUEST_CODE);
效果是這樣,同時申請多個權(quán)限可以避免彈出多個對話框造成不好的視覺影響。

3.特殊權(quán)限
特殊權(quán)限是指特別敏感的權(quán)限,這里主要是指兩個。
SYSTEM_ALERT_WINDOW,設(shè)置懸浮窗
WRITE_SETTINGS 修改系統(tǒng)設(shè)置
關(guān)于上面兩個特殊權(quán)限的授權(quán),做法是使用startActivityForResult啟動授權(quán)界面來完成,下面是請求SYSTEM_ALERT_WINDOW權(quán)限。
private static final int REQUEST_CODE = 1;
private void requestAlertWindowPermission() {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, REQUEST_CODE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Settings.canDrawOverlays(this)) {
Log.i("AlertWindowPermission", "onActivityResult granted");
}
}
}
}
需要注意:
- 使用Action
Settings.ACTION_MANAGE_OVERLAY_PERMISSION啟動隱式Intent - 使用
"package:" + getPackageName()攜帶App的包名信息 - 使用
Settings.canDrawOverlays方法判斷授權(quán)結(jié)果
WRITE_SETTINGS 使用的則是 Action Settings.ACTION_MANAGE_WRITE_SETTINGS,使用Settings.System.canWrite方法檢測授權(quán)結(jié)果