在Android6.0之前,应用安装时系统会列出AndroidManifest清单上申请的所有权限,用户必须全部接受才能继续安装,并且这些权限授权之后无法撤销。这对于开发者来说是比较方便的,需要什么权限只需要在AndroidManifest申请即可,不需要考虑权限被拒绝等各种场景。但是对于用户来说却没有办法自主选择屏蔽他不想授予的权限,也容易给恶意程序利用。
Android6.0之后,权限的申请由安装时变成了运行时。开发者仍然需要在AndroidManifest里面列出所需要的所有权限,但用户安装时不需要对这些权限进行授权,而是在运行时需要用到某个权限时才询问用户是否授权,用户可以选择接受或者拒绝。另外即使用户接受了,也可以在权限管理中进行撤销。如果直接使用用户没有授权的权限会导致crash,因此,开发时需要考虑这些场景,并作出处理。
对于一些老的应用来说,也不用太担心,如果app的targetSdkVersion低于23,将继续使用旧有规则。看到这有人可能觉得干脆将所有的targetSdkVersion设置为23以下不就好了,也不用那么麻烦考虑权限的问题。但是要注意,在6.0的系统上,即使安装时取得了所有的权限,用户仍然可以之后在权限管理中撤销授权。因此,我们还是需要与时俱进,将targetSdkVersion升级到23并好好处理权限问题。
Android的权限总的来说分为三种,分别是normal类型、dangerous类型和special类型:
1、normal类型的权限不会威胁到用户的隐私,可以直接在AndroidManifest里面注册,在安装时就被授权,不需要每次使用时都检查权限,并且用户不能取消。主要包括:
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类型的权限可以直接访问用户的敏感数据,不仅需要在AndroidManifest里面注册,还需要在使用时请求授权。主要包括:
可以看到dangerous类型的权限进行了分组,同一组的任何一个权限被授权了,其他权限也自动被授权。例如,一旦WRITE_CONTACTS被授权了,app也有READ_CONTACTS和GET_ACCOUNTS权限了。
dangerous类型的权限申请主要调用这几个方法:
Context.checkSelfPermission(String permission) 检查是否被授予了某个权限
Activity.requestPermissions(String[] permissions, int requestCode) 申请一组权限
Activity.onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) 权限申请结果回调
由于这些方法都是在api23引入的,所以需要在使用时先进行版本判断。下面以相机为例说明怎样申请权限:
public void checkCameraPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// 23及以后的版本需要检测权限
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之前的版本权限在安装时已经获取
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()可以避免版本判断,唯一的区别就是需要带上额外的参数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);
}
}
权限的判断和申请其实比较简单,对于开发者来说更重要的其实是权限被接受或拒绝后的不同处理,也就是上面的cameraPermissionGranted(boolea granted)方法。比如如果你的应用必须使用相机,那么在相机权限申请拒绝后可以弹框提示用户,直到用户授权后才能进入使用界面。又或者应用并非必须使用相机,也可以从相册加载图片,那么当用户拒绝授权时,只需要禁掉相机部分的功能即可。
3、special类型的权限包括WRITE_SETTINGS和SYSTEM_ALERT_WINDOW,Android单独制作了一个activity作为这两个权限的用户授权界面,必须通过指定intent,然后通过startActivity(intent)的方式来申请。
special类型的权限申请主要用到以下几个方法:
Settings.System.canWrite(Context context) 检查是否被授予了WRITE_SETTINGS权限
Settings.canDrawOverlays(Context context) 检查是否被授予了SYSTEM_ALERT_WINDOW权限
startActivityForResult(Intent intent, in requestCode) 打开用户授权界面
onActivityResult(int requestCode, int resultCode, Intent data) 权限申请结果回调
申请WRITE_SETTINGS权限的代码如下:
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权限
if (Settings.System.canWrite(this)) {
writeSettingsPermissionGranted(true);
}else {
writeSettingsPermissionGranted(false);
}
}
}
申请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);
}
}
}