赞
踩
最近把AGP插件升级到8.1.0,新建项目的时候目标版本和编译版本都是33,发现之前的demo使用Camerax拍照和录像都失败了,于是查看了一下官网和各种资料,找到了Android13的适配方案.
- 作者:一笑的小酒馆
- 链接:https://juejin.cn/post/7267840969605382198
与早期版本一样,Android 13 包含一些行为变更,这些变更可能会影响您的应用。以下行为变更仅影响以 Android 13 或更高版本为目标平台的应用。如果您的应用以 Android 13 或更高版本为目标平台,您应该修改自己的应用以适当地支持这些行为(如果适用)。
此外,请务必查看对 Android 13 上运行的所有应用都有影响的行为变更列表。
该对话框的两个按钮,从上至下分别为“Allow”和“Don't allow”"图 1. 您在请求 READ_MEDIA_AUDIO
权限时向用户显示的系统权限对话框。
如果您的应用以 Android 13 或更高版本为目标平台,并且需要访问其他应用已经创建的媒体文件,您必须请求以下一项或多项细化的媒体权限,而不是READ_EXTERNAL_STORAGE
权限:
媒体类型 | 请求权限 |
---|---|
图片和照片 | READ_MEDIA_IMAGES |
视频 | READ_MEDIA_VIDEO |
音频文件 | READ_MEDIA_AUDIO |
如果用户之前向您的应用授予了 READ_EXTERNAL_STORAGE
权限,系统会自动向您的应用授予细化的媒体权限。否则,当应用请求上表中显示的任何权限时,系统会显示面向用户的对话框。在图 1 中,应用请求 READ_MEDIA_AUDIO
权限。
如果您同时请求 READ_MEDIA_IMAGES
权限和 READ_MEDIA_VIDEO
权限,系统只会显示一个系统权限对话框。
https://developer.android.google.cn/about/versions/13/features?hl=zh-cn
https://blog.csdn.net/as425017946/article/details/127530660
https://blog.csdn.net/guolin_blog/article/details/127024559
这里的依赖都是基于AGP8.1.0,Android Studio的插件版本 Gifaffe 2022.3.1
在项目的gradle目录下新建libs.version.toml文件
[versions] agp = "8.1.0" org-jetbrains-kotlin-android = "1.8.0" core-ktx = "1.10.1" junit = "4.13.2" androidx-test-ext-junit = "1.1.5" espresso-core = "3.5.1" appcompat = "1.6.1" material = "1.9.0" constraintlayout = "2.1.4" glide = "4.13.0" glide-compiler = "4.13.0" camerax = "1.1.0-beta03" camerax-core = "1.1.0-beta03" camerax-video = "1.1.0-beta03" camerax-view = "1.1.0-beta03" camerax-extensions = "1.1.0-beta03" camerax-lifecycle = "1.1.0-beta03" [libraries] core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" } junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" } espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" } appcompat = { group = "androidx.appcompat", name = "appcompat", version = "1.6.1" } material = { group = "com.google.android.material", name = "material", version.ref = "material" } constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } glide = {group = "com.github.bumptech.glide",name = "glide",version.ref = "glide"} camerax = {group = "androidx.camera",name = "camera-camera2",version.ref = "camerax" } camerax-core = {group = "androidx.camera",name = "camera-core",version.ref = "camerax-core"} camerax-video = {group = "androidx.camera",name = "camera-video",version.ref = "camerax-video"} camerax-view = {group = "androidx.camera",name = "camera-view",version.ref = "camerax-view"} camerax-extensions = {group = "androidx.camera",name = "camera-extensions",version.ref = "camerax-extensions"} camerax-lifecycle = {group = "androidx.camera",name = "camera-lifecycle",version.ref = "camerax-lifecycle"} kotlin-stdlib = {group = "org.jetbrains.kotlin",name = "kotlin-stdlib-jdk7",version.ref = "kotlin-stdlib"} kotlin-reflect = {group = "org.jetbrains.kotlin",name = "kotlin-reflect",version.ref = "kotlin-reflect"} kotlinx-coroutines-core = {group = "org.jetbrains.kotlin",name = "kotlinx-coroutines-core",version.ref = "kotlinx-coroutines-core"} kotlin-kotlinx-coroutines-android = {group = "org.jetbrains.kotlin",name = "kotlinx-coroutines-androidt",version.ref = "kotlinx-coroutines-android"} glide-compiler = {group = "com.github.bumptech.glide",name = "compiler",version.ref = "glide-compiler"} utilcodex = {group = "com.blankj",name = "utilcodex",version.ref = "utilcodex"}
dependencies { implementation(libs.core.ktx) implementation(libs.appcompat) implementation(libs.material) implementation(libs.constraintlayout) implementation(libs.glide) implementation(libs.camerax) implementation(libs.camerax.core) implementation(libs.camerax.view) implementation(libs.camerax.extensions) implementation(libs.camerax.lifecycle) implementation(libs.camerax.video) implementation(libs.kotlin.stdlib) implementation(libs.kotlin.reflect) implementation(libs.utilcodex) testImplementation(libs.junit) androidTestImplementation(libs.androidx.test.ext.junit) androidTestImplementation(libs.espresso.core) annotationProcessor(libs.glide.compiler) }
- <?xml version="1.0" encoding="utf-8"?>
- <androidx.constraintlayout.widget.ConstraintLayout xmlns: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">
-
-
-
-
- <androidx.camera.view.PreviewView
- android:id="@+id/mPreviewView"
- android:layout_width="0dp"
- android:layout_height="0dp"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toRightOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
-
-
- <Button
- android:id="@+id/btnCameraCapture"
- android:layout_width="0dp"
- android:layout_height="50dp"
- android:layout_marginBottom="50dp"
- android:background="@color/colorPrimaryDark"
- android:text="拍照"
- android:textColor="@color/white"
- android:textSize="16sp"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toLeftOf="@+id/btnVideo" />
-
-
- <Button
- android:id="@+id/btnVideo"
- android:layout_width="0dp"
- android:layout_height="50dp"
- android:layout_marginStart="10dp"
- android:layout_marginBottom="50dp"
- android:background="@color/colorPrimaryDark"
- android:text="录像"
- android:textColor="@color/white"
- android:textSize="16sp"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintLeft_toRightOf="@+id/btnCameraCapture"
- app:layout_constraintRight_toLeftOf="@+id/btnSwitch" />
-
-
- <Button
- android:id="@+id/btnSwitch"
- android:layout_width="0dp"
- android:layout_height="50dp"
- android:layout_marginStart="10dp"
- android:layout_marginBottom="50dp"
- android:background="@color/colorPrimaryDark"
- android:gravity="center"
- android:text="切换镜头"
- android:textColor="@color/white"
- android:textSize="16sp"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintLeft_toRightOf="@+id/btnVideo"
- app:layout_constraintRight_toRightOf="parent" />
-
-
- <Button
- android:id="@+id/btnOpenCamera"
- android:layout_width="200dp"
- android:layout_height="50dp"
- android:background="@color/colorPrimaryDark"
- android:text="进入相机拍照界面"
- android:textColor="@color/white"
- android:textSize="16sp"
- tools:ignore="MissingConstraints" />
-
-
-
-
- </androidx.constraintlayout.widget.ConstraintLayout>

- <?xml version="1.0" encoding="utf-8"?>
- <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- xmlns:app="http://schemas.android.com/apk/res-auto">
- <Button
- android:id="@+id/btnCamera"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:text="打开相机"
- tools:ignore="MissingConstraints" />
-
-
- <ImageView
- android:id="@+id/iv_avatar"
- android:layout_width="100dp"
- android:layout_height="100dp"
- android:layout_marginStart="20dp"
- android:layout_marginTop="20dp"
- android:background="@mipmap/ic_launcher"
- app:layout_constraintLeft_toRightOf="@+id/btnCamera"
- app:layout_constraintTop_toTopOf="parent" />
- </androidx.constraintlayout.widget.ConstraintLayout>

Android13相较于之前的版本变化很大,细化了权限请求,具体的参考上面的官网资料,直接上代码:
可以看到,API 32也就是Android 12及以下系统,我们仍然声明的是READ_EXTERNAL_STORAGE权限。
从Android 13开始,我们就会使用READ_MEDIA_IMAGES、READ_MEDIA_VIDEO、READ_MEDIA_AUDIO来替代了。
- <!--存储图像或者视频权限-->
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
- android:maxSdkVersion="32" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
- tools:ignore="ScopedStorage" android:maxSdkVersion="32"/>
- <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
- android:maxSdkVersion="32"
- tools:ignore="ScopedStorage" />
- <!--录制音频权限-->
- <uses-permission android:name="android.permission.RECORD_AUDIO"/>
- <uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
- <uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>
- <uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
7.项目中简单适配:
- @SuppressLint("RestrictedApi")
- private fun initPermission() {
- if (allPermissionsGranted()) {
- // ImageCapture
- startCamera()
- } else {
- ActivityCompat.requestPermissions(
- this, REQUIRED_PERMISSIONS, Constants.REQUEST_CODE_PERMISSIONS
- )
- }
- }
-
-
- private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
- ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
- }
-
-
- override fun onRequestPermissionsResult(
- requestCode: Int, permissions: Array<String>, grantResults:
- IntArray
- ) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults)
- if (requestCode == Constants.REQUEST_CODE_PERMISSIONS) {
- if (allPermissionsGranted()) {
- startCamera()
- } else {
- ToastUtils.shortToast("请您打开必要权限")
- }
- }
- }

7.2 Android13及以上的版本权限请求适配如下:
- private fun initPermission() {
- if (checkPermissions()) {
- // ImageCapture
- startCamera()
- } else {
- requestPermission()
- }
- }
-
-
- /**
- * Android13检查权限进行了细化,每个需要单独申请,这里我有拍照和录像,所以加入相机和录像权限
- *
- **/
- private fun checkPermissions(): Boolean {
- when {
- Build.VERSION.SDK_INT >= 33 -> {
- val permissions = arrayOf(
- Manifest.permission.READ_MEDIA_IMAGES,
- Manifest.permission.READ_MEDIA_AUDIO,
- Manifest.permission.READ_MEDIA_VIDEO,
- Manifest.permission.CAMERA,
- Manifest.permission.RECORD_AUDIO,
- )
- for (permission in permissions) {
- return Environment.isExternalStorageManager()
- }
- }
-
-
- else -> {
- for (permission in REQUIRED_PERMISSIONS) {
- if (ContextCompat.checkSelfPermission(
- this,
- permission
- ) != PackageManager.PERMISSION_GRANTED
- ) {
- return false
- }
- }
- }
- }
- return true
- }
-
- /**
- * 用户拒绝后请求权限需要同时申请,刚开始我是单独申请的调试后发现一直报错,所以改为一起申请
- *
- **/
- private fun requestPermission() {
- when {
- Build.VERSION.SDK_INT >= 33 -> {
- ActivityCompat.requestPermissions(
- this,
- arrayOf(Manifest.permission.READ_MEDIA_IMAGES,
- Manifest.permission.READ_MEDIA_AUDIO,
- Manifest.permission.READ_MEDIA_VIDEO,
- Manifest.permission.CAMERA,
- Manifest.permission.RECORD_AUDIO),
- Constants.REQUEST_CODE_PERMISSIONS
- )
- }
-
-
- else -> {
- ActivityCompat.requestPermissions(this,
- REQUIRED_PERMISSIONS, Constants.REQUEST_CODE_PERMISSIONS)
- }
- }
- }
-
-
- /**
- *用户请求权限后的回调,这里我是测试demo,所以用户拒绝后我会重复请求,真实项目根自己的需求来动态申请
- *
- **/
- override fun onRequestPermissionsResult(
- requestCode: Int, permissions: Array<String>, grantResults:
- IntArray
- ) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults)
- when (requestCode) {
- Constants.REQUEST_CODE_PERMISSIONS -> {
- var allPermissionsGranted = true
- for (result in grantResults) {
- if (result != PackageManager.PERMISSION_GRANTED) {
- allPermissionsGranted = false
- break
- }
- }
- when {
- allPermissionsGranted -> {
- // 权限已授予,执行文件读写操作
- startCamera()
- }
-
-
- else -> {
- // 权限被拒绝,处理权限请求失败的情况
- ToastUtils.shortToast("请您打开必要权限")
- requestPermission()
- }
- }
- }
- }
- }

- /**
- * 开始拍照
- */
- private fun takePhoto() {
- val imageCapture = imageCamera ?: return
- val photoFile = createFile(outputDirectory, DATE_FORMAT, PHOTO_EXTENSION)
- val metadata = ImageCapture.Metadata().apply {
- // Mirror image when using the front camera
- isReversedHorizontal = lensFacing == CameraSelector.LENS_FACING_FRONT
- }
- val outputOptions =
- ImageCapture.OutputFileOptions.Builder(photoFile).setMetadata(metadata).build()
- imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this),
- object : ImageCapture.OnImageSavedCallback {
- override fun onError(exc: ImageCaptureException) {
- LogUtils.e(TAG, "Photo capture failed: ${exc.message}", exc)
- ToastUtils.shortToast(" 拍照失败 ${exc.message}")
- }
-
-
- override fun onImageSaved(output: ImageCapture.OutputFileResults) {
- val savedUri = output.savedUri ?: Uri.fromFile(photoFile)
- ToastUtils.shortToast(" 拍照成功 $savedUri")
- LogUtils.e(TAG, savedUri.path.toString())
- val mimeType = MimeTypeMap.getSingleton()
- .getMimeTypeFromExtension(savedUri.toFile().extension)
- MediaScannerConnection.scanFile(
- this@MainActivity,
- arrayOf(savedUri.toFile().absolutePath),
- arrayOf(mimeType)
- ) { _, uri ->
- LogUtils.d(
- TAG,
- "Image capture scanned into media store: ${uri.path.toString()}"
- )
- }
- }
- })
- }

之前的版本很老还在测试阶段,所以官方api发生了一些改变:
旧的api示例如下:
- /**
- * 开始录像
- */
- @SuppressLint("RestrictedApi", "ClickableViewAccessibility")
- private fun takeVideo() {
- isRecordVideo = true
- val mFileDateFormat = SimpleDateFormat(DATE_FORMAT, Locale.US)
- //视频保存路径
- val file = File(FileManager.getCameraVideoPath(), mFileDateFormat.format(Date()) + ".mp4")
- //开始录像
- videoCapture?.startRecording(
- file,
- Executors.newSingleThreadExecutor(),
- object : OnVideoSavedCallback {
- override fun onVideoSaved(@NonNull file: File) {
- isRecordVideo = false
- LogUtils.d(TAG,"===视频保存的地址为=== ${file.absolutePath}")
- //保存视频成功回调,会在停止录制时被调用
- ToastUtils.shortToast(" 录像成功 $file")
- }
-
-
- override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) {
- //保存失败的回调,可能在开始或结束录制时被调用
- isRecordVideo = false
- LogUtils.e(TAG, "onError: $message")
- ToastUtils.shortToast(" 录像失败 $message")
- }
- })
- }

新的api示例如下:
startRecording方法参数发生了一些变化:第一个参数是传入一个文件输出信息类,之前是直接传入文件,其实影响不大
我们通过val outputOptions = OutputFileOptions.Builder(file)这个类构建一个对象,然后在开始录像时传入即可.
- /**
- * 开始录像
- */
- @SuppressLint("RestrictedApi", "ClickableViewAccessibility", "MissingPermission")
- private fun takeVideo() {
- //开始录像
- try {
- isRecordVideo = true
- val mFileDateFormat = SimpleDateFormat(DATE_FORMAT, Locale.US)
- //视频保存路径
- val file =
- File(FileManager.getCameraVideoPath(), mFileDateFormat.format(Date()) + ".mp4")
- val outputOptions = OutputFileOptions.Builder(file)
- videoCapture?.startRecording(
- outputOptions.build(),
- Executors.newSingleThreadExecutor(),
- object : OnVideoSavedCallback {
- override fun onVideoSaved(outputFileResults: VideoCapture.OutputFileResults) {
- isRecordVideo = false
- LogUtils.d(TAG, "===视频保存的地址为=== ${file.absolutePath}")
- //保存视频成功回调,会在停止录制时被调用
- ToastUtils.shortToast(" 录像成功 $file")
- }
-
-
- override fun onError(
- videoCaptureError: Int,
- message: String,
- cause: Throwable?
- ) {
- //保存失败的回调,可能在开始或结束录制时被调用
- isRecordVideo = false
- LogUtils.e(TAG, "onError: $message")
- ToastUtils.shortToast(" 录像失败 $message")
- }
- })
- } catch (e: Exception) {
- e.printStackTrace()
- LogUtils.e(TAG, "===录像出错===${e.message}")
- }
- }

这里通过isRecordVideo是否正在录像进行录像和停止录像的操作
- btnVideo.setOnClickListener {
- if (!isRecordVideo) {
- takeVideo()
- isRecordVideo = true
- btnVideo.text = "停止录像"
- } else {
- isRecordVideo = false
- videoCapture?.stopRecording()//停止录制
- //preview?.clear()//清除预览
- btnVideo.text = "开始录像"
- }
- }
- object Constants {
- const val REQUEST_CODE_PERMISSIONS = 101
- const val REQUEST_CODE_CAMERA = 102
- const val REQUEST_CODE_CROP = 103
-
-
- const val DATE_FORMAT = "yyyy-MM-dd HH-mm-ss"
- const val PHOTO_EXTENSION = ".jpg"
-
-
- val REQUIRED_PERMISSIONS = arrayOf(
- Manifest.permission.CAMERA,
- Manifest.permission.WRITE_EXTERNAL_STORAGE,
- Manifest.permission.READ_EXTERNAL_STORAGE,
- Manifest.permission.RECORD_AUDIO
- )
- }

- package com.example.cameraxdemo.utils
-
-
- import android.annotation.SuppressLint
- import android.app.Activity
- import android.content.Context
- import android.os.Handler
- import android.os.Looper
- import android.os.Message
- import android.util.Log
- import android.view.Gravity
- import android.widget.Toast
- import androidx.annotation.StringRes
- import com.example.cameraxdemo.app.CameraApp
- import java.lang.reflect.Field
-
-
- /**
- *@author: njb
- *@date: 2023/8/15 17:13
- *@desc:
- */
- object ToastUtils {
- private const val TAG = "ToastUtil"
- private var mToast: Toast? = null
- private var sField_TN: Field? = null
- private var sField_TN_Handler: Field? = null
- private var sIsHookFieldInit = false
- private const val FIELD_NAME_TN = "mTN"
- private const val FIELD_NAME_HANDLER = "mHandler"
- private fun showToast(
- context: Context, text: CharSequence,
- duration: Int, isShowCenterFlag: Boolean
- ) {
- val toastRunnable = ToastRunnable(context, text, duration, isShowCenterFlag)
- if (context is Activity) {
- if (!context.isFinishing) {
- context.runOnUiThread(toastRunnable)
- }
- } else {
- val handler = Handler(context.mainLooper)
- handler.post(toastRunnable)
- }
- }
-
-
- fun shortToast(context: Context, text: CharSequence) {
- showToast(context, text, Toast.LENGTH_SHORT, false)
- }
-
-
- fun longToast(context: Context, text: CharSequence) {
- showToast(context, text, Toast.LENGTH_LONG, false)
- }
-
-
- fun shortToast(msg: String) {
- showToast(CameraApp.mInstance, msg, Toast.LENGTH_SHORT, false)
- }
-
-
- fun shortToast(@StringRes resId: Int) {
- showToast(
- CameraApp.mInstance, CameraApp.mInstance.getText(resId),
- Toast.LENGTH_SHORT, false
- )
- }
-
-
- fun centerShortToast(msg: String) {
- showToast(CameraApp.mInstance, msg, Toast.LENGTH_SHORT, true)
- }
-
-
- fun centerShortToast(@StringRes resId: Int) {
- showToast(
- CameraApp.mInstance, CameraApp.mInstance.getText(resId),
- Toast.LENGTH_SHORT, true
- )
- }
-
-
- fun cancelToast() {
- val looper = Looper.getMainLooper()
- if (looper.thread === Thread.currentThread()) {
- mToast!!.cancel()
- } else {
- Handler(looper).post { mToast!!.cancel() }
- }
- }
-
-
- @SuppressLint("SoonBlockedPrivateApi")
- private fun hookToast(toast: Toast?) {
- try {
- if (!sIsHookFieldInit) {
- sField_TN = Toast::class.java.getDeclaredField(FIELD_NAME_TN)
- sField_TN?.run {
- isAccessible = true
- sField_TN_Handler = type.getDeclaredField(FIELD_NAME_HANDLER)
- }
- sField_TN_Handler?.isAccessible = true
- sIsHookFieldInit = true
- }
- val tn = sField_TN!![toast]
- val originHandler = sField_TN_Handler!![tn] as Handler
- sField_TN_Handler!![tn] = SafelyHandlerWrapper(originHandler)
- } catch (e: Exception) {
- Log.e(TAG, "Hook toast exception=$e")
- }
- }
-
-
- private class ToastRunnable(
- private val context: Context,
- private val text: CharSequence,
- private val duration: Int,
- private val isShowCenter: Boolean
- ) : Runnable {
- @SuppressLint("ShowToast")
- override fun run() {
- if (mToast == null) {
- mToast = Toast.makeText(context, text, duration)
- } else {
- mToast!!.setText(text)
- if (isShowCenter) {
- mToast!!.setGravity(Gravity.CENTER, 0, 0)
- }
- mToast!!.duration = duration
- }
- hookToast(mToast)
- mToast!!.show()
- }
- }
-
-
- private class SafelyHandlerWrapper(private val originHandler: Handler?) : Handler() {
- override fun dispatchMessage(msg: Message) {
- try {
- super.dispatchMessage(msg)
- } catch (e: Exception) {
- Log.e(TAG, "Catch system toast exception:$e")
- }
- }
-
-
- override fun handleMessage(msg: Message) {
- originHandler?.handleMessage(msg)
- }
- }
- }

- package com.example.cameraxdemo.utils;
-
-
- import android.app.Activity;
- import android.content.ContentResolver;
- import android.content.ContentUris;
- import android.content.ContentValues;
- import android.content.Context;
- import android.content.Intent;
- import android.database.Cursor;
- import android.net.Uri;
- import android.os.Build;
- import android.os.Environment;
- import android.provider.DocumentsContract;
- import android.provider.MediaStore;
- import android.text.TextUtils;
- import android.util.Log;
-
-
- import androidx.annotation.NonNull;
-
-
- import com.example.cameraxdemo.app.CameraApp;
-
-
- import java.io.File;
-
-
- /**
- * @author: njb
- * @date: 2023/8/15 17:13
- * @desc:
- */
- public class FileManager {
- // 媒体模块根目录
- private static final String SAVE_MEDIA_ROOT_DIR = Environment.DIRECTORY_DCIM;
- // 媒体模块存储路径
- private static final String SAVE_MEDIA_DIR = SAVE_MEDIA_ROOT_DIR + "/CameraXApp";
- private static final String AVATAR_DIR = "/avatar";
- private static final String SAVE_MEDIA_VIDEO_DIR = SAVE_MEDIA_DIR + "/video";
- private static final String SAVE_MEDIA_PHOTO_DIR = SAVE_MEDIA_DIR + "/photo";
- // JPG后缀
- public static final String JPG_SUFFIX = ".jpg";
- // PNG后缀
- public static final String PNG_SUFFIX = ".png";
- // MP4后缀
- public static final String MP4_SUFFIX = ".mp4";
- // YUV后缀
- public static final String YUV_SUFFIX = ".yuv";
- // h264后缀
- public static final String H264_SUFFIX = ".h264";
-
-
-
-
- /**
- * 保存图片到系统相册
- *
- * @param context
- * @param file
- */
- public static String saveImage(Context context, File file) {
- ContentResolver localContentResolver = context.getContentResolver();
- ContentValues localContentValues = getImageContentValues(context, file, System.currentTimeMillis());
- localContentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, localContentValues);
-
-
- Intent localIntent = new Intent("android.intent.action.MEDIA_SCANNER_SCAN_FILE");
- final Uri localUri = Uri.fromFile(file);
- localIntent.setData(localUri);
- context.sendBroadcast(localIntent);
- return file.getAbsolutePath();
- }
-
-
- public static ContentValues getImageContentValues(Context paramContext, File paramFile, long paramLong) {
- ContentValues localContentValues = new ContentValues();
- localContentValues.put("title", paramFile.getName());
- localContentValues.put("_display_name", paramFile.getName());
- localContentValues.put("mime_type", "image/jpeg");
- localContentValues.put("datetaken", Long.valueOf(paramLong));
- localContentValues.put("date_modified", Long.valueOf(paramLong));
- localContentValues.put("date_added", Long.valueOf(paramLong));
- localContentValues.put("orientation", Integer.valueOf(0));
- localContentValues.put("_data", paramFile.getAbsolutePath());
- localContentValues.put("_size", Long.valueOf(paramFile.length()));
- return localContentValues;
- }
-
-
- /**
- * 获取App存储根目录
- */
- public static String getAppRootDir() {
- String path = getStorageRootDir();
- FileUtil.createOrExistsDir(path);
- return path;
- }
-
-
- /**
- * 获取文件存储根目录
- */
- public static String getStorageRootDir() {
- File filePath = CameraApp.Companion.getMInstance().getExternalFilesDir("");
- String path;
- if (filePath != null) {
- path = filePath.getAbsolutePath();
- } else {
- path = CameraApp.Companion.getMInstance().getFilesDir().getAbsolutePath();
- }
- return path;
- }
-
-
- /**
- * 图片地址
- */
- public static String getCameraPhotoPath() {
- return getFolderDirPath(SAVE_MEDIA_PHOTO_DIR);
- }
-
-
- /**
- * 获取拍照普通图片文件
- */
- public static File getSavedPictureFile(long timeStamp) {
- String fileName = "image"+ "_"+ + timeStamp + JPG_SUFFIX;
- return new File(getCameraPhotoPath(), fileName);
- }
-
-
- /**
- * 头像地址
- */
- public static String getAvatarPath(String fileName) {
- String path;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- path = getFolderDirPath(SAVE_MEDIA_DIR + AVATAR_DIR);
- } else {
- path = getSaveDir(AVATAR_DIR);
- }
- return path + File.separator + fileName;
- }
-
-
- /**
- * 视频地址
- */
- public static String getCameraVideoPath() {
- return getFolderDirPath(SAVE_MEDIA_VIDEO_DIR);
- }
-
-
- public static String getFolderDirPath(String dstDirPathToCreate) {
- File dstFileDir = new File(Environment.getExternalStorageDirectory(), dstDirPathToCreate);
- if (!dstFileDir.exists() && !dstFileDir.mkdirs()) {
- Log.e("Failed to create file", dstDirPathToCreate);
- return null;
- }
- return dstFileDir.getAbsolutePath();
- }
-
-
- /**
- * 获取具体模块存储目录
- */
- public static String getSaveDir(@NonNull String directory) {
- String path = "";
- if (TextUtils.isEmpty(directory) || "/".equals(directory)) {
- path = "";
- } else if (directory.startsWith("/")) {
- path = directory;
- } else {
- path = "/" + directory;
- }
- path = getAppRootDir() + path;
- FileUtil.createOrExistsDir(path);
- return path;
- }
-
-
- /**
- * 通过媒体文件Uri获取文件-Android 11兼容
- *
- * @param fileUri 文件Uri
- */
- public static File getMediaUri2File(Uri fileUri) {
- String[] projection = {MediaStore.Images.Media.DATA};
- Cursor cursor = CameraApp.Companion.getMInstance().getContentResolver().query(fileUri, projection,
- null, null, null);
- if (cursor != null) {
- if (cursor.moveToFirst()) {
- int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
- String path = cursor.getString(columnIndex);
- cursor.close();
- return new File(path);
- }
- }
- return null;
- }
-
-
- /**
- * 根据Uri获取图片绝对路径,解决Android4.4以上版本Uri转换
- *
- * @param context 上下文
- * @param imageUri 图片地址
- */
- public static String getImageAbsolutePath(Activity context, Uri imageUri) {
- if (context == null || imageUri == null)
- return null;
- if (DocumentsContract.isDocumentUri(context, imageUri)) {
- if (isExternalStorageDocument(imageUri)) {
- String docId = DocumentsContract.getDocumentId(imageUri);
- String[] split = docId.split(":");
- String type = split[0];
- if ("primary".equalsIgnoreCase(type)) {
- return Environment.getExternalStorageDirectory() + "/" + split[1];
- }
- } else if (isDownloadsDocument(imageUri)) {
- String id = DocumentsContract.getDocumentId(imageUri);
- Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.parseLong(id));
- return getDataColumn(context, contentUri, null, null);
- } else if (isMediaDocument(imageUri)) {
- String docId = DocumentsContract.getDocumentId(imageUri);
- String[] split = docId.split(":");
- String type = split[0];
- Uri contentUri = null;
- if ("image".equals(type)) {
- contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
- } else if ("video".equals(type)) {
- contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
- } else if ("audio".equals(type)) {
- contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
- }
- String selection = MediaStore.Images.Media._ID + "=?";
- String[] selectionArgs = new String[]{split[1]};
- return getDataColumn(context, contentUri, selection, selectionArgs);
- }
- } // MediaStore (and general)
- else if ("content".equalsIgnoreCase(imageUri.getScheme())) {
- // Return the remote address
- if (isGooglePhotosUri(imageUri))
- return imageUri.getLastPathSegment();
- return getDataColumn(context, imageUri, null, null);
- }
- // File
- else if ("file".equalsIgnoreCase(imageUri.getScheme())) {
- return imageUri.getPath();
- }
- return null;
- }
-
-
- /**
- * @param uri The Uri to checkRemote.
- * @return Whether the Uri authority is ExternalStorageProvider.
- */
- public static boolean isExternalStorageDocument(Uri uri) {
- return "com.android.externalstorage.documents".equals(uri.getAuthority());
- }
-
-
- /**
- * @param uri The Uri to checkRemote.
- * @return Whether the Uri authority is DownloadsProvider.
- */
- public static boolean isDownloadsDocument(Uri uri) {
- return "com.android.providers.downloads.documents".equals(uri.getAuthority());
- }
-
-
- /**
- * @param uri The Uri to checkRemote.
- * @return Whether the Uri authority is MediaProvider.
- */
- public static boolean isMediaDocument(Uri uri) {
- return "com.android.providers.media.documents".equals(uri.getAuthority());
- }
-
-
- /**
- * @param uri The Uri to checkRemote.
- * @return Whether the Uri authority is Google Photos.
- */
- public static boolean isGooglePhotosUri(Uri uri) {
- return "com.google.android.apps.photos.content".equals(uri.getAuthority());
- }
-
-
- public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
- String column = MediaStore.Images.Media.DATA;
- String[] projection = {column};
- try (Cursor cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null)) {
- if (cursor != null && cursor.moveToFirst()) {
- int index = cursor.getColumnIndexOrThrow(column);
- return cursor.getString(index);
- }
- }
- return null;
- }
- }

- package com.example.cameraxdemo.utils
-
-
- import android.content.Context
- import android.os.Environment
- import com.example.cameraxdemo.R
- import java.io.File
- import java.text.SimpleDateFormat
- import java.util.Locale
-
-
- /**
- *@author: njb
- *@date: 2023/8/15 17:13
- *@desc:
- */
- object VideoFileUtils {
- /**
- * 获取视频文件路径
- */
- fun getVideoName(): String {
- val videoPath = Environment.getExternalStorageDirectory().toString() + "/CameraXApp"
- val dir = File(videoPath)
- if (!dir.exists() && !dir.mkdirs()) {
- ToastUtils.shortToast("文件不存在")
- }
- return videoPath
- }
-
-
- /**
- * 获取图片文件路径
- */
- fun getImageFileName(): String {
- val imagePath = Environment.getExternalStorageDirectory().toString() + "/images"
- val dir = File(imagePath)
- if (!dir.exists() && !dir.mkdirs()) {
- ToastUtils.shortToast("文件不存在")
- }
- return imagePath
- }
-
-
- /**
- * 拍照文件保存路径
- * @param context
- * @return
- */
- fun getPhotoDir(context: Context?): String? {
- return FileManager.getFolderDirPath(
- "DCIM/Camera/CameraXApp/photo"
- )
- }
-
-
- /**
- * 视频文件保存路径
- * @param context
- * @return
- */
- fun getVideoDir(): String? {
- return FileManager.getFolderDirPath(
- "DCIM/Camera/CameraXApp/video"
- )
- }
-
-
- /** Use external media if it is available, our app's file directory otherwise */
- fun getOutputDirectory(context: Context): File {
- val appContext = context.applicationContext
- val mediaDir = context.externalMediaDirs.firstOrNull()?.let {
- File(it, appContext.resources.getString(R.string.app_name)).apply { mkdirs() }
- }
- return if (mediaDir != null && mediaDir.exists())
- mediaDir else appContext.filesDir
- }
-
-
- fun createFile(baseFolder: File, format: String, extension: String) =
- File(
- baseFolder, SimpleDateFormat(format, Locale.US)
- .format(System.currentTimeMillis()) + extension
- )
- }

- package com.example.cameraxdemo.app
-
-
- import android.app.Application
-
-
- /**
- *@author: njb
- *@date: 2023/8/15 17:07
- *@desc:
- */
- class CameraApp : Application() {
-
-
- override fun onCreate() {
- super.onCreate()
- mInstance = this
- }
-
-
- companion object {
- lateinit var mInstance: CameraApp
- private set
- }
- }

- package com.example.cameraxdemo
-
-
- import android.Manifest
- import android.annotation.SuppressLint
- import android.content.Intent
- import android.content.pm.PackageManager
- import android.media.MediaScannerConnection
- import android.net.Uri
- import android.os.Build
- import android.os.Bundle
- import android.os.Environment
- import android.webkit.MimeTypeMap
- import android.widget.Button
- import androidx.appcompat.app.AppCompatActivity
- import androidx.camera.core.AspectRatio
- import androidx.camera.core.Camera
- import androidx.camera.core.CameraSelector
- import androidx.camera.core.ImageCapture
- import androidx.camera.core.ImageCaptureException
- import androidx.camera.core.Preview
- import androidx.camera.core.VideoCapture
- import androidx.camera.core.VideoCapture.OnVideoSavedCallback
- import androidx.camera.core.VideoCapture.OutputFileOptions
- import androidx.camera.lifecycle.ProcessCameraProvider
- import androidx.camera.view.PreviewView
- import androidx.core.app.ActivityCompat
- import androidx.core.content.ContextCompat
- import androidx.core.net.toFile
- import com.blankj.utilcode.util.LogUtils
- import com.example.cameraxdemo.activity.CameraActivity
- import com.example.cameraxdemo.utils.Constants
- import com.example.cameraxdemo.utils.Constants.Companion.DATE_FORMAT
- import com.example.cameraxdemo.utils.Constants.Companion.PHOTO_EXTENSION
- import com.example.cameraxdemo.utils.Constants.Companion.REQUIRED_PERMISSIONS
- import com.example.cameraxdemo.utils.FileManager
- import com.example.cameraxdemo.utils.ToastUtils
- import com.example.cameraxdemo.utils.VideoFileUtils.createFile
- import com.example.cameraxdemo.utils.VideoFileUtils.getOutputDirectory
- import java.io.File
- import java.text.SimpleDateFormat
- import java.util.*
- import java.util.concurrent.ExecutorService
- import java.util.concurrent.Executors
-
-
- class MainActivity : AppCompatActivity() {
- private var imageCamera: ImageCapture? = null
- private lateinit var cameraExecutor: ExecutorService
- private var videoCapture: VideoCapture? = null
- private var cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA//当前相机
- private var preview: Preview? = null//预览对象
- private var cameraProvider: ProcessCameraProvider? = null//相机信息
- private lateinit var camera: Camera //相机对象
- private var isRecordVideo: Boolean = false
- private val TAG = "CameraXApp"
- private lateinit var outputDirectory: File
- private var lensFacing: Int = CameraSelector.LENS_FACING_BACK
- private val btnCameraCapture: Button by lazy { findViewById(R.id.btnCameraCapture) }
- private val btnVideo: Button by lazy { findViewById(R.id.btnVideo) }
- private val btnSwitch: Button by lazy { findViewById(R.id.btnSwitch) }
- private val btnOpenCamera: Button by lazy { findViewById(R.id.btnOpenCamera) }
- private val viewFinder: PreviewView by lazy { findViewById(R.id.mPreviewView) }
-
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
- initPermission()
- initView()
- }
-
-
- private fun initView() {
- outputDirectory = getOutputDirectory(this)
- }
-
-
- @SuppressLint("RestrictedApi")
- private fun initListener() {
- btnCameraCapture.setOnClickListener {
- takePhoto()
- }
- btnVideo.setOnClickListener {
- if (!isRecordVideo) {
- takeVideo()
- isRecordVideo = true
- btnVideo.text = "停止录像"
- } else {
- isRecordVideo = false
- videoCapture?.stopRecording()//停止录制
- //preview?.clear()//清除预览
- btnVideo.text = "开始录像"
- }
- }
- btnSwitch.setOnClickListener {
- cameraSelector = if (cameraSelector == CameraSelector.DEFAULT_BACK_CAMERA) {
- CameraSelector.DEFAULT_FRONT_CAMERA
- } else {
- CameraSelector.DEFAULT_BACK_CAMERA
- }
- if (!isRecordVideo) {
- startCamera()
- }
- }
- btnOpenCamera.setOnClickListener {
- val intent = Intent(this, CameraActivity::class.java)
- startActivity(intent)
- }
- }
-
-
-
-
- private fun initPermission() {
- if (checkPermissions()) {
- // ImageCapture
- startCamera()
- } else {
- requestPermission()
- }
- }
-
-
- private fun requestPermission() {
- when {
- Build.VERSION.SDK_INT >= 33 -> {
- ActivityCompat.requestPermissions(
- this,
- arrayOf(Manifest.permission.READ_MEDIA_IMAGES,Manifest.permission.READ_MEDIA_AUDIO,Manifest.permission.READ_MEDIA_VIDEO,Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO),
- Constants.REQUEST_CODE_PERMISSIONS
- )
- }
-
-
- else -> {
- ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, Constants.REQUEST_CODE_PERMISSIONS)
- }
- }
- }
-
-
-
-
- /**
- * 开始拍照
- */
- private fun takePhoto() {
- val imageCapture = imageCamera ?: return
- val photoFile = createFile(outputDirectory, DATE_FORMAT, PHOTO_EXTENSION)
- val metadata = ImageCapture.Metadata().apply {
- // Mirror image when using the front camera
- isReversedHorizontal = lensFacing == CameraSelector.LENS_FACING_FRONT
- }
- val outputOptions =
- ImageCapture.OutputFileOptions.Builder(photoFile).setMetadata(metadata).build()
- imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this),
- object : ImageCapture.OnImageSavedCallback {
- override fun onError(exc: ImageCaptureException) {
- LogUtils.e(TAG, "Photo capture failed: ${exc.message}", exc)
- ToastUtils.shortToast(" 拍照失败 ${exc.message}")
- }
-
-
- override fun onImageSaved(output: ImageCapture.OutputFileResults) {
- val savedUri = output.savedUri ?: Uri.fromFile(photoFile)
- ToastUtils.shortToast(" 拍照成功 $savedUri")
- LogUtils.e(TAG, savedUri.path.toString())
- val mimeType = MimeTypeMap.getSingleton()
- .getMimeTypeFromExtension(savedUri.toFile().extension)
- MediaScannerConnection.scanFile(
- this@MainActivity,
- arrayOf(savedUri.toFile().absolutePath),
- arrayOf(mimeType)
- ) { _, uri ->
- LogUtils.d(
- TAG,
- "Image capture scanned into media store: ${uri.path.toString()}"
- )
- }
- }
- })
- }
-
-
-
-
- /**
- * 开始录像
- */
- @SuppressLint("RestrictedApi", "ClickableViewAccessibility", "MissingPermission")
- private fun takeVideo() {
- //开始录像
- try {
- isRecordVideo = true
- val mFileDateFormat = SimpleDateFormat(DATE_FORMAT, Locale.US)
- //视频保存路径
- val file =
- File(FileManager.getCameraVideoPath(), mFileDateFormat.format(Date()) + ".mp4")
- val outputOptions = OutputFileOptions.Builder(file)
- videoCapture?.startRecording(
- outputOptions.build(),
- Executors.newSingleThreadExecutor(),
- object : OnVideoSavedCallback {
- override fun onVideoSaved(outputFileResults: VideoCapture.OutputFileResults) {
- isRecordVideo = false
- LogUtils.d(TAG, "===视频保存的地址为=== ${file.absolutePath}")
- //保存视频成功回调,会在停止录制时被调用
- ToastUtils.shortToast(" 录像成功 $file")
- }
-
-
- override fun onError(
- videoCaptureError: Int,
- message: String,
- cause: Throwable?
- ) {
- //保存失败的回调,可能在开始或结束录制时被调用
- isRecordVideo = false
- LogUtils.e(TAG, "onError: $message")
- ToastUtils.shortToast(" 录像失败 $message")
- }
- })
- } catch (e: Exception) {
- e.printStackTrace()
- LogUtils.e(TAG, "===录像出错===${e.message}")
- }
- }
-
-
- /**
- * 开始相机预览
- */
- @SuppressLint("RestrictedApi")
- private fun startCamera() {
- cameraExecutor = Executors.newSingleThreadExecutor()
- val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
- cameraProviderFuture.addListener(Runnable {
- cameraProvider = cameraProviderFuture.get()//获取相机信息
-
-
- //预览配置
- preview = Preview.Builder()
- .build()
- .also {
- it.setSurfaceProvider(viewFinder.surfaceProvider)
- }
-
-
- imageCamera = ImageCapture.Builder()
- .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
- .build()
-
-
- videoCapture = VideoCapture.Builder()//录像用例配置
- .setTargetAspectRatio(AspectRatio.RATIO_16_9) //设置高宽比
- //.setTargetRotation(viewFinder.display!!.rotation)//设置旋转角度
- .build()
- try {
- cameraProvider?.unbindAll()//先解绑所有用例
- camera = cameraProvider?.bindToLifecycle(
- this,
- cameraSelector,
- preview,
- imageCamera,
- videoCapture
- )!!//绑定用例
- } catch (e: Exception) {
- LogUtils.e(TAG, "Use case binding failed", e.message)
- }
-
-
- }, ContextCompat.getMainExecutor(this))
- initListener()
- }
-
-
- override fun onRequestPermissionsResult(
- requestCode: Int, permissions: Array<String>, grantResults:
- IntArray
- ) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults)
- when (requestCode) {
- Constants.REQUEST_CODE_PERMISSIONS -> {
- var allPermissionsGranted = true
- for (result in grantResults) {
- if (result != PackageManager.PERMISSION_GRANTED) {
- allPermissionsGranted = false
- break
- }
- }
- when {
- allPermissionsGranted -> {
- // 权限已授予,执行文件读写操作
- startCamera()
- }
-
-
- else -> {
- // 权限被拒绝,处理权限请求失败的情况
- ToastUtils.shortToast("请您打开必要权限")
- requestPermission()
- }
- }
- }
- }
- }
-
-
- private fun checkPermissions(): Boolean {
- when {
- Build.VERSION.SDK_INT >= 33 -> {
- val permissions = arrayOf(
- Manifest.permission.READ_MEDIA_IMAGES,
- Manifest.permission.READ_MEDIA_AUDIO,
- Manifest.permission.READ_MEDIA_VIDEO,
- Manifest.permission.CAMERA,
- Manifest.permission.RECORD_AUDIO,
- )
- for (permission in permissions) {
- return Environment.isExternalStorageManager()
- }
- }
-
-
- else -> {
- for (permission in REQUIRED_PERMISSIONS) {
- if (ContextCompat.checkSelfPermission(
- this,
- permission
- ) != PackageManager.PERMISSION_GRANTED
- ) {
- return false
- }
- }
- }
- }
- return true
- }
-
-
- override fun onDestroy() {
- super.onDestroy()
- cameraExecutor.shutdown()
- }
- }

模拟器日志如下:
真机日志打印:
- package com.example.cameraxdemo.activity
-
-
- import androidx.appcompat.app.AppCompatActivity
- import android.content.ContentValues
- import android.content.Intent
- import android.graphics.Bitmap
- import android.net.Uri
- import android.os.Build
- import android.os.Bundle
- import android.provider.MediaStore
- import android.util.Log
- import android.widget.Button
- import android.widget.ImageView
- import com.blankj.utilcode.util.LogUtils
- import com.bumptech.glide.Glide
- import com.example.cameraxdemo.R
- import com.example.cameraxdemo.utils.Constants.Companion.REQUEST_CODE_CAMERA
- import com.example.cameraxdemo.utils.Constants.Companion.REQUEST_CODE_CROP
- import com.example.cameraxdemo.utils.FileManager
- import com.example.cameraxdemo.utils.FileUtil
- import java.io.File
-
-
- /**
- *@author: njb
- *@date: 2023/8/15 17:20
- *@desc:
- */
- class CameraActivity :AppCompatActivity(){
- private var mUploadImageUri: Uri? = null
- private var mUploadImageFile: File? = null
- private var photoUri: Uri? = null
- private val btnCamera:Button by lazy { findViewById(R.id.btnCamera) }
- private val ivAvatar:ImageView by lazy { findViewById(R.id.iv_avatar) }
- private val TAG = CameraActivity::class.java.name
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_camera)
- initView()
- }
-
-
- private fun initView() {
- btnCamera.setOnClickListener {
- startSystemCamera()
- }
- }
-
-
- /**
- * 调起系统相机拍照
- */
- private fun startSystemCamera() {
- val takeIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
- val values = ContentValues()
- //根据uri查询图片地址
- photoUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
- LogUtils.d(TAG, "photoUri:" + photoUri?.authority + ",photoUri:" + photoUri?.path)
- //放入拍照后的地址
- takeIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri)
- //调起拍照
- startActivityForResult(
- takeIntent,
- REQUEST_CODE_CAMERA
- )
- }
-
-
-
-
- /**
- * 设置用户头像
- */
- private fun setAvatar() {
- val file: File? = if (mUploadImageUri != null) {
- FileManager.getMediaUri2File(mUploadImageUri)
- } else {
- mUploadImageFile
- }
- Glide.with(this).load(file).into(ivAvatar)
- LogUtils.d(TAG,"filepath"+ file!!.absolutePath)
- }
-
-
- /**
- * 系统裁剪方法
- */
- private fun workCropFun(imgPathUri: Uri?) {
- mUploadImageUri = null
- mUploadImageFile = null
- if (imgPathUri != null) {
- val imageObject: Any = FileUtil.getHeadJpgFile()
- if (imageObject is Uri) {
- mUploadImageUri = imageObject
- }
- if (imageObject is File) {
- mUploadImageFile = imageObject
- }
- val intent = Intent("com.android.camera.action.CROP")
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
- }
- intent.run {
- setDataAndType(imgPathUri, "image/*")// 图片资源
- putExtra("crop", "true") // 裁剪
- putExtra("aspectX", 1) // 宽度比
- putExtra("aspectY", 1) // 高度比
- putExtra("outputX", 150) // 裁剪框宽度
- putExtra("outputY", 150) // 裁剪框高度
- putExtra("scale", true) // 缩放
- putExtra("return-data", false) // true-返回缩略图-data,false-不返回-需要通过Uri
- putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()) // 保存的图片格式
- putExtra("noFaceDetection", true) // 取消人脸识别
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- putExtra(MediaStore.EXTRA_OUTPUT, mUploadImageUri)
- } else {
- val imgCropUri = Uri.fromFile(mUploadImageFile)
- putExtra(MediaStore.EXTRA_OUTPUT, imgCropUri)
- }
- }
- startActivityForResult(
- intent, REQUEST_CODE_CROP
- )
- }
- }
-
-
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
- if (resultCode == RESULT_OK) {
- if (requestCode == REQUEST_CODE_CAMERA) {//拍照回调
- workCropFun(photoUri)
- } else if (requestCode == REQUEST_CODE_CROP) {//裁剪回调
- setAvatar()
- }
- }
- }
- }

今天虽然遇到了不少问题,但是出现问题后调试的过程很安逸,基本找出原因和解决花费了将近3个小时才把完整的demo整理出来,花费这么久的时间有三点原因:
1.Android13适配的规则没有搞清楚就直接升级更换了新版本,导致请求和拒绝后一直出错。
2.CameraX新的api和录像权限没看完整导致这里来回折腾了很久,最后找到了官网api才解决。
3.对于AGP8.1.0使用不熟悉,导致刚开始配置依赖也浪费了一点时间。
“路漫漫其修远兮,吾将上下而求索”,后面会把整理出来的完整适配Android13的例子也放出来,还有关于AGP8.1.0配置依赖方式变更的也会整理,遇到问题进行求索的过程比结果更重要,只要有一颗战胜困难的心,相信问题最终都会解决,如果感兴趣的可以尝试一下,若有其他问题可以提出来一起讨论解决,共同学习,一起成长.
https://gitee.com/jackning_admin/camera-xdemo
关注我获取更多知识或者投稿
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。