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

bas365 admin 2025-08-10 01:38:13 阅读 9119

前言:

最近为了实现拍照方面的工作内容,思前想后决定使用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.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;

}

}

界面描述:

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">

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"/>

android:id="@+id/pv"

android:layout_width="match_parent"

android:layout_height="700dp"/>

android:id="@+id/iv_photo"

android:layout_width="100dp"

android:layout_height="100dp"

android:layout_gravity="bottom|left"

android:scaleType="fitXY"/>

android:id="@+id/btn_take_photo"

android:text="拍照"

android:layout_width="100dp"

android:layout_height="100dp"

android:layout_gravity="bottom|center"

/>

Gitee地址:

lvlv/AndroidDemo大全 - Gitee.com

相关文章