使用Camera X遇到的坑_OnPause时没有释放相机导致回来时黑屏

前言:

         最近为了实现拍照方面的工作内容,思前想后决定使用Camera X作为这个工程的拍照API,原因主要有:1、API使用方面没有Camera V2 API所需的代码量大,虽然已经有过相关的工作经验,但想起其代码量,对比了一下Camera X的例子,还是不太想用。2、兼容性更好,而且提供的常用API基本满足要求,像对焦、闪光灯等常用API都有了,更适合作为Demo快速建立。

问题:

        在我根据Android developer中所介绍的例子进行了自己的demo搭建后,我对Camera X的适用已经有了一定的了解。但是发现OnPause之后再回来,预览画面就丢失了。于是我想到了两个可能:

1、PreviewView可能被销毁引起的问题。

2、和相机的生命周期有关,我可能没有使用好。

首先从第一个可能入手。试着在OnResume的时候调用如下代码:

        if (mPreview != null) {
            //为预览窗口添加surface通道
            mPreview.setSurfaceProvider(mPreviewView.getSurfaceProvider());
        }

        确实OnResume之后预览画面能重新出来,但却依然要等5秒钟。于是怀疑更可能和可能性2有关。

        按照之前Camera V2的经验,如果相机资源没释放导致一些异常,那么一般其他要调用相机的应用也会出问题。于是我先打开自己的demo,再打开微信扫一扫,发现扫一扫的预览也黑屏了5秒才出来,比较符合上述直觉和经验。于是在OnPause中调用unBindAll释放相机,OnResume的时候再重新初始化并设定预览窗,问题果然就解决了。

详细代码:

相机逻辑:

package com.example.cameraXDemo;

import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.Surface;
import android.view.View;
import android.webkit.MimeTypeMap;
import android.widget.Button;
import android.widget.ImageView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.core.AspectRatio;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraInfoUnavailableException;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCaptureException;
import androidx.camera.core.Preview;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.view.PreviewView;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;

import com.bumptech.glide.Glide;
import com.example.piccut.R;
import com.google.common.util.concurrent.ListenableFuture;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class CameraXDemoActivity_1 extends Activity implements LifecycleOwner {
    private ProcessCameraProvider mCameraPRrovider = null;
    private int mLensFacing = CameraSelector.LENS_FACING_BACK;
    private PreviewView mPreviewView;
    private LifecycleRegistry mLifecycleRegistry;
    /**拍照器**/
    private ImageCapture mImageCapture;
    private ExecutorService mTakePhotoExecutor;
    private Button mBtnTakePhoto;
    private ImageView mImagePhoto;
    private Preview mPreview;
    private Button mBtnFlashLight;


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //拍照专用线程,让它不要卡住主线程:
        mTakePhotoExecutor = Executors.newSingleThreadExecutor();
        //权限申请
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 1);
        }

        mLifecycleRegistry = new LifecycleRegistry(this);
        mLifecycleRegistry.markState(Lifecycle.State.CREATED);

        setContentView(R.layout.camera_x_demo);
        mBtnFlashLight = findViewById(R.id.btn_flash_light);
        mPreviewView = (PreviewView) findViewById(R.id.pv);
        mBtnTakePhoto = findViewById(R.id.btn_take_photo);
        mImagePhoto = findViewById(R.id.iv_photo);
        //拍照按钮
        mBtnTakePhoto.setOnClickListener(v -> {
            takePhoto(mImageCapture);
        });
        //闪光灯
        mBtnFlashLight.setOnClickListener(v -> {
            switch ((String) v.getTag()) {
                case "auto":
                    mImageCapture.setFlashMode(ImageCapture.FLASH_MODE_ON);
                    ((Button) v).setText("闪光灯:常开");
                    v.setTag("on");
                    break;
                case "on":
                    mImageCapture.setFlashMode(ImageCapture.FLASH_MODE_OFF);
                    ((Button) v).setText("闪光灯:关闭");
                    v.setTag("off");
                    break;
                default:
                    mImageCapture.setFlashMode(ImageCapture.FLASH_MODE_AUTO);
                    ((Button) v).setText("闪光灯:自动");
                    v.setTag("auto");
                    break;
            }
        });
    }

    @Override
    public void onConfigurationChanged(@NonNull Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        bindCameraUseCases(mCameraPRrovider, null);
    }

    @Override
    protected void onStart() {
        super.onStart();
        mLifecycleRegistry.markState(Lifecycle.State.STARTED);
    }

    @Override
    protected void onResume() {
        super.onResume();
        ListenableFuture<ProcessCameraProvider> processCameraProvider = ProcessCameraProvider.getInstance(this);
        //回来的时候要重新绑定一下:
        processCameraProvider.addListener(() -> {
            try {
                mCameraPRrovider = processCameraProvider.get();
                //绑定预览窗等
                bindCameraUseCases(mCameraPRrovider, null);
            } catch (ExecutionException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, getMainExecutor()); //只能在主线程
        mLifecycleRegistry.markState(Lifecycle.State.RESUMED);
    }

    @Override
    protected void onPause() {
        super.onPause();
        //出去的时候要释放相机资源
        mCameraPRrovider.unbindAll();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mLifecycleRegistry.markState(Lifecycle.State.DESTROYED);
        mTakePhotoExecutor.shutdown();
    }

    /** Returns true if the device has an available back camera. False otherwise */
    private boolean hasBackCamera() {
        try {
            return mCameraPRrovider.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA);
        } catch (CameraInfoUnavailableException e) {
            e.printStackTrace();
        }
        return false;
    }

    /** Returns true if the device has an available front camera. False otherwise */
    private boolean hasFrontCamera() {
        try {
            return mCameraPRrovider.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA);
        } catch (CameraInfoUnavailableException e) {
            e.printStackTrace();
        }
        return false;
    }

    private void bindCameraUseCases(ProcessCameraProvider cameraProvider, Surface surface) {
         int aspectRatio = AspectRatio.RATIO_4_3;
        //预览界面:
        mPreview = new Preview.Builder()
                .setTargetAspectRatio(aspectRatio)
                .setTargetRotation(mPreviewView.getDisplay().getRotation())
                .build();
        //为预览窗口添加surface通道
        mPreview.setSurfaceProvider(mPreviewView.getSurfaceProvider());
        //照片抓取:
        mImageCapture = new ImageCapture.Builder()
                .setTargetAspectRatio(aspectRatio)
                //低延迟拍照,反应速度会比较快
                .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
                .setTargetRotation(mPreviewView.getDisplay().getRotation())
                .setFlashMode(ImageCapture.FLASH_MODE_AUTO) //闪光灯调节
                .build();
        ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
                .setTargetAspectRatio(aspectRatio)
                .setTargetRotation(mPreviewView.getDisplay().getRotation())
                .build();
        //解绑之前可能存在的绑定关系
        cameraProvider.unbindAll();
        //有后置摄像头就选后置,否则用前置:
        if (hasBackCamera()) {
            mLensFacing = CameraSelector.LENS_FACING_BACK;
        } if (hasFrontCamera()) {
//            mLensFacing = CameraSelector.LENS_FACING_FRONT;
        } else {
            throw new IllegalStateException("前后摄像头都没");
        }
        CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(mLensFacing).build();

        //绑定生命周期、预览窗、拍照获取器等
        cameraProvider.bindToLifecycle(this,
                cameraSelector, mPreview, mImageCapture, imageAnalysis);
    }

    /**实际拍照逻辑**/
    private void takePhoto(ImageCapture imageCapture) {
        if (null == imageCapture) {
            Log.e("cjztest", "imageCapture is null");
            return;
        }
        String fileFormatPattern = "yyyy-MM-dd-HH-mm-ss-SSS";
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(fileFormatPattern);
        //先存放到APP本地文件夹:
//        File cacheFileDir = getCacheDir(); //APP内cache地址
        File cacheFileDir = getExternalCacheDir(); //共用的、大家都可以访问的cache地址
        if (cacheFileDir.exists() && cacheFileDir.isDirectory()) {
            File newFile = new File(cacheFileDir.getAbsolutePath() + String.format("/%s.jpg", simpleDateFormat.format(System.currentTimeMillis())));
            Log.i("cjztest", "newFile:" + newFile.getAbsolutePath());
            try {
                newFile.createNewFile();
                if (!newFile.exists()) {
                    return;
                }
                ImageCapture.OutputFileOptions outputOptions = new ImageCapture.OutputFileOptions.Builder(newFile)
                        .setMetadata(new ImageCapture.Metadata())
                        .build();
                //照片拍好之后从回调里面接收
                imageCapture.takePicture(outputOptions, mTakePhotoExecutor, new ImageCapture.OnImageSavedCallback() {

                    @Override
                    public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
                        Uri savedUri = outputFileResults.getSavedUri() == null ?
                                Uri.fromFile(newFile) :
                                outputFileResults.getSavedUri();
                        //给按钮弄个照片
                        runOnUiThread(() -> {
                            if (newFile.exists()) {
                                Glide.with(mImagePhoto)
                                        .load(newFile)
//                                .apply(RequestOptions.circleCropTransform())
                                        .into(mImagePhoto);
                            }
                        });
                        // We can only change the foreground Drawable using API level 23+ API
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                            // Update the gallery thumbnail with latest picture taken
//                            setGalleryThumbnail(savedUri);
                        }

                        // Implicit broadcasts will be ignored for devices running API level >= 24
                        // so if you only target API level 24+ you can remove this statement
                        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
                            sendBroadcast(
                                    new Intent(android.hardware.Camera.ACTION_NEW_PICTURE, savedUri)
                            );
                        }
                        // If the folder selected is an external media directory, this is
                        // unnecessary but otherwise other apps will not be able to access our
                        // images unless we scan them using [MediaScannerConnection]
                        String mimeType = MimeTypeMap.getSingleton()
                                .getMimeTypeFromExtension(".jpg");
                        MediaScannerConnection.scanFile(CameraXDemoActivity_1.this,
                                new String[] {savedUri.getPath()},
                                new String[] {mimeType},
                                new MediaScannerConnection.OnScanCompletedListener() {
                                    @Override
                                    public void onScanCompleted(String path, Uri uri) {
                                        Log.i("cjztest", "新文件扫描完成, 文件大小:" + newFile.length());
                                    }
                                });
                    }

                    @Override
                    public void onError(@NonNull ImageCaptureException exception) {
                        Log.e("cjztest", "拍照失败");
                    }
                });
            } catch (IOException e) {
                Log.e("cjztest", "创建文件失败");
                e.printStackTrace();
            }
        }
        Log.i("cjztest", "cacheFileDir:" + cacheFileDir);
    }

    @NonNull
    @Override
    public Lifecycle getLifecycle() {
        return mLifecycleRegistry;
    }
}

界面描述:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
    android:background="#FF000000">

    <Button
        android:id="@+id/btn_flash_light"
        android:text="闪光灯:自动"
        android:tag="auto"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right|top"/>

    <androidx.camera.view.PreviewView
        android:id="@+id/pv"
        android:layout_width="match_parent"
        android:layout_height="700dp"/>

    <ImageView
        android:id="@+id/iv_photo"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_gravity="bottom|left"
        android:scaleType="fitXY"/>

    <Button
        android:id="@+id/btn_take_photo"
        android:text="拍照"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_gravity="bottom|center"
        />

</FrameLayout>

Gitee地址:

lvlv/AndroidDemo大全 - Gitee.com

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
使用Camera2 API调用系统相机需要以下步骤: 1.添加权限到 AndroidManifest.xml 文件中: ```xml <uses-permission android:name="android.permission.CAMERA" /> ``` 2.在布局文件中添加一个 SurfaceView 用于预览相机图像: ```xml <SurfaceView android:id="@+id/surfaceView" android:layout_width="match_parent" android:layout_height="match_parent" /> ``` 3.在 Activity 中获取 SurfaceView 对象并创建 CameraDevice: ```java private CameraDevice cameraDevice; private CaptureRequest.Builder captureRequestBuilder; private SurfaceView surfaceView; private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) { cameraDevice = camera; createCameraPreview(); } @Override public void onDisconnected(CameraDevice camera) { cameraDevice.close(); } @Override public void onError(CameraDevice camera, int error) { cameraDevice.close(); cameraDevice = null; } }; private void openCamera() { CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); try { String cameraId = manager.getCameraIdList()[0]; CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); Size[] outputSizes = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) .getOutputSizes(SurfaceHolder.class); Size size = outputSizes[0]; manager.openCamera(cameraId, stateCallback, null); } catch (CameraAccessException e) { e.printStackTrace(); } } ``` 4.为 CameraDevice 创建预览会话: ```java private void createCameraPreview() { try { SurfaceHolder holder = surfaceView.getHolder(); holder.setFixedSize(size.getWidth(), size.getHeight()); holder.setKeepScreenOn(true); final Surface surface = holder.getSurface(); captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); captureRequestBuilder.addTarget(surface); cameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(CameraCaptureSession session) { try { CaptureRequest captureRequest = captureRequestBuilder.build(); session.setRepeatingRequest(captureRequest, null, null); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(CameraCaptureSession session) { Toast.makeText(MainActivity.this, "Failed to configure camera", Toast.LENGTH_SHORT).show(); } }, null); } catch (CameraAccessException e) { e.printStackTrace(); } } ``` 5.在 Activity 的 onResume() 方法中调用 openCamera() 方法: ```java @Override protected void onResume() { super.onResume(); openCamera(); } ``` 6.在 Activity 的 onPause() 方法中释放相机资源: ```java @Override protected void onPause() { super.onPause(); if (cameraDevice != null) { cameraDevice.close(); cameraDevice = null; } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值