Android CameraX适配15

Android CameraX适配15

1.前言:

最近10月初把公司的项目升级到了Android15,但是发现了很多问题,这这其中最恶心的是Android15EdgeToEdge适配 ,因为这个所有界面都要修改,之前的导航栏和状态栏都失效了,今天是讲解CameraX升级15的过程,直接上代码。

2.edge-to-edge全面屏:

在Android 15设备上,若应用程序的targetSDK版本大于等于Android 15,则必须 强制进行全屏展示,同时状态栏和导航栏将透明化处理。对于targetSDK版本小于Android 15的应用,其默认不会启用边到边特性,即用户层的View仍会保持在状态栏和导航栏之间。

此外,在Android 15平台上,先前用于设置系统栏颜色的API,如setNavigationBarColor等,将被弃用。即便使用这些方法进行设置,系统也将默认提供沉浸式体验。

简适配代码如下:

kotlin 复制代码
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_main)
        initPermission()
        initView()
        val window = window
        WindowCompat.setDecorFitsSystemWindows(window, false)
        ViewCompat.setOnApplyWindowInsetsListener(rootView) { v, insets ->
            val stateBars = insets.getInsets(WindowInsetsCompat.Type.statusBars())
            v.setPadding(stateBars.left, 0, stateBars.right, 0)
            insets
        }
    }

3.升级后报错:

打开系统拍照界面后返回,就直接崩溃了,崩溃信息如下。

kotlin 复制代码
                    Process: com.example.cameraxdemo, PID: 17531
                                                                                                java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=102, result=-1, data=null} to activity {com.example.cameraxdemo/com.example.cameraxdemo.activity.CameraActivity}: java.lang.IllegalArgumentException: Mutation of _data is not allowed.
                                                                                                	at android.app.ActivityThread.deliverResults(ActivityThread.java:6371)
                                                                                                	at android.app.ActivityThread.handleSendResult(ActivityThread.java:6410)
                                                                                                	at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:69)
                                                                                                	at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:60)
                                                                                                	at android.app.servertransaction.TransactionExecutor.executeNonLifecycleItem(TransactionExecutor.java:179)
                                                                                                	at android.app.servertransaction.TransactionExecutor.executeTransactionItems(TransactionExecutor.java:114)
                                                                                                	at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:86)
                                                                                                	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2914)
                                                                                                	at android.os.Handler.dispatchMessage(Handler.java:112)
                                                                                                	at android.os.Looper.loopOnce(Looper.java:288)
                                                                                                	at android.os.Looper.loop(Looper.java:393)
                                                                                                	at android.app.ActivityThread.main(ActivityThread.java:9564)
                                                                                                	at java.lang.reflect.Method.invoke(Native Method)
                                                                                                	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:600)
                                                                                                	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1010)
                                                                                                Caused by: java.lang.IllegalArgumentException: Mutation of _data is not allowed.
                                                                                                	at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:185)
                                                                                                	at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:155)
                                                                                                	at android.content.ContentProviderProxy.insert(ContentProviderNative.java:589)
                                                                                                	at android.content.ContentResolver.insert(ContentResolver.java:2241)
                                                                                                	at android.content.ContentResolver.insert(ContentResolver.java:2197)
                                                                                                	at com.example.cameraxdemo.utils.FileUtil.getHeadJpgFile(FileUtil.java:1427)
                                                                                                	at com.example.cameraxdemo.activity.CameraActivity.workCropFun(CameraActivity.kt:100)
                                                                                                	at com.example.cameraxdemo.activity.CameraActivity.onActivityResult(CameraActivity.kt:139)
                                                                                                	at android.app.Activity.onActivityResult(Activity.java:7666)
                                                                                                	at android.app.Activity.internalDispatchActivityResult(Activity.java:9588)
                                                                                                	at android.app.Activity.dispatchActivityResult(Activity.java:9565)
                                                                                                	at android.app.ActivityThread.deliverResults(ActivityThread.java:6360)
                                                                                                	at android.app.ActivityThread.handleSendResult(ActivityThread.java:6410) 
                                                                                                	at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:69) 
                                                                                                	at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:60) 
                                                                                                	at android.app.servertransaction.TransactionExecutor.executeNonLifecycleItem(TransactionExecutor.java:179) 
                                                                                                	at android.app.servertransaction.TransactionExecutor.executeTransactionItems(TransactionExecutor.java:114) 
                                                                                                	at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:86) 
                                                                                                	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2914) 
                                                                                                	at android.os.Handler.dispatchMessage(Handler.java:112) 
                                                                                                	at android.os.Looper.loopOnce(Looper.java:288) 
                                                                                                	at android.os.Looper.loop(Looper.java:393) 
                                                                                                	at android.app.ActivityThread.main(ActivityThread.java:9564) 
                                                                                                	at java.lang.reflect.Method.invoke(Native Method) 
                                                                                                	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:600) 
                                                                                                	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1010) 

4.报错的核心代码:

kotlin 复制代码
    public static Object getHeadJpgFile() {
        String fileName = System.currentTimeMillis() + FileManager.JPG_SUFFIX;
        String headPath = FileManager.getAvatarPath(fileName);
        File imgFile;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            imgFile = new File(headPath);
            // 通过 MediaStore API 插入file 为了拿到系统裁剪要保存到的uri(因为App没有权限不能访问公共存储空间,需要通过 MediaStore API来操作)
            ContentValues values = new ContentValues();
            values.put(MediaStore.Images.Media.DATA, imgFile.getAbsolutePath());
            values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
            values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
            return CameraApp.Companion.getMInstance().getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
        } else {
            imgFile = new File(headPath);
        }
        return imgFile;
    }

5.报错原因分析:

该错误的核心原因是尝试修改 Android 系统中受保护的 _data 字段_data 是媒体库(MediaStore)中存储文件路径的字段,Android 从 API 29(Android 10)开始限制直接修改该字段,强制要求使用 MediaStore 的规范 API 进行文件操作,禁止通过 ContentResolver 直接插入或修改 _data 字段,否则会抛出 IllegalArgumentException: Mutation of _data is not allowed 异常。

从报错堆栈看,问题出现在 FileUtil.getHeadJpgFile() 方法中通过 ContentResolver.insert() 操作媒体库时,试图设置 _data 字段的值,违反了系统限制。

6.解决方法:

6.1 直接去掉此属性设置:

kotlin 复制代码
    public static Object getHeadJpgFile() {
        String fileName = System.currentTimeMillis() + FileManager.JPG_SUFFIX;
        String headPath = FileManager.getAvatarPath(fileName);
        File imgFile;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            imgFile = new File(headPath);
            // 通过 MediaStore API 插入file 为了拿到系统裁剪要保存到的uri(因为App没有权限不能访问公共存储空间,需要通过 MediaStore API来操作)
            ContentValues values = new ContentValues();
//            values.put(MediaStore.Images.Media.DATA, imgFile.getAbsolutePath());
            values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
            values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
            return CameraApp.Companion.getMInstance().getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
        } else {
            imgFile = new File(headPath);
        }
        return imgFile;
    }

6.2 使用MediaStore的其他属性:

RELATIVE_PATH、DISPLAY_NAME等.

kotlin 复制代码
    /**
     * 获取jpg图片输出路径
     *
     * @return 输出路径
     */
    public static Object getHeadJpgFile() {
        String fileName = System.currentTimeMillis() + FileManager.JPG_SUFFIX;
        String headPath = FileManager.getAvatarPath(fileName);
        File imgFile;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            imgFile = new File(headPath);
            // 通过 MediaStore API 插入file 为了拿到系统裁剪要保存到的uri(因为App没有权限不能访问公共存储空间,需要通过 MediaStore API来操作)
            ContentValues values = new ContentValues();
            //方式1去掉Media.DATA
            //values.put(MediaStore.Images.Media.DATA, imgFile.getAbsolutePath());
            //方式2使用MediaStore。RELATIVE_PATH
            values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_DCIM + "/CustomFolder");
            values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
            values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
            return CameraApp.Companion.getMInstance().getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
        } else {
            imgFile = new File(headPath);
        }
        return imgFile;
    }public static Object getHeadJpgFile() {
    String fileName = System.currentTimeMillis() + FileManager.JPG_SUFFIX;
    String headPath = FileManager.getAvatarPath(fileName);
    File imgFile;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        imgFile = new File(headPath);
        // 通过 MediaStore API 插入file 为了拿到系统裁剪要保存到的uri(因为App没有权限不能访问公共存储空间,需要通过 MediaStore API来操作)
        ContentValues values = new ContentValues();
        values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_DCIM + "/CustomFolder");
        values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
        values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
        return CameraApp.Companion.getMInstance().getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
    } else {
        imgFile = new File(headPath);
    }
    return imgFile;
}

7.运行效果:




8.日志打印:

8.1 使用RELATIVE_PATH方式

8.2 去掉Media.DATA

9.完整测试代码:

kotlin 复制代码
package com.example.cameraxdemo

import android.Manifest
import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Color
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.view.View
import android.view.WindowManager
import android.webkit.MimeTypeMap
import android.widget.Button
import androidx.activity.enableEdgeToEdge
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 androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
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
    @SuppressLint("RestrictedApi")
    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) }
    private val rootView : View by lazy {findViewById(R.id.main_root) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_main)
        initPermission()
        initView()
        val window = window
        WindowCompat.setDecorFitsSystemWindows(window, false)
        ViewCompat.setOnApplyWindowInsetsListener(rootView) { v, insets ->
            val stateBars = insets.getInsets(WindowInsetsCompat.Type.statusBars())
            v.setPadding(stateBars.left, 0, stateBars.right, 0)
            insets
        }
    }

    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
                        if(BuildConfig.DEBUG){
                            LogUtils.d(TAG, "===视频保存的地址为=== ${file.absolutePath}")
                        }
                        //保存视频成功回调,会在停止录制时被调用
                        ToastUtils.shortToast(" 录像成功 $file")
                    }

                    override fun onError(
                        videoCaptureError: Int,
                        message: String,
                        cause: Throwable?
                    ) {
                        //保存失败的回调,可能在开始或结束录制时被调用
                        isRecordVideo = false
                        if(BuildConfig.DEBUG) {
                            LogUtils.e(TAG, "onError: $message")
                        }
                        ToastUtils.shortToast(" 录像失败 $message")
                    }
                })
        } catch (e: Exception) {
            e.printStackTrace()
            if(BuildConfig.DEBUG) {
                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) {
                if(BuildConfig.DEBUG) {
                    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()
    }
}
kotlin 复制代码
package com.example.cameraxdemo.activity

import android.annotation.SuppressLint
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.view.View
import android.widget.Button
import android.widget.ImageView
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
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
    private val rootView : View by lazy {findViewById(R.id.camera_root) }

    @SuppressLint("RestrictedApi")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_camera)
        initView()
        ViewCompat.setOnApplyWindowInsetsListener(rootView) { v, insets ->
            val stateBars = insets.getInsets(WindowInsetsCompat.Type.statusBars())
            v.setPadding(stateBars.left, stateBars.top, stateBars.right, stateBars.bottom)
            insets
        }
    }

    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", 1000) // 裁剪框宽度
                putExtra("outputY", 1000) // 裁剪框高度
                putExtra("scale", true) // 缩放
                putExtra("return-data", false) // true-返回缩略图-data,false-不返回-需要通过Uri
                putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()) // 保存的图片格式
                putExtra("noFaceDetection", true) // 取消人脸识别
                putExtra("quality", 100)
                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()
            }
        }
    }
}

10.总结:

升级Android15后需要适配EdgeToEdge和MediaStore.Images.Media.DATA, 找到报错,分析出原因,解决即可.当然15还有其他适配,这里只是在跑之前的项目遇到的问题顺手讲解一下而已,后面会重点讲解全面屏适配.

11.源码地址:

https://gitee.com/jackning_admin/camera-xdemo

相关推荐
s***41131 小时前
MySQL——表操作及查询
android·mysql·adb
村里小码农1 小时前
Android 按键拦截
android·按键拦截·keycode·keyevent
我命由我123451 小时前
Android 开发问题:布局文件中的文本,在预览时有显示出来,但是,在应用中没有显示出来
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
e***28291 小时前
【细如狗】记录一次使用MySQL的Binlog进行数据回滚的完整流程
android·数据库·mysql
安卓兼职framework应用工程师1 小时前
Android 15.0修改recovery 菜单项字体大小
android·recovery·菜单项·菜单项字体大小
东坡肘子1 小时前
当 Android 手机『强行兼容』AirDrop -- 肘子的 Swift 周报 #113
android·swiftui·swift
人民的石头1 小时前
android 设备实现回声消除方案
android
d***9351 小时前
【玩转全栈】----Django连接MySQL
android·mysql·django
Frank_HarmonyOS1 小时前
如何在Android中使用RemoteCallbackList?
android