今天实现一个小功能,调用相机权限实现手电筒,顺带学习一下CameraManager
系统服务和两个好用的权限请求框架,主要推荐使用XXPermissions
1.权限设置
在manifest.xml
中引入相机权限
<!--引入相机权限-->
<uses-permissionandroid:name="android.permission.CAMERA"/>
2.xml布局文件
<?xmlversion="1.0" encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ImageView
android:id="@+id/flashBtn"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/off"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</RelativeLayout>
3.前备知识
CameraManager
是系统服务之一,专门用于 检测 和 打开相机,以及 获取相机设备特性
String[] getCameraIdList()
获取当前连接的相机设备列表,这个 id 通常都是从 0 开始并依次递增的。
对于一般的手机而言:
后置摄像头一般为 CameraCharacteristics.LENS_FACING_FRONT
,常量值为 “0”;前置摄像头一般为 CameraCharacteristics.LENS_FACING_BACK
,常量值为 “1”
2. void setTorchMode(String cameraId, boolean enabled)
打开和关闭指定相机设备的闪光灯功能。
4.XXPermissions框架(推荐)
4.1框架亮点
- 首款适配 Android 13 的权限请求框架
- 首款也是唯一一款适配所有 Android 版本的权限请求框架
- 简洁易用:采用链式调用的方式,使用只需一句代码
- 体积感人:功能在同类框架中是最全的,但是框架体积是垫底的
- 适配极端情况:无论在多么极端恶劣的环境下申请权限,框架依然坚挺
- 向下兼容属性:新权限在旧系统可以正常申请,框架会做自动适配,无需调用者适配
- 自动检测错误:如果出现错误框架会主动抛出异常给调用者(仅在 Debug 下判断,把 Bug 扼杀在摇篮中)
4.2集成步骤
- 如果你的项目 Gradle 配置是在
7.0 以下
,需要在build.gradle
文件中加入
allprojects {
repositories {
// JitPack 远程仓库:https://jitpack.io
maven { url'https://jitpack.io' }
}
}
- 如果你的 Gradle 配置是
7.0 及以上
,则需要在settings.gradle
文件中加入
dependencyResolutionManagement {
repositories {
// JitPack 远程仓库:https://jitpack.io
maven { url'https://jitpack.io' }
}
}
- 配置完远程仓库后,在项目 app 模块下的
build.gradle
文件中加入远程依赖
android {
// 支持 JDK 1.8
compileOptions {
targetCompatibilityJavaVersion.VERSION_1_8
sourceCompatibilityJavaVersion.VERSION_1_8
}
}
dependencies {
// 权限请求框架:https://github.com/getActivity/XXPermissions
implementation'com.github.getActivity:XXPermissions:16.0'
}
- 如果项目是基于 AndroidX 包,请在项目
gradle.properties
文件中加入
#表示将第三方库迁移到AndroidX
android.enableJetifier=true
- 如果项目是基于 Support 包则不需要加入此配置
4.3请求权限
XXPermissions.with(this)
// 申请单个权限
.permission(Permission.RECORD_AUDIO)
// 申请多个权限
.permission(Permission.Group.CALENDAR)
// 设置权限请求拦截器(局部设置)
//.interceptor(new PermissionInterceptor())
// 设置不触发错误检测机制(局部设置)
//.unchecked()
.request(newOnPermissionCallback() {
@Override
publicvoidonGranted(List<String>permissions, booleanall) {
if (!all) {
toast("获取部分权限成功,但部分权限未正常授予");
return;
}
toast("获取录音和日历权限成功");
}
@Override
publicvoidonDenied(List<String>permissions, booleannever) {
if (never) {
toast("被永久拒绝授权,请手动授予录音和日历权限");
// 如果是被永久拒绝就跳转到应用权限系统设置页面
XXPermissions.startPermissionActivity(context, permissions);
} else {
toast("获取录音和日历权限失败");
}
}
});
4.4框架其他API
// 判断一个或多个权限是否全部授予了
XXPermissions.isGranted(Contextcontext, String... permissions);
// 获取没有授予的权限
XXPermissions.getDenied(Contextcontext, String... permissions);
// 判断某个权限是否为特殊权限
XXPermissions.isSpecial(Stringpermission);
// 判断一个或多个权限是否被永久拒绝了
XXPermissions.isPermanentDenied(Activityactivity, String... permissions);
// 跳转到应用权限设置页
XXPermissions.startPermissionActivity(Contextcontext, String... permissions);
XXPermissions.startPermissionActivity(Activityactivity, String... permissions);
XXPermissions.startPermissionActivity(Activityactivity, String... permission, OnPermissionPageCallbackcallback);
XXPermissions.startPermissionActivity(Fragmentfragment, String... permissions);
XXPermissions.startPermissionActivity(Fragmentfragment, String... permissions, OnPermissionPageCallbackcallback);
// 设置不触发错误检测机制(全局设置)
XXPermissions.setCheckMode(false);
// 设置权限申请拦截器(全局设置)
XXPermissions.setInterceptor(newIPermissionInterceptor() {});
5.Dexter框架
这个框架不再处于积极开发中,最新的一次的更新是在14个月前。
Dexter
是一个 Android 库,它简化了在运行时请求权限的过程。 Android Marshmallow
包含一项新功能,允许用户在运行应用程序时授予或拒绝权限,而不是在安装时授予所有权限。 这种方法让用户可以更好地控制应用程序,但需要开发人员添加大量代码来支持它。 Dexter 将您的权限代码从您的活动中释放出来,让您可以在任何您想要的地方编写该逻辑。
5.1引入依赖
将库包含在您的build.gradle
dependencies{
implementation'com.karumi:dexter:6.2.3'
}
5.2单个权限
对于每个权限,注册一个PermissionListener
实现以接收请求的状态:
Dexter.withContext(this)
.withPermission(Manifest.permission.CAMERA)
.withListener(newPermissionListener() {
@OverridepublicvoidonPermissionGranted(PermissionGrantedResponseresponse) {/* ... */}
@OverridepublicvoidonPermissionDenied(PermissionDeniedResponseresponse) {/* ... */}
@OverridepublicvoidonPermissionRationaleShouldBeShown(PermissionRequestpermission, PermissionTokentoken) {/* ... */}
}).check();
5.3多个权限
Dexter.withContext(this)
.withPermissions(
Manifest.permission.CAMERA,
Manifest.permission.READ_CONTACTS,
Manifest.permission.RECORD_AUDIO
).withListener(newMultiplePermissionsListener() {
@OverridepublicvoidonPermissionsChecked(MultiplePermissionsReportreport) {/* ... */}
@OverridepublicvoidonPermissionRationaleShouldBeShown(List<PermissionRequest>permissions, PermissionTokentoken) {/* ... */}
}).check();
6.同类权限请求框架之间的对比
适配细节 | XXPermissions | AndPermission | PermissionX | AndroidUtilCode | PermissionsDispatcher | RxPermissions | EasyPermissions |
对应版本 | 16.0 | 2.0.3 | 1.6.4 | 1.31.0 | 4.9.2 | 0.12 | 3.0.0 |
issues 数 | |||||||
框架体积 | 51 KB | 127 KB | 90 KB | 500 KB | 99 KB | 28 KB | 48 KB |
框架维护状态 | 维护中 | 停止维护 | 维护中 | 停止维护 | 维护中 | 停止维护 | 停止维护 |
闹钟提醒权限 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
所有文件管理权限 | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
安装包权限 | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
悬浮窗权限 | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
系统设置权限 | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
通知栏权限 | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
通知栏监听权限 | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
勿扰权限 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
忽略电池优化权限 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
查看应用使用情况权限 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
VPN 权限 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
Android 13 危险权限 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
Android 12 危险权限 | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
Android 11 危险权限 | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
Android 10 危险权限 | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ |
Android 9.0 危险权限 | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ |
Android 8.0 危险权限 | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ |
新权限自动兼容旧设备 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
屏幕方向旋转场景适配 | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ |
后台申请权限场景适配 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
Android 12 内存泄漏问题修复 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
错误检测机制 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
7.Dexter请求权限实现手电筒
使用这个框架的前提只需要在build.gradle
中引入依赖即可
publicclassMainActivityextendsAppCompatActivity {
ImageViewflashBtn;
booleanstate;
@Override
protectedvoidonCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
flashBtn=findViewById(R.id.flashBtn);
Dexter.withContext(this)//指定页面
.withPermission(Manifest.permission.CAMERA)//指定权限
.withListener(newPermissionListener() {//创建监听
@Override
publicvoidonPermissionGranted(PermissionGrantedResponsepermissionGrantedResponse) {
//运行手电筒
runFlashlight();
}
//用户提示
@Override
publicvoidonPermissionDenied(PermissionDeniedResponsepermissionDeniedResponse) {
Toast.makeText(MainActivity.this,"Camera permission request",Toast.LENGTH_SHORT).show();
}
@Override
publicvoidonPermissionRationaleShouldBeShown(PermissionRequestpermissionRequest, PermissionTokenpermissionToken) {
}
}).check();
}
privatevoidrunFlashlight() {
flashBtn.setOnClickListener(newView.OnClickListener() {
@Override
publicvoidonClick(Viewview) {
if(!state){
//设置相机功能管理
CameraManagercameraManager=(CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
//getCameraIdList()按标识符返回当前连接的相机设备列表,包括可能被其他相机 API 客户端使用的相机。
StringcameraId=cameraManager.getCameraIdList()[0];
//打开和关闭指定相机设备的闪光灯功能。
cameraManager.setTorchMode(cameraId,true);
//设置状态为true,打开
state=true;
//更改图片资源
flashBtn.setImageResource(R.drawable.on);
}catch (CameraAccessExceptione){
}
}else{
//设置相机功能管理
CameraManagercameraManager=(CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
//getCameraIdList()按标识符返回当前连接的相机设备列表,包括可能被其他相机 API 客户端使用的相机。
StringcameraId=cameraManager.getCameraIdList()[0];
//打开和关闭指定相机设备的闪光灯功能。
cameraManager.setTorchMode(cameraId,false);
state=false;
flashBtn.setImageResource(R.drawable.off);
}catch (CameraAccessExceptione){
}
}
}
});
}
}
8.XXPermissions请求权限实现手电筒
- 我的项目的 Gradle 配置是
7.0 及以上
,所以我需要在settings.gradle
文件中加入maven { url 'https://jitpack.io' }
, - 并且我的项目是基于 AndroidX 包,需要在项目
gradle.properties
文件中加入android.enableJetifier = true
- 最后在项目 app 模块下的
build.gradle
文件中加入远程依赖
publicclassMainActivityextendsAppCompatActivity {
ImageViewflashBtn;
booleanstate;
@Override
protectedvoidonCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
flashBtn=findViewById(R.id.flashBtn);
XXPermissions.with(this)
// 申请单个权限(相机)
.permission(Permission.CAMERA)
// 申请多个权限(日历),要申请多个权限只需要把要申请的权限列出来即可
//.permission(Permission.Group.CALENDAR)
.request(newOnPermissionCallback() {
@Override
publicvoidonGranted(List<String>permissions, booleanall) {
if (!all) {
Toast.makeText(MainActivity.this,"获取部分权限成功,但部分权限未正常授予",Toast.LENGTH_SHORT).show();
return;
}
runFlashlight();
Toast.makeText(MainActivity.this,"获取相机权限成功",Toast.LENGTH_SHORT).show();
}
@Override
publicvoidonDenied(List<String>permissions, booleannever) {
if (never) {
Toast.makeText(MainActivity.this,"被永久拒绝授权,请手动授予相机权限",Toast.LENGTH_SHORT).show();
// 如果是被永久拒绝就跳转到应用权限系统设置页面
XXPermissions.startPermissionActivity(MainActivity.this, permissions);
} else {
Toast.makeText(MainActivity.this,"获取录音和日历权限失败",Toast.LENGTH_SHORT).show();
}
}
});
}
privatevoidrunFlashlight() {
flashBtn.setOnClickListener(newView.OnClickListener() {
@Override
publicvoidonClick(Viewview) {
if(!state){
//设置相机功能管理
CameraManagercameraManager=(CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
//getCameraIdList()按标识符返回当前连接的相机设备列表,包括可能被其他相机 API 客户端使用的相机。
StringcameraId=cameraManager.getCameraIdList()[0];
//打开和关闭指定相机设备的闪光灯功能。
cameraManager.setTorchMode(cameraId,true);
//设置状态为true,打开
state=true;
//更改图片资源
flashBtn.setImageResource(R.drawable.on);
}catch (CameraAccessExceptione){
}
}else{
//设置相机功能管理
CameraManagercameraManager=(CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
//getCameraIdList()按标识符返回当前连接的相机设备列表,包括可能被其他相机 API 客户端使用的相机。
StringcameraId=cameraManager.getCameraIdList()[0];
//打开和关闭指定相机设备的闪光灯功能。
cameraManager.setTorchMode(cameraId,false);
state=false;
flashBtn.setImageResource(R.drawable.off);
}catch (CameraAccessExceptione){
}
}
}
});
}
}
9.参考资料
Teacher.Hu(胡老师博客)Android 动态权限最全解析
[Teacher.Hu(胡老师博客)【Android】相对布局(RelativeLayout)最全解析
Android Camera2 之 CameraManager 详解