Android在 6.0中摒棄了之前的install time permissions model取而代之的是runtime permissions model,也就是動態(tài)權(quán)限管理。
這種改變讓用戶更加容易的控制自己的隱私,好處不言而喻。但是對于程序員來說,還是有點(diǎn)小負(fù)擔(dān)的,增加了一些學(xué)習(xí)和開發(fā)的成本。

權(quán)限分類
Android 將系統(tǒng)權(quán)限分成了四個保護(hù)等級:
-
normal:普通級別 -
dangerous:危險(xiǎn)級別 -
signature:簽名級別 -
signatureOrSystem:系統(tǒng)簽名級別
而對于開發(fā)而言,關(guān)心的只有 普通權(quán)限 和 危險(xiǎn)權(quán)限 兩類
其他兩級權(quán)限,為高級權(quán)限,應(yīng)用擁有platform級別的認(rèn)證才能申請。
當(dāng)應(yīng)用試圖在沒有權(quán)限的情況下做受限操作,應(yīng)用將被系統(tǒng)殺掉以警示。
所以權(quán)限的控制很重要,一個不留神,程序就會系統(tǒng)干掉,后果很嚴(yán)重~~
普通權(quán)限 (normal permission)
普通權(quán)限 會在App安裝期間被默認(rèn)賦予。這類權(quán)限不需要開發(fā)人員進(jì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
FLASHLIGHT
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
危險(xiǎn)權(quán)限
這些權(quán)限是在開發(fā)6.0程序時(shí),必須要注意的。
這些權(quán)限處理不好,程序可能會直接被系統(tǒng)干掉。
權(quán)限如下:
| 權(quán)限組 | 權(quán)限 |
|---|---|
| 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 |
我們會發(fā)現(xiàn)這些權(quán)限被分成了組。每個組里面包含了一些相近的權(quán)限。
分組的作用:
這些分組實(shí)際上是有一些特殊含義的。
系統(tǒng)在動態(tài)賦予權(quán)利的時(shí)候,是按照組去賦予的。即:
如果允許了某一個權(quán)限,那么同組中的其他權(quán)限也會被直接賦予
對于申請時(shí)彈出的dialog上面的文本說明也是對整個權(quán)限組的說明,而不是對單個權(quán)限的說明。
注意:
不要對權(quán)限組過多的依賴,盡可能對每個危險(xiǎn)權(quán)限都進(jìn)行正常流程的申請,因?yàn)樵诤笃诘陌姹局羞@個權(quán)限組可能會產(chǎn)生變化。
代碼的處理
-
權(quán)限檢查
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED
) {
Toast.makeText(this, "權(quán)限拒絕了", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "權(quán)限同意了", Toast.LENGTH_SHORT).show();
}
-
權(quán)限申請
權(quán)限的申請方法??梢砸淮紊暾埗鄠€方法。
// 類似 startActivityForResult()中的REQUEST_CODE
int REQUEST_CODE = 99;
// 權(quán)限列表,將要申請的權(quán)限以數(shù)組的形式提交。
// 系統(tǒng)會依次進(jìn)行彈窗提示。
// 注意:如果AndroidManifest.xml中沒有進(jìn)行權(quán)限聲明,這里配置了也是無效的,不會有彈窗提示。
String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
ActivityCompat.requestPermissions(this,
permissions,
REQUEST_CODE);
-
權(quán)限回調(diào)
這個方法是Activity的一個回調(diào)方法。每一次調(diào)用 requestPermissions() 方法,都會回調(diào)一次這個方法。
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case REQUEST_CODE: {
// grantResults是一個數(shù)組,和申請的數(shù)組一一對應(yīng)。
// 如果請求被取消,則結(jié)果數(shù)組為空。
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 權(quán)限同意了,做相應(yīng)處理
} else {
// 權(quán)限被拒絕了
}
return;
}
}
}
-
權(quán)限再次申請
當(dāng)用戶拒絕了某個權(quán)限時(shí),我們可以再次去申請這個權(quán)限。但是這個時(shí)候,你應(yīng)該告訴用戶,你為什么要申請這個權(quán)限。
系統(tǒng)有一個API可以判斷一個權(quán)限是否被用戶拒絕了。
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// 用戶拒絕過這個權(quán)限了,應(yīng)該提示用戶,為什么需要這個權(quán)限。
}
注意:
這個API有兩種情況會返回false:
- 用戶申請的權(quán)限沒有被用戶拒絕。
- 用戶在系統(tǒng)權(quán)限彈窗中,選中了 不再提醒 選項(xiàng)。
-
整合代碼
看看官方給的代碼示例
// 首先檢查權(quán)限
if (ContextCompat.checkSelfPermission(thisActivity,Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// 檢查用戶是否拒絕了這個權(quán)限
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) {
// 給出一個提示,告訴用戶為什么需要這個權(quán)限
} else {
// 用戶沒有拒絕,直接申請權(quán)限
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
//用戶授權(quán)的結(jié)果會回調(diào)到FragmentActivity的onRequestPermissionsResult
}
}else {
//已經(jīng)擁有授權(quán)
//TODO: 正常業(yè)務(wù)邏輯
}
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
readContacts();
} else {
// 權(quán)限拒絕了。
}
return;
}
}
}
注意:
這里有一個問題,就是當(dāng)用戶在彈窗中用戶選擇了 不再提示 。
前面也說了,選中了 不再提示 會導(dǎo)致shouldShowRequestPermissionRationale返回false。
同時(shí)系統(tǒng)的權(quán)限彈窗也不會再出現(xiàn)了。就算調(diào)用代碼
ActivityCompat.requestPermissions()也不會有彈窗。
這里我們就需要自己做一個判斷,如果用戶選擇了 不再提示 (沒有API,自己加標(biāo)記判斷吧),我們可以給個提示,并且跳轉(zhuǎn)到設(shè)置界面,讓用戶手動設(shè)置,跳轉(zhuǎn)設(shè)置界面代碼如下:
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
intent.setData(uri);
activity.startActivityForResult(intent, PERMISSIONS_REQUEST_READ_CONTACTS);
-
示例代碼
判斷用戶是否選擇了 不再提示 代碼示例:
SharedPreferences sp; // 用來保存配置
private void setFlag(boolean f) {
SharedPreferences.Editor edit = sp.edit();
edit.putBoolean("flag", f);
edit.commit();
}
private boolean getFlag() {
return sp.getBoolean("flag", false);
}
private boolean dontShowAgain() {
return getFlag() && !ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
// 檢查權(quán)限
private void permission() {
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED
) {
// 權(quán)限拒絕了
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// 用戶拒絕過權(quán)限了,這里給用戶個提示。
} else {
if (dontShowAgain()) {
// 用戶選擇了 “不再提示”??梢蕴D(zhuǎn)到設(shè)置界面。
} else {
// 用戶沒有拒絕權(quán)限,這時(shí)正常申請權(quán)限。
String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
ActivityCompat.requestPermissions(this,
permissions,
REQUEST_CODE);
}
setFlag(true);
}
} else {
// 權(quán)限同意了
}
}
網(wǎng)上有很多關(guān)于權(quán)限的封裝庫。在真正使用的時(shí)候可以在網(wǎng)上找一個封裝庫來使用,會方便很多。