在最新的Android6.0Marshmallow系统中,Google加入了在程序运行时检测权限的机制,下面这些权限是需要在运行时进行判断的:
如果设置项目的targetSdkVersion>=23,当程序运行在Android6.0以上的手机上时,默认是不授予权限的,可以在应用详细信息的权限选项中看到,如图:
如果targetSdkVersion设置在6.0以下的程序安装在了6.0上,则会默认打开这些权限,可以用此办法来作为应急的解决办法。
下面就来看看如何针对Android6.0的这一特性在运行时申请权限。
1、在Activity中请求权限
ContextCompat类
在ContextCompat类里新增了静态方法checkSelfPermission(@NonNull Context context,@NonNull Permission permission);来检查当前是否已经拥有了相应的权限。该方法会返回一个int值,对应PackageManager.PERMISSION_GRANTED或者PackageManager.PERMISSION_DENIED两个常量,通常可以用下面的写法:
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { // Camera permission has not been granted. requestCameraPermission(); } else { // Camera permissions is already available, show the camera preview. Log.i(TAG, "CAMERA permission has already been granted. Displaying camera preview."); showCameraPreview(); }上面的代码是Google RuntimePermissions Demo中的一段内容,代码很简单,如果没有权限就调用requestCameraPermission()方法去请求权限,如果已经有了权限就调用showCameraPreview()来调用相机。其中ActivityCompat类继承了ContextCompat类,因此也有了checkSelfPermission()方法。另外在ActivityCompat类中还有另外两个静态方法:requestPermissions(final @NonNull Activity activity,final @NonNull String[] permissions, final int requestCode);和shouldShowRequestPermissionRationale(@NonNull Activity activity,@NonNull String permission);下面来看一下requestCameraPermission()是如何实现的:
/** * Requests the Camera permission. * If the permission has been denied previously, a SnackBar will prompt the user to grant the * permission, otherwise it is requested directly. */ private void requestCameraPermission() { Log.i(TAG, "CAMERA permission has NOT been granted. Requesting permission."); // BEGIN_INCLUDE(camera_permission_request) if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) { // Provide an additional rationale to the user if the permission was not granted // and the user would benefit from additional context for the use of the permission. // For example if the user has previously denied the permission. Log.i(TAG, "Displaying camera permission rationale to provide additional context."); Snackbar.make(mLayout, R.string.permission_camera_rationale, Snackbar.LENGTH_INDEFINITE) .setAction(R.string.ok, new View.OnClickListener() { @Override public void onClick(View view) { ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA); } }) .show(); } else { // Camera permission has not been granted yet. Request it directly. ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA); } // END_INCLUDE(camera_permission_request) }
在requestCameraPermission中又进行了一次判断,那这次判断又是为什么呢?其实这次判断是为了给用户一个更好的提示,当前Activity为第一次请求权限时,shouldShowRequestPermissionRationale()方法会返回false,这时候调用requestPermissions方法会默认显示系统的权限申请窗口,如下图:
但是这个界面并没有任何提示,用户可能会选择拒绝或者允许,如果用户在这时选择了拒绝,当他再次执行同样的操作申请权限时,显然不能重复的显示这个界面而不给用户任何提示,我们知道这样的用户体验会很不好,因此就可以通过调用shouldShowRequestPermissionRationale()方法来判断是否用户之前拒绝过权限申请,我们可以在这个方法返回true的时候,给用户一点提示,说明程序为什么要申请这个权限,也就是上面代码中SnackBar.make...中的提示信息,下面是拒绝权限后再次点击的效果:
从上面代码我们知道点击OK会再次进行权限申请,这样的逻辑无疑会提高程序的用户体验。
调用requestPermissions方法后,结果会回调到Activity的方法:public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,@NonNull int[] grantResults);中,其中requestCode就是调用requestPermissions方法传入的requestCode。grantResults里包含了请求的结果。根据grantResults就可以判断用户是否授予了权限:
/** * Callback received when a permissions request has been completed. */ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == REQUEST_CAMERA) { // BEGIN_INCLUDE(permission_result) // Received permission result for camera permission. Log.i(TAG, "Received response for Camera permission request."); // Check if the only required permission has been granted if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // Camera permission has been granted, preview can be displayed Log.i(TAG, "CAMERA permission has now been granted. Showing preview."); Snackbar.make(mLayout, R.string.permision_available_camera, Snackbar.LENGTH_SHORT).show(); } else { Log.i(TAG, "CAMERA permission was NOT granted."); Snackbar.make(mLayout, R.string.permissions_not_granted, Snackbar.LENGTH_SHORT).show(); } // END_INCLUDE(permission_result) } else if (requestCode == REQUEST_CONTACTS) { Log.i(TAG, "Received response for contact permissions request."); // We have requested multiple permissions for contacts, so all of them need to be // checked. if (PermissionUtil.verifyPermissions(grantResults)) { // All required permissions have been granted, display contacts fragment. Snackbar.make(mLayout, R.string.permision_available_contacts, Snackbar.LENGTH_SHORT) .show(); } else { Log.i(TAG, "Contacts permissions were NOT granted."); Snackbar.make(mLayout, R.string.permissions_not_granted, Snackbar.LENGTH_SHORT) .show(); } } else { super.onRequestPermissionsResult(requestCode, permissions, grantResults); } }
除了使用ActivityCompat类中的静态方法外,Activity中也封装了requestPermissions(),checkSelfPermission(),shouldShowRequestPermissionRationale()这几个方法,用法类似。
2、在Fragment中请求权限
在Fragment中请求权限时,不要调用Activity的requestPerminssions方法,因为结果会回调到Activity的onRequestPermissionsResult里,不利于在Fragment中进行处理,而是需要调用Fragment中相同的方法(参见:https://www.aswifter.com/2015/11/04/android-6-permission/ Fragment中也有requestPermissions和shouldShowRequestPermissionRationale这两个方法,checkSelfPermissions可以使用Activity中的)。
3.相关的开源项目(参见:https://www.aswifter.com/2015/11/04/android-6-permission/)