前言
在6.0之前的版本上安装应用,必须要接受应用申请的所有权限,否则就无法安装使用。但实际中有些应用用户只需要它们的部分权限,只有特殊情况下才会使用一些关于用户隐私的权限,比如有些评论用户只喜欢添加文本评论不喜欢晒图,这时就没有必要一开始就赋予应用查看图片库的功能。Android6.0开始支持动态权限申请功能,它把所有的权限分成了两大类,普通权限和危险权限,普通权限还像以前以前一样写到AndroidManifest文件里就可以,危险权限需要用户手动确认,用户也可以到应用设置里随时取消赋予的权限。现在就来学习一下动态权限管理使用的接口。
权限说明
普通权限的申请方式和以前的相同就不再介绍了,这里主要介绍危险权限,Android对危险权限做了详细的分组,一旦用户启用了某个组的某个权限,那么应用就自动获取到了这个权限组里的权限。权限组的分配如下表所示:
权限组 | 说明 | 权限 |
---|---|---|
android.permission-group.CONTACTS | 联系人权限 | android.permission.WRITE_CONTACTS android.permission.GET_ACCOUNTS android.permission.READ_CONTACTS |
android.permission-group.PHONE | 电话权限 | android.permission.READ_CALL_LOG android.permission.READ_PHONE_STATE android.permission.CALL_PHONE android.permission.WRITE_CALL_LOG android.permission.USE_SIP android.permission.PROCESS_OUTGOING_CALLS com.android.voicemail.permission.ADD_VOICEMAIL |
android.permission-group.CALENDAR | 日历权限 | android.permission.READ_CALENDAR android.permission.WRITE_CALENDAR |
android.permission-group.CAMERA | 相机权限 | android.permission.CAMERA |
android.permission-group.SENSORS | 传感器权限 | android.permission.BODY_SENSORS |
android.permission-group.LOCATION | 定位权限 | android.permission.ACCESS_FINE_LOCATION android.permission.ACCESS_COARSE_LOCATION |
android.permission-group.STORAGE | 外部存储权限 | android.permission.READ_EXTERNAL_STORAGE android.permission.WRITE_EXTERNAL_STORAGE |
android.permission-group.MICROPHONE | 麦克风音频权限 | android.permission.RECORD_AUDIO |
android.permission-group.SMS | 短消息权限 | android.permission.READ_SMS android.permission.RECEIVE_WAP_PUSH android.permission.RECEIVE_MMS android.permission.RECEIVE_SMS android.permission.SEND_SMS android.permission.READ_CELL_BROADCASTS |
除了上面的危险权限外还有两种特殊的权限SYSTEM_ALERT_WINDOW 和 WRITE_SETTINGS,它们属于特殊权限不在普通权限和危险权限里面,申请它们除了要在清单文件里写入权限申请还要到设置界面里人工授权。
危险权限申请
首先简单的实现拍照然后将照片保存到本地,再展示到界面上。首先需要在AndroidManifest文件中声明需要android.permission.WRITE_EXTERNAL_STORAGE权限,在6.0之前可以直接调用ACTION_CAPTURE_IMAGE使用Android提供的拍照应用拍照,通过onActivityResult里的intent.getExtra(“data”)获取到Bitmap对象,然后保存到外部sdcard文件中。
private void startTakePhoto() {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, REQUEST_TAKE_PHOTO);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_TAKE_PHOTO && resultCode == Activity.RESULT_OK) {
Bitmap bitmap = (Bitmap) data.getExtras().get("data");
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "photo.jpg");
FileOutputStream fos = null;
try {
if (!file.exists()) file.createNewFile();
fos = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fos);
} catch (Exception e) {
e.printStackTrace();
} finally {
IOUtils.close(fos);
}
Bitmap bmp = BitmapFactory.decodeFile(file.getAbsolutePath());
imageView.setImageBitmap(bmp);
}
}
但是在6.0以上版本直接调用保存会报下面的错误:
java.io.FileNotFoundException: /storage/emulated/0/Pictures/photo.jpg: open failed: EACCES (Permission denied)
at libcore.io.IoBridge.open(IoBridge.java:452)
at java.io.FileOutputStream.<init>(FileOutputStream.java:87)
at java.io.FileOutputStream.<init>(FileOutputStream.java:72)
at com.example.permisswion.MainActivity.onActivityResult(MainActivity.java:83)
这个错误提示的很明确找不到文件,没有权限访问对应文件。现在开始使用系统提供的接口申请外部存储写入权限,整体过程如下:首先检查当前系统版本是否是6.0以上,如果是通过ContextCompat.checkSelfPermission判断应用是否已经有了外部写入权限,如果有就直接调用拍照功能,没有就通过ActivityCompat.requestPermissions申请权限;如果不是6.0以上版本直接调用照相功能即可。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
startTakePhoto();
} else {
ActivityCompat.requestPermissions(this,
new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_WRITE_STORAGE);
}
} else {
startTakePhoto();
}
调用ActivityCompat.requestPermissions之后会弹出一个对话框提示用户是否授权,点击确定或者取消都会进入Activity.onRequestPermissionsResult回调中去,在回调里会有一个permissionResult数组对象,数组索引和申请的permission一一对应,这里只申请了Manifest.permission.WRITE_EXTERNAL_STORAGE权限,所以permissionResult也只有一个结果。通过对比权限申请结果是PackageManager.PERMISSION_GRANTED表明获得申请权限,PackageManager.PERMISSION_DENIED表明用户没有授予权限。
if (requestCode == REQUEST_WRITE_STORAGE && grantResults != null && grantResults.length > 0) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startTakePhoto();
} else {
Toast.makeText(getApplicationContext(), R.string.has_no_permission, Toast.LENGTH_SHORT).show();
}
}
WRITE_SETTINGS权限申请
很多电子书应用都会分白天和黑夜模式,如果黑夜模式特别亮用户就会觉得很刺眼,可以通过调整屏幕的亮度来解决刺眼的问题。这种调整屏幕亮度的需求就属于写入系统设置的范畴,在6.0以上的版本如果直接设置系统亮度会报没有权限的异常。接下来通过代码来实现6.0以上版本的权限动态申请。
首先在AndroidManifest中声明android.permission.WRITE_SETTINGS权限,在XML中定义一个SeekBar控件并且为它注册SeekBar.OnSeekBarChangeListener回调,接下来在onProgressChanged回调方法里首先判断是否是6.0以上版本,如果是判断能否写入Settings,不能则需要到Settings设置界面设置授权当前应用写入Settings。授权成功后设置屏幕亮度模式为人工设置模式,同时更新当前屏幕的亮度值,拖动SeekBar的调整句柄就会看到屏幕亮度随之发生了变化。
<SeekBar
android:id="@+id/seekbar"
android:layout_marginTop="50dp"
android:max="255"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.System.canWrite(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
intent.setData(Uri.parse("package:" + getPackageName()));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
} else {
changeScreenBrightness(progress);
}
} else {
changeScreenBrightness(progress);
}
}
private void changeScreenBrightness(int progress) {
Settings.System.putInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
Settings.System.putInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS, progress);
}
ALERT_SYSTEM_WINDOW权限申请
有一些应用比如像素尺子,它们需要把自己的界面悬浮在其他的应用界面之上,为了实现悬浮效果,就需要向系统申请 ALERT_SYSTEM_WINDOW权限。对于6.0以上版本如果没有动态申请这个权限,系统也会报异常。现在通过代码来说明如何申请这个权限,首先在AndroidManifest中添加android.permission.SYSTEM_ALERT_WINDOW权限声明。然后在通过Settings.canDrawOverlays判断是否有SYSTEM_ALERT_WINDOW权限,如果没有需要到Settings的设置界面请求授予权限,最后在onActivityResult回调中测试用户是否授权成功。
public void requestPermission() {
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, OVERLAY_PERMISSION_REQUEST);
} else {
Intent intent = new Intent(this, RulerService.class);
startService(intent);
}
}
}
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == OVERLAY_PERMISSION_REQUEST) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
Toast.makeText(this, "权限授予失败,无法开启悬浮窗", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "权限授予成功!", Toast.LENGTH_SHORT).show();
}
}
}
}