Android 6.0之后需要动态申请权限
Android6.0权限介绍
Google在Android6.0系统之后对权限进行了分类:
正常(Normal Protection)权限:
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
危险(Dangerous)权限
CALENDAR CAMERA CONTACTS LOCATION MICROPHONE PHONE SENSORS SMS STORAGE
权限分组及其具体权限如下:
特殊(Particular)权限
SYSTEM_ALERT_WINDOW,设置悬浮窗 WRITE_SETTINGS 修改系统设置
其他权限(一般很少用到)
Android6.0在之前AndroidManifest.xml声明权限的基础上对危险权限和特殊权限新增了运行时权限,需要动态获取权限。
运行时权限处理
特殊权限
特殊权限的做法是使用startResultActivity启动授权界面来完成。
请求SYSTEM_ALERT_WINDOW权限
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 (Settings.canDrawOverlays(this)) {
Log.i(LOGTAG, "onActivityResult granted");
}
}
}
上述代码需要注意的是
使用Action Settings.ACTION_MANAGE_OVERLAY_PERMISSION启动隐式Intent
使用”package:” + getPackageName()携带App的包名信息
使用Settings.canDrawOverlays方法判断授权结果
请求WRITE_SETTINGS
private static final int REQUEST_CODE_WRITE_SETTINGS = 2; private void requestWriteSettings() { Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS); intent.setData(Uri.parse("package:" + getPackageName())); startActivityForResult(intent, REQUEST_CODE_WRITE_SETTINGS ); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CODE_WRITE_SETTINGS) { if (Settings.System.canWrite(this)) { Log.i(LOGTAG, "onActivityResult write settings granted" ); } } }
上述代码需要注意的是
使用Action Settings.ACTION_MANAGE_WRITE_SETTINGS启动隐式Intent
使用”package:” + getPackageName()携带App的包名信息
使用Settings.canDrawOverlays方法判断授权结果
危险权限
我们需要使用以下API:
int checkSelfPermission(String permission) 用来检测应用是否已经具有权限
void requestPermissions(String[] permissions, int requestCode) 进行请求单个或多个权限
void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) 用户对请求作出响应后的回调
以请求READ_EXTERNAL_STORAGE权限为例:private static final int READ_EXTERNAL_STORAGE_REQUEST_CODE = 1; @Override public void onClick(View v) { if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, READ_EXTERNAL_STORAGE_REQUEST_CODE); } } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == READ_EXTERNAL_STORAGE_REQUEST_CODE){ if (grantResults[0] == PackageManager.PERMISSION_GRANTED){ Intent intent = new Intent(Intent.ACTION_PICK, null); intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, IMAGE_TYPE); startActivityForResult(intent, RESULT_IMAGE); }else{ Snackbar.make(recyclerView,"没有权限臣妾做不到呀",Snackbar.LENGTH_INDEFINITE).setAction("再次获取权限", new View.OnClickListener() { @Override public void onClick(View v) { ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, READ_EXTERNAL_STORAGE_REQUEST_CODE); } }); } } }
请求权限后,系统会弹出请求权限的Dialog
当用户选择允许或拒绝,我们就可以在onRequestPermissionsResult方法中进行响应的处理,如果用户拒绝,应用再次申请权限将会出现不在询问的选项:
当用户勾选了”不再询问“拒绝后,应用的这个权限就无法使用了。
不过,你还有一丝希望,那就是再出现上述的对话框之前做一些说明信息,比如你使用这个权限的目的(一定要坦白)。
shouldShowRequestPermissionRationale这个API可以帮我们判断接下来的对话框是否包含”不再询问“选择框。因此,一个标准的申请权限流程如下:
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");
}
批量申请权限
只需要字符串数组放置多个权限即可:
private static final int REQUEST_CODE = 1;
private void requestMultiplePermissions() {
String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_PHONE_STATE};
requestPermissions(permissions, REQUEST_CODE);
}
权限很多呢
前面权限介绍已经提到了权限分组,举一个例子,如果你的应用授权了读取联系人的权限,那么你的应用也是被赋予了写入联系人的权限。因为读取联系人和写入联系人这两个权限都属于联系人权限分组,所以一旦组内某个权限被允许,该组的其他权限也是被允许的。
Fragment中运行时权限的特殊处理
- 在Fragment中申请权限,不要使用ActivityCompat.requestPermissions, 直接使用Fragment的requestPermissions方法,否则会回调到Activity的onRequestPermissionsResult
- 如果在Fragment中嵌套Fragment,在子Fragment中使用requestPermissions方法,onRequestPermissionsResult不会回调回来,建议使用getParentFragment().requestPermissions方法,
这个方法会回调到父Fragment中的onRequestPermissionsResult,加入以下代码可以把回调透传到子Fragment@Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); List<Fragment> fragments = getChildFragmentManager().getFragments(); if (fragments != null) { for (Fragment fragment : fragments) { if (fragment != null) { fragment.onRequestPermissionsResult(requestCode,permissions,grantResults); } } } }
注意
即使支持了运行时权限,也要在Manifest声明,因为市场应用会根据这个信息和硬件设备进行匹配,决定你的应用是否在该设备上显示。
相关开源项目
- PermissionsDispatcher
使用标注的方式,动态生成类处理运行时权限,目前还不支持嵌套Fragment。 - RxPermissions
基于RxJava的运行时权限检测框架 - Grant
简化运行时权限的处理,比较灵活 - android-RuntimePermissions
Google官方的例子
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!