在Android6.0之前,應(yīng)用安裝時(shí)系統(tǒng)會(huì)列出AndroidManifest清單上申請(qǐng)的所有權(quán)限,用戶必須全部接受才能繼續(xù)安裝,并且這些權(quán)限授權(quán)之后無(wú)法撤銷。這對(duì)于開(kāi)發(fā)者來(lái)說(shuō)是比較方便的,需要什么權(quán)限只需要在AndroidManifest申請(qǐng)即可,不需要考慮權(quán)限被拒絕等各種場(chǎng)景。但是對(duì)于用戶來(lái)說(shuō)卻沒(méi)有辦法自主選擇屏蔽他不想授予的權(quán)限,也容易給惡意程序利用。
Android6.0之后,權(quán)限的申請(qǐng)由安裝時(shí)變成了運(yùn)行時(shí)。開(kāi)發(fā)者仍然需要在AndroidManifest里面列出所需要的所有權(quán)限,但用戶安裝時(shí)不需要對(duì)這些權(quán)限進(jìn)行授權(quán),而是在運(yùn)行時(shí)需要用到某個(gè)權(quán)限時(shí)才詢問(wèn)用戶是否授權(quán),用戶可以選擇接受或者拒絕。另外即使用戶接受了,也可以在權(quán)限管理中進(jìn)行撤銷。如果直接使用用戶沒(méi)有授權(quán)的權(quán)限會(huì)導(dǎo)致crash,因此,開(kāi)發(fā)時(shí)需要考慮這些場(chǎng)景,并作出處理。
對(duì)于一些老的應(yīng)用來(lái)說(shuō),也不用太擔(dān)心,如果app的targetSdkVersion低于23,將繼續(xù)使用舊有規(guī)則。看到這有人可能覺(jué)得干脆將所有的targetSdkVersion設(shè)置為23以下不就好了,也不用那么麻煩考慮權(quán)限的問(wèn)題。但是要注意,在6.0的系統(tǒng)上,即使安裝時(shí)取得了所有的權(quán)限,用戶仍然可以之后在權(quán)限管理中撤銷授權(quán)。因此,我們還是需要與時(shí)俱進(jìn),將targetSdkVersion升級(jí)到23并好好處理權(quán)限問(wèn)題。
Android的權(quán)限總的來(lái)說(shuō)分為三種,分別是normal類型、dangerous類型和special類型:
1、normal類型的權(quán)限不會(huì)威脅到用戶的隱私,可以直接在AndroidManifest里面注冊(cè),在安裝時(shí)就被授權(quán),不需要每次使用時(shí)都檢查權(quán)限,并且用戶不能取消。主要包括:
android.permission.ACCESS_LOCATION_EXTRA_COMMANDS
android.permission.ACCESS_NETWORK_STATE
android.permission.ACCESS_NOTIFICATION_POLICY
android.permission.ACCESS_WIFI_STATE
android.permission.ACCESS_WIMAX_STATE
android.permission.BLUETOOTH
android.permission.BLUETOOTH_ADMIN
android.permission.BROADCAST_STICKY
android.permission.CHANGE_NETWORK_STATE
android.permission.CHANGE_WIFI_MULTICAST_STATE
android.permission.CHANGE_WIFI_STATE
android.permission.CHANGE_WIMAX_STATE
android.permission.DISABLE_KEYGUARD
android.permission.EXPAND_STATUS_BAR
android.permission.FLASHLIGHT
android.permission.GET_ACCOUNTS
android.permission.GET_PACKAGE_SIZE
android.permission.INTERNET
android.permission.KILL_BACKGROUND_PROCESSES
android.permission.MODIFY_AUDIO_SETTINGS
android.permission.NFC
android.permission.READ_SYNC_SETTINGS
android.permission.READ_SYNC_STATS
android.permission.RECEIVE_BOOT_COMPLETED
android.permission.REORDER_TASKS
android.permission.REQUEST_INSTALL_PACKAGES
android.permission.SET_TIME_ZONE
android.permission.SET_WALLPAPER
android.permission.SET_WALLPAPER_HINTS
android.permission.SUBSCRIBED_FEEDS_READ
android.permission.TRANSMIT_IR
android.permission.USE_FINGERPRINT
android.permission.VIBRATE
android.permission.WAKE_LOCK
android.permission.WRITE_SYNC_SETTINGS
com.android.alarm.permission.SET_ALARM
com.android.launcher.permission.INSTALL_SHORTCUT
com.android.launcher.permission.UNINSTALL_SHORTCUT
2、dangerous類型的權(quán)限可以直接訪問(wèn)用戶的敏感數(shù)據(jù),不僅需要在AndroidManifest里面注冊(cè),還需要在使用時(shí)請(qǐng)求授權(quán)。主要包括:

可以看到dangerous類型的權(quán)限進(jìn)行了分組,同一組的任何一個(gè)權(quán)限被授權(quán)了,其他權(quán)限也自動(dòng)被授權(quán)。例如,一旦WRITE_CONTACTS被授權(quán)了,app也有READ_CONTACTS和GET_ACCOUNTS權(quán)限了。
dangerous類型的權(quán)限申請(qǐng)主要調(diào)用這幾個(gè)方法:
Context.checkSelfPermission(String permission) 檢查是否被授予了某個(gè)權(quán)限
Activity.requestPermissions(String[] permissions, int requestCode) 申請(qǐng)一組權(quán)限
Activity.onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) 權(quán)限申請(qǐng)結(jié)果回調(diào)
由于這些方法都是在api23引入的,所以需要在使用時(shí)先進(jìn)行版本判斷。下面以相機(jī)為例說(shuō)明怎樣申請(qǐng)權(quán)限:
public void checkCameraPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// 23及以后的版本需要檢測(cè)權(quán)限
int hasCameraPermission = checkSelfPermission(Manifest.permission.CAMERA);
if (hasCameraPermission != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.CAMERA}, PERMISSION_REQUEST_CODE_CAMERA);
} else {
cameraPermissionGranted(true);
}
} else {
// 23之前的版本權(quán)限在安裝時(shí)已經(jīng)獲取
cameraPermissionGranted(true);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE_CAMERA) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
cameraPermissionGranted(true);
} else {
cameraPermissionGranted(false);
}
}
}
另外,v4包中也提供了兼容方法ContextCompat.checkSelfPermission()和ActivityCompat.requestPermissions()可以避免版本判斷,唯一的區(qū)別就是需要帶上額外的參數(shù)Context或Activity,其他都一樣:
private void checkCameraPermission(Activity activity) {
int hasCameraPermission = ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA);
if (hasCameraPermission != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.CAMERA}, PERMISSION_REQUEST_CODE_CAMERA);
} else {
cameraPermissionGranted(true);
}
}
權(quán)限的判斷和申請(qǐng)其實(shí)比較簡(jiǎn)單,對(duì)于開(kāi)發(fā)者來(lái)說(shuō)更重要的其實(shí)是權(quán)限被接受或拒絕后的不同處理,也就是上面的cameraPermissionGranted(boolea granted)方法。比如如果你的應(yīng)用必須使用相機(jī),那么在相機(jī)權(quán)限申請(qǐng)拒絕后可以彈框提示用戶,直到用戶授權(quán)后才能進(jìn)入使用界面。又或者應(yīng)用并非必須使用相機(jī),也可以從相冊(cè)加載圖片,那么當(dāng)用戶拒絕授權(quán)時(shí),只需要禁掉相機(jī)部分的功能即可。
3、special類型的權(quán)限包括WRITE_SETTINGS和SYSTEM_ALERT_WINDOW,Android單獨(dú)制作了一個(gè)activity作為這兩個(gè)權(quán)限的用戶授權(quán)界面,必須通過(guò)指定intent,然后通過(guò)startActivity(intent)的方式來(lái)申請(qǐng)。
special類型的權(quán)限申請(qǐng)主要用到以下幾個(gè)方法:
Settings.System.canWrite(Context context) 檢查是否被授予了WRITE_SETTINGS權(quán)限
Settings.canDrawOverlays(Context context) 檢查是否被授予了SYSTEM_ALERT_WINDOW權(quán)限
startActivityForResult(Intent intent, in requestCode) 打開(kāi)用戶授權(quán)界面
onActivityResult(int requestCode, int resultCode, Intent data) 權(quán)限申請(qǐng)結(jié)果回調(diào)
申請(qǐng)WRITE_SETTINGS權(quán)限的代碼如下:
public void checkWriteSettingsPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.System.canWrite(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS, Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, PERMISSION_REQUEST_CODE_WRITE_SETTINGS);
} else {
writeSettingsPermissionGranted(true);
}
} else {
writeSettingsPermissionGranted(true);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PERMISSION_REQUEST_CODE_WRITE_SETTINGS) {
// 判斷是否有WRITE_SETTINGS權(quán)限
if (Settings.System.canWrite(this)) {
writeSettingsPermissionGranted(true);
}else {
writeSettingsPermissionGranted(false);
}
}
}
申請(qǐng)SYSTEM_ALERT_WINDOW的代碼如下:
public void checkSystemAlertWindowPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, PERMISSION_REQUEST_CODE_SYSTEM_ALERT_WINDOW);
} else {
systemAlertWindowPermissionGranted(true);
}
} else {
systemAlertWindowPermissionGranted(true);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PERMISSION_REQUEST_CODE_SYSTEM_ALERT_WINDOW) {
if (Settings.canDrawOverlays(this)) {
systemAlertWindowPermissionGranted(true);
} else {
systemAlertWindowPermissionGranted(false);
}
}
}