最近做直播相关的项目时涉及到Camera2使用摄像头的问题,中间有许多坑,其中涉及到动态申请使用Camera权限的问题,经过我的一番查资料和API最后终于有了些眉目,下面将的解决办法介绍一下,加深理解,也有助于以后遗忘时查看。
往常应用开发时我习惯了在AndroidManifest.xml文件中声明使用权限,就像这样
<uses-permission android:name="android.permission.CAMERA" />
打开照相机的代码如下
CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
mCameraIdList = cameraManager.getCameraIdList();
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
cameraManager.openCamera(mCameraIdList[0], mStateCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
这次项目的targetSdkVersion是25,开始是我依然按照往常的思路在AndroidManifest.xml中声明权限然而在运行程序时却直接崩溃了,查看logcat发现报错Lacking privileges to access camera service,可以初步确定就是camera的权限问题,根据编译器生成的TODO注释代码也可发现检查权限if判断中只是进行了判断并没有进行相应的处理。
查找了一些资料后发现原来API23以后一些权限需要在运行时授权,其中包括Camera。
好,既然已经知道了错误原因那接下来就是一个字–“干”。
首先,我们根据TODO注释我们知道可以通过ActivityCompat#requestPermissions这个方法来请求使用权限;
接下来,我们从API文档中查看一下用法
requestPermissions(Activity activity, String[] permissions, int requestCode)
其中
activity 是目标activity
permissions 请求的权限。我必须非空且不为空
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);
//REQUEST_CAMERA_PERMISSION是自己在程序中定义的静态变量
运行一下试试吧,我们回到设备上,果然有效果,弹出了如下图所示对话框:
果然上面的语句还是起作用了,这个对话框就是申请权限的对话框。
我们点击允许,进入到app中,但是发现照相机并没有打开,退出程序再次打开app发现camera正常打开了,这是为什么?
进过一番查找最终又把目光放到了检查权限的判断上了,我们把它提取出来
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);
return;
}
我们发现如果if条件成立了,即还没有动态申请Camera的使用权限,当我们请求完使用权限后就直接返回了,所以后面的打开照相机操作根本没有执行,所以我们第一次打开程序的点击允许以后发现camera并没有打开,而第二次打开应用程序的时候if判断不成立了所以没有执行return,所以照相机可以正常打开。
有人可能会说,那干嘛还要加一个return,干脆把return去掉,申请完权限以后接着执行openCamera()不就解决了吗?
我们来试验一下,卸载应用注释掉return这一行,再重新安装,会发现第一次打开又报错了,如下图:
查看logcat,开始的那个错误怎么又回来了,难道申请权限的代码没有执行???
我们点击X关闭应用以后,发现后面有申请权限的对话框,所以说不是申请权限的代码没有执行,是执行了以后又继续向下执行了,因为用户还没有来得及给程序赋予使用照相机的权限程序就已经运行到了openCamera()函数,所以程序就崩溃了,从对话框的叠放次序上也可以判断。
所以这样看来return语句是不能省的,那难道我们就只能赋予权限以后再打开一次应用这个办法吗?
当然不是,我们只要想个办法在申请完权限之后再调用打开照相机的函数不就好了。
我使用的办法是将检查权限的判断和打开照相机的方法封装成一个函数,分别在public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) 函数和protected void onResume()函数中调用一次,这样就解决了打开两次应用才能使用camera的问题。
上面方法是只是简单的解决了动态申请权限的问题,但是不够完善,我们在上面从API文档中查看ActivityCompat#requestPermissions方法时,应该会注意到,动态权限的申请其实不止简单的涉及到这一个方法,还涉及到了三个方法:
int checkSelfPermission (Context context, String permission)
void onRequestPermissionsResult (int requestCode, String[]
permissions, int[] grantResults)
boolean shouldShowRequestPermissionRationale (Activity activity,
String permission
第一个函数上面已经用到,就是确定您是否被授予特定权限;
第二个函数顾名思义就是回调请求权限的结果,并且对于requestPermissions上的每个调用都调用此方法,三个参数分别表示
不管申请权限是成功还是失败,我们都可以在这个函数中做相应的处理;
例如,对于申请camera权限来说:
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == REQUEST_CAMERA_PERMISSION) {//申请camera权限后调用
if (grantResults.length != 1 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
//没有获得权限,弹出自定义对话框告诉用户为什么申请权限
new ConfirmationDialog().showDialog().show();//弹出自定义对话框
}else{
//获得权限
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
第三个函数从名字上来看是显示请求权限的理由,那么这又是什么意思呢?
其实这个函数的作用就是可以通过UI界面向用户解释一下你为什么要申请这个权限,例如:
if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
//返回值为false,弹出自定义对话框告诉用户为什么申请权限
new ConfirmationDialog().showDialog().show();//弹出自定义对话框
} else {
//返回值为true,动态申请权限
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);
}
关于shouldShowRequestPermissionRationale()这个函数,这里我们要多说几句,这个函数在什么时候返回值是true什么时候返回值为false呢?
首先我们需要先了解一下申请权限对话框,对话框有两种模式分别如下:
第一种(下面我叫它对话框1):
启动应用时第一次弹出的权限申请对话框
第二种(下面我叫它对话框2):
如果弹出对话框1时点击允许是看不到对话框2的,但是如果点击的拒绝后面还会继续申请权限时就会弹出如下对话框
对话框1,2的主要区别在于有没有”不再询问”的复选框.
了解这两个对话框以后下面我们总结一下shouldShowRequestPermissionRationale()这个函数的返回值问题:
ture | false |
---|---|
对话框1用户拒绝了 | 应用安装后第一次访问 |
对话框2拒绝,并没有选择“不再提醒”的选项 | 对话框2,用户拒绝了,并选择了“不再提醒”的选项时 |
如果用户允许的请求权限 | |
设备的系统设置中禁止当前应用获取这个权限的授权 |
可以根据上表灵活的使用,以达到自己想要的效果.
下面列出的这些权限是API23以后需要运行时授权的:
上图来自官方文档,这张图中提及的权限不仅需要在AndroidManifest中进行配置,在SDK23以上开发的时候还需要在运行时让用户进行授权。