运行时动态权限简介
在targetSdkVersion小于23的应用中默认授予AndroidManifest中声明的所有权限,而无需手动授予。当targetSdkVersion在23或以上时,应用默认不会授予“Dangerous”级别的权限,Android默认只要授予该组一个权限即可获得该组的所有权限。
动态权限获取
下面将演示如何通过运行时权限获取来调用系统相机进行拍照和录制视频
首先以targetSdkVersion 23来编译调试应用,要调用相机拍照并存储需要在在AndroidManifest.xml文件里声明以下两个权限:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
至于界面的代码我这里就不贴上来了,可下载源码查看。这个时候(未加入运行时动态权限检测)如果调试应用,点击拍照按钮你会发现应用会崩溃,其实原因很简单,就是应用没有获取以上两个权限。
因此从targetSdkVersion 23开始,对“Dangerous”级别的权限我们要开启运行时动态权限检测,检测是否有相应权限代码如下:
if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED
|| ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
requestPermission();
} else {
openCamera(v);
}
checkSelfPermission函数检测是否具有某个权限,该函数现有两个版本(推荐使用后一个版本)checkSelfPermission(String permission)
和ActivityCompat.checkSelfPermission(Context context, String permission)
。前一个版本是API 23中引入的,只能在API 23及以上使用。后一个版本是support v4包里的,使用时可以省去了版本检测的代码。该函数的返回值只有两个PackageManager.PERMISSION_GRANTED
和PackageManager.PERMISSION_DENIED
,即权限被授予和拒绝。这里if语句判断权限是否都被授予了,如果缺少权限则调用requestPermission()去请求权限,否则就进行下一步操作。
/**
* 请求权限,一次请求多个
*/
private void requestPermission() {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)
|| ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
Snackbar.make(fl, "需要获取相关的权限", Snackbar.LENGTH_INDEFINITE)
.setAction("确定", new View.OnClickListener() {
@Override
public void onClick(View view) {
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE},
REQUEST_PERMISSION);
}
})
.setActionTextColor(getResources().getColor(R.color.colorAccent))
.show();
} else {
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE},
REQUEST_PERMISSION);
}
}
/**
* 请求权限结果的回调
*
* @param requestCode
* @param permissions
* @param grantResults
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_PERMISSION) {
if (grantResults[0] == PackageManager.PERMISSION_DENIED && grantResults[1] == PackageManager.PERMISSION_DENIED) {
Snackbar.make(fl, "未授予任何权限", Snackbar.LENGTH_SHORT)
.show();
} else if (grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_DENIED) {
Snackbar.make(fl, "未授予读写存储空间权限", Snackbar.LENGTH_SHORT)
.show();
} else if (grantResults[0] == PackageManager.PERMISSION_DENIED && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
Snackbar.make(fl, "未授予打开相机权限", Snackbar.LENGTH_SHORT)
.show();
} else if (grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED){
openCamera(view);
}
}
}
requestPermissions是权限请求函数,同checkSelfPermission一样也是两个版本,对此这里不再讨论。该函数有个回调函数onRequestPermissionsResult:这里可以判断权限被授予还是拒绝,从而进行下一步操作。
shouldShowRequestPermissionRationale函数同checkSelfPermission一样也是两个版本,对此这里不再讨论。这里详细说明shouldShowRequestPermissionRationale函数的返回值:
1、第一次调用总是返回false,因此直接调用requestPermissions去请求权限组,如图:
2、当第一次请求权限点击拒绝以后,如果再次请求权限,则返回true,这时候权限对话框会多出一个‘不再询问’的选项。如果勾选了该选项,就只能选择拒绝权限,此时若选择了拒绝只能去应用管理授予权限了或者清除数据再次请求。
3、如果勾选了不再询问选项并拒绝权限,再次请求权限时永远返回false,不会显示权限对话框,所有权限都默认被拒绝了,并且直接回调onRequestPermissionsResult函数。
注:SnackBar是Android Support Library里面新增提供的一个控件,类似Toast但比Toast更好用,想要了解请参见我另一篇博客Android Material design之Snackbars
openCamera函数代码如下,一看就明白,这里不再讨论了。
/**
* 打开相机
*
* @param view
*/
private void openCamera(View view) {
file = new File(Environment.getExternalStorageDirectory() + File.separator + "CameraDemo1");
if (!file.exists()) {
file.mkdirs();
}
vv.setVisibility(View.GONE);
iv.setVisibility(View.GONE);
switch (view.getId()) {
case R.id.bt_picture:
name = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
uri = Uri.fromFile(new File(file.getPath() + File.separator + name + ".jpg"));
// 启动系统相机拍照
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
startActivityForResult(intent, PICTURE);
break;
case R.id.bt_video:
name = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
uri = Uri.fromFile(new File(file.getPath() + File.separator + name + ".mp4"));
// 启动系统相机录像
Intent intent1 = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
intent1.putExtra(MediaStore.EXTRA_OUTPUT, uri);
intent1.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
startActivityForResult(intent1, VIDEO);
break;
}
}