Android Camera2 应用程序在离开相机 activity 并重新进入后中断
Android Camera2 app breaks after leaving camera activity and re-entering
我正在尝试根据此处的教程实现一个简单的相机照片捕捉应用程序:https://android.jlelse.eu/the-least-you-can-do-with-camera2-api-2971c8c81b8b
该应用程序第一次运行时效果很好。预览显示,我可以拍照保存正确
我的问题是,当我关闭应用程序、返回、最小化应用程序等...然后 return 到相机 activity 时,预览不再显示任何内容,并且当我尝试拍照时,应用程序崩溃了。
我几乎可以肯定我没有以某种方式正确关闭相机,但与工作实施相比,我逐行梳理了我的代码,但我看不出我在做什么我在做 differently/wrong.
package com.example.cameratest;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Size;
import android.view.Display;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Toast;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.Locale;
public class SecondCamera extends AppCompatActivity {
// Define the variables we need to use the camera
// Our request code can be anything. I like 8.
private static final int CAMERA_REQUEST_CODE = 8;
private CameraManager cameraManager;
private int cameraFacing;
private TextureView.SurfaceTextureListener surfaceTextureListener;
private String cameraId;
private Size previewSize;
private CameraDevice cameraDevice;
private TextureView textureView;
private CameraCaptureSession cameraCaptureSession;
private HandlerThread backgroundThread;
private Handler backgroundHandler;
private CameraDevice.StateCallback stateCallback;
private CaptureRequest.Builder captureRequestBuilder;
private CaptureRequest captureRequest;
private File galleryFolder;
private WindowManager windowManager;
private int windowHeight;
private int windowWidth;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second_camera);
// Get the size of our display in order to properly scale the camera view
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
windowHeight = size.y;
windowWidth = size.x;
textureView = (TextureView) findViewById(R.id.cameraTextureView);
// Let's ask for permission to use the camera feature
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, CAMERA_REQUEST_CODE);
// Get the camera system service
cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
// Make sure we use the back camera
cameraFacing = CameraCharacteristics.LENS_FACING_BACK;
// Set up a listener to communicate to our TextureView
surfaceTextureListener = new TextureView.SurfaceTextureListener(){
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
setUpCamera();
openCamera();
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
}
};
textureView.setSurfaceTextureListener(surfaceTextureListener);
// Manage the three states of our CameraDevice. Opened, Closed, and Error
stateCallback = new CameraDevice.StateCallback(){
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
// Grab our camera device and start up the preview
SecondCamera.this.cameraDevice = cameraDevice;
createPreviewSession();
createImageGallery();
}
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
// Close and disconnect the cameraDevice
cameraDevice.close();
SecondCamera.this.cameraDevice = null;
}
@Override
public void onError(@NonNull CameraDevice cameraDevice, int i) {
// Close and disconnect the cameraDevice
cameraDevice.close();
SecondCamera.this.cameraDevice = null;
}
};
}
@Override
protected void onResume(){
super.onResume();
openBackgroundThread();
// If our texture view is available, let's set up and open the camera on it.
if(textureView.isAvailable()){
setUpCamera();
openCamera();
} else {
// If not, we need to set up the SurfaceTextureListener,
// which will do the same once the texture view is available
textureView.setSurfaceTextureListener(surfaceTextureListener);
}
}
@Override
protected void onPause(){
// Close the Camera and Background Thread to avoid memory leakage
closeBackgroundThread();
closeCamera();
super.onPause();
}
/**
* Close the CameraCaptureSession and CameraDevice
*/
private void closeCamera(){
if (cameraCaptureSession != null){
cameraCaptureSession.close();
cameraCaptureSession = null;
}
if (cameraDevice != null){
cameraDevice.close();
cameraDevice = null;
}
}
/**
* Shut down our backgroundThread and handler
*/
private void closeBackgroundThread(){
backgroundThread.quitSafely();
try {
backgroundThread.join();
backgroundThread = null;
backgroundHandler = null;
} catch (InterruptedException ie){
ie.printStackTrace();
}
}
/**
* Open a thread in the background in order to run the camera
*/
private void openBackgroundThread(){
backgroundThread = new HandlerThread("Camera Background Thread");
backgroundThread.start();
backgroundHandler = new Handler(backgroundThread.getLooper());
}
/**
* Establish the camera to use, and get information to scale our preview correctly
*/
private void setUpCamera(){
try{
for (String cameraID: cameraManager.getCameraIdList()){
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraID);
// If we find the appropriate camera:
if (characteristics.get(CameraCharacteristics.LENS_FACING) == cameraFacing){
// Get the preview size we need, and set this class's camera ID
StreamConfigurationMap streamConfigurationMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
// The first element in the list of output sizes is the highest resolution one.
// Get a Size object which is prepared for a SurfaceTexture.
Size[] possibleSizes = streamConfigurationMap.getOutputSizes(SurfaceTexture.class);
previewSize = chooseOptimalSize(possibleSizes, windowWidth, windowHeight);
this.cameraId = cameraID;
}
}
if (this.cameraId == null){
Toast.makeText(this, "ERROR, NO CAMERA FOUND", Toast.LENGTH_SHORT).show();
}
} catch (CameraAccessException cae){
cae.printStackTrace();
}
}
/**
* Attempt to open the camera in the background thread
* if we find that we have been granted permission.
*/
private void openCamera(){
try {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED){
cameraManager.openCamera(cameraId, stateCallback, backgroundHandler);
}
else {
Toast.makeText(this, "Camera Permissions must be enabled for this activity to function", Toast.LENGTH_SHORT).show();
}
} catch (CameraAccessException cae){
cae.printStackTrace();
}
}
/**
* Initialize a preview for our camera screen, so the user can
* see in real time what the camera sees.
*/
private void createPreviewSession(){
try {
// Get the surfaceTexture out of our textureView. This is what we stream to
SurfaceTexture surfaceTexture = textureView.getSurfaceTexture();
// We want to set our size correctly based on the preview Size from before
surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
// Set up a Surface based on our Surface Texture
Surface previewSurface = new Surface(surfaceTexture);
// We want to build a request for a preview style stream, and send it to our surface
captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
captureRequestBuilder.addTarget(previewSurface);
cameraDevice.createCaptureSession(Collections.singletonList(previewSurface), new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
if (cameraDevice != null){
try {
captureRequest = captureRequestBuilder.build();
SecondCamera.this.cameraCaptureSession = cameraCaptureSession;
SecondCamera.this.cameraCaptureSession.setRepeatingRequest(captureRequest, null, backgroundHandler);
} catch (CameraAccessException cae) {
cae.printStackTrace();
}
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
// Do nothing?
}
}, backgroundHandler);
} catch (CameraAccessException cae){
cae.printStackTrace();
}
}
/**
* Initialize a folder in our pictures library to save photos to
*/
private void createImageGallery(){
File storageDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
galleryFolder = new File(storageDirectory, getResources().getString(R.string.app_name));
if (!galleryFolder.exists()){
boolean wasCreated = galleryFolder.mkdirs();
if (!wasCreated){
System.out.println("Failed to create directory " + galleryFolder.getPath());
}
}
}
/**
* Create a temp file to store our image to
*/
private File createImageFile(File galleryFolder) throws IOException {
// Grab our timestamp and use it to create a unique image name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmsss", Locale.getDefault()).format(new Date());
String imageFilename = getResources().getString(R.string.app_name) + timeStamp;
Toast.makeText(this, "Created image " + imageFilename + ".jpg", Toast.LENGTH_SHORT).show();
return File.createTempFile(imageFilename, ".jpg", galleryFolder);
}
/**
* Capture everything on the screen, and output it to our file
*/
public void takePhoto(View view){
try (FileOutputStream outputPhoto = new FileOutputStream(createImageFile(galleryFolder))) {
lock();
textureView.getBitmap().compress(Bitmap.CompressFormat.PNG, 100, outputPhoto);
} catch (IOException ioe) {
ioe.printStackTrace();
} finally {
unlock();
}
}
/**
* Lock our camera preview, as if the shutter of a camera
*/
private void lock(){
try {
// Lock the screen for a second
cameraCaptureSession.capture(captureRequestBuilder.build(), null, backgroundHandler);
} catch (CameraAccessException cae){
cae.printStackTrace();
}
}
/**
* Unlock our camera preview, allowing the user to see freely again
*/
private void unlock(){
try {
// Go back to the repeating preview request
cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(),
null, backgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* Do some calculations to figure out how to show the correct
* aspect ratio for our camera preview
*
* @param outputSizes The array of possible output sizes
* @param width The width of our device screen
* @param height The height of our device screen
* @return The size to set our display to
*/
private Size chooseOptimalSize(Size[] outputSizes, int width, int height) {
double preferredRatio = height / (double) width;
Size currentOptimalSize = outputSizes[0];
double currentOptimalRatio = currentOptimalSize.getWidth() / (double) currentOptimalSize.getHeight();
for (Size currentSize : outputSizes) {
double currentRatio = currentSize.getWidth() / (double) currentSize.getHeight();
if (Math.abs(preferredRatio - currentRatio) <
Math.abs(preferredRatio - currentOptimalRatio)) {
currentOptimalSize = currentSize;
currentOptimalRatio = currentRatio;
}
}
return currentOptimalSize;
}
}
在崩溃日志中,我看到:
E/CameraDeviceState: Cannot call configure while in state: 0
E/AndroidRuntime: FATAL EXCEPTION: Camera Background Thread
Process: com.example.cameratest, PID: 21313
java.lang.IllegalStateException: Session has been closed; further changes are illegal.
at android.hardware.camera2.impl.CameraCaptureSessionImpl.checkNotClosed(CameraCaptureSessionImpl.java:627)
at android.hardware.camera2.impl.CameraCaptureSessionImpl.setRepeatingRequest(CameraCaptureSessionImpl.java:234)
at com.example.cameratest.SecondCamera.onConfigured(SecondCamera.java:263)
at java.lang.reflect.Method.invoke(Native Method)
at android.hardware.camera2.dispatch.InvokeDispatcher.dispatch(InvokeDispatcher.java:39)
at android.hardware.camera2.dispatch.HandlerDispatcher.run(HandlerDispatcher.java:65)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.os.HandlerThread.run(HandlerThread.java:61)
这让我现在相信我的 CameraCaputreSession 有问题,但我不知道是什么。
编辑
换行
cameraManager.openCamera(cameraId, stateCallback, backgroundHandler);
在我的 openCamera() 方法中传递 null 作为第三个参数而不是我的 backgroundHandler 似乎已经修复了它,但老实说,我完全不知道为什么。谁能给我解释一下?这是正确的解决方案吗?
一种可能 - 您在 onResume() 中启动后台线程,但您也尝试在 TextureViewListener 的 onSurfaceTextureAvailable 中打开相机。
在第一次启动时,您只在 onResume 中设置后台线程后连接 TextureViewListener,但您永远不会注销该侦听器。第二次启动时,onSurfaceTextureAvailable 回调可能在 onResume() 之前 运行,因为应用 UI 在 onStart() 中可见,而不是在 onResume() 中可见。
这将使用空后台线程(转到主线程)打开相机,然后 onResume 将触发,并再次打开相机,从 onSurfaceTextureAvailable 驱逐第一个相机对象。对第一个相机对象进行进一步操作时导致错误。
一个解决方法是在 onPaused 中取消注册 TextureViewListener,或者重组事物,以便尽早获得 onSurfaceTextureAvailable 回调并设置后台线程。
我正在尝试根据此处的教程实现一个简单的相机照片捕捉应用程序:https://android.jlelse.eu/the-least-you-can-do-with-camera2-api-2971c8c81b8b
该应用程序第一次运行时效果很好。预览显示,我可以拍照保存正确
我的问题是,当我关闭应用程序、返回、最小化应用程序等...然后 return 到相机 activity 时,预览不再显示任何内容,并且当我尝试拍照时,应用程序崩溃了。
我几乎可以肯定我没有以某种方式正确关闭相机,但与工作实施相比,我逐行梳理了我的代码,但我看不出我在做什么我在做 differently/wrong.
package com.example.cameratest;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Size;
import android.view.Display;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Toast;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.Locale;
public class SecondCamera extends AppCompatActivity {
// Define the variables we need to use the camera
// Our request code can be anything. I like 8.
private static final int CAMERA_REQUEST_CODE = 8;
private CameraManager cameraManager;
private int cameraFacing;
private TextureView.SurfaceTextureListener surfaceTextureListener;
private String cameraId;
private Size previewSize;
private CameraDevice cameraDevice;
private TextureView textureView;
private CameraCaptureSession cameraCaptureSession;
private HandlerThread backgroundThread;
private Handler backgroundHandler;
private CameraDevice.StateCallback stateCallback;
private CaptureRequest.Builder captureRequestBuilder;
private CaptureRequest captureRequest;
private File galleryFolder;
private WindowManager windowManager;
private int windowHeight;
private int windowWidth;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second_camera);
// Get the size of our display in order to properly scale the camera view
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
windowHeight = size.y;
windowWidth = size.x;
textureView = (TextureView) findViewById(R.id.cameraTextureView);
// Let's ask for permission to use the camera feature
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, CAMERA_REQUEST_CODE);
// Get the camera system service
cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
// Make sure we use the back camera
cameraFacing = CameraCharacteristics.LENS_FACING_BACK;
// Set up a listener to communicate to our TextureView
surfaceTextureListener = new TextureView.SurfaceTextureListener(){
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
setUpCamera();
openCamera();
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
}
};
textureView.setSurfaceTextureListener(surfaceTextureListener);
// Manage the three states of our CameraDevice. Opened, Closed, and Error
stateCallback = new CameraDevice.StateCallback(){
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
// Grab our camera device and start up the preview
SecondCamera.this.cameraDevice = cameraDevice;
createPreviewSession();
createImageGallery();
}
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
// Close and disconnect the cameraDevice
cameraDevice.close();
SecondCamera.this.cameraDevice = null;
}
@Override
public void onError(@NonNull CameraDevice cameraDevice, int i) {
// Close and disconnect the cameraDevice
cameraDevice.close();
SecondCamera.this.cameraDevice = null;
}
};
}
@Override
protected void onResume(){
super.onResume();
openBackgroundThread();
// If our texture view is available, let's set up and open the camera on it.
if(textureView.isAvailable()){
setUpCamera();
openCamera();
} else {
// If not, we need to set up the SurfaceTextureListener,
// which will do the same once the texture view is available
textureView.setSurfaceTextureListener(surfaceTextureListener);
}
}
@Override
protected void onPause(){
// Close the Camera and Background Thread to avoid memory leakage
closeBackgroundThread();
closeCamera();
super.onPause();
}
/**
* Close the CameraCaptureSession and CameraDevice
*/
private void closeCamera(){
if (cameraCaptureSession != null){
cameraCaptureSession.close();
cameraCaptureSession = null;
}
if (cameraDevice != null){
cameraDevice.close();
cameraDevice = null;
}
}
/**
* Shut down our backgroundThread and handler
*/
private void closeBackgroundThread(){
backgroundThread.quitSafely();
try {
backgroundThread.join();
backgroundThread = null;
backgroundHandler = null;
} catch (InterruptedException ie){
ie.printStackTrace();
}
}
/**
* Open a thread in the background in order to run the camera
*/
private void openBackgroundThread(){
backgroundThread = new HandlerThread("Camera Background Thread");
backgroundThread.start();
backgroundHandler = new Handler(backgroundThread.getLooper());
}
/**
* Establish the camera to use, and get information to scale our preview correctly
*/
private void setUpCamera(){
try{
for (String cameraID: cameraManager.getCameraIdList()){
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraID);
// If we find the appropriate camera:
if (characteristics.get(CameraCharacteristics.LENS_FACING) == cameraFacing){
// Get the preview size we need, and set this class's camera ID
StreamConfigurationMap streamConfigurationMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
// The first element in the list of output sizes is the highest resolution one.
// Get a Size object which is prepared for a SurfaceTexture.
Size[] possibleSizes = streamConfigurationMap.getOutputSizes(SurfaceTexture.class);
previewSize = chooseOptimalSize(possibleSizes, windowWidth, windowHeight);
this.cameraId = cameraID;
}
}
if (this.cameraId == null){
Toast.makeText(this, "ERROR, NO CAMERA FOUND", Toast.LENGTH_SHORT).show();
}
} catch (CameraAccessException cae){
cae.printStackTrace();
}
}
/**
* Attempt to open the camera in the background thread
* if we find that we have been granted permission.
*/
private void openCamera(){
try {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED){
cameraManager.openCamera(cameraId, stateCallback, backgroundHandler);
}
else {
Toast.makeText(this, "Camera Permissions must be enabled for this activity to function", Toast.LENGTH_SHORT).show();
}
} catch (CameraAccessException cae){
cae.printStackTrace();
}
}
/**
* Initialize a preview for our camera screen, so the user can
* see in real time what the camera sees.
*/
private void createPreviewSession(){
try {
// Get the surfaceTexture out of our textureView. This is what we stream to
SurfaceTexture surfaceTexture = textureView.getSurfaceTexture();
// We want to set our size correctly based on the preview Size from before
surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
// Set up a Surface based on our Surface Texture
Surface previewSurface = new Surface(surfaceTexture);
// We want to build a request for a preview style stream, and send it to our surface
captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
captureRequestBuilder.addTarget(previewSurface);
cameraDevice.createCaptureSession(Collections.singletonList(previewSurface), new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
if (cameraDevice != null){
try {
captureRequest = captureRequestBuilder.build();
SecondCamera.this.cameraCaptureSession = cameraCaptureSession;
SecondCamera.this.cameraCaptureSession.setRepeatingRequest(captureRequest, null, backgroundHandler);
} catch (CameraAccessException cae) {
cae.printStackTrace();
}
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
// Do nothing?
}
}, backgroundHandler);
} catch (CameraAccessException cae){
cae.printStackTrace();
}
}
/**
* Initialize a folder in our pictures library to save photos to
*/
private void createImageGallery(){
File storageDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
galleryFolder = new File(storageDirectory, getResources().getString(R.string.app_name));
if (!galleryFolder.exists()){
boolean wasCreated = galleryFolder.mkdirs();
if (!wasCreated){
System.out.println("Failed to create directory " + galleryFolder.getPath());
}
}
}
/**
* Create a temp file to store our image to
*/
private File createImageFile(File galleryFolder) throws IOException {
// Grab our timestamp and use it to create a unique image name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmsss", Locale.getDefault()).format(new Date());
String imageFilename = getResources().getString(R.string.app_name) + timeStamp;
Toast.makeText(this, "Created image " + imageFilename + ".jpg", Toast.LENGTH_SHORT).show();
return File.createTempFile(imageFilename, ".jpg", galleryFolder);
}
/**
* Capture everything on the screen, and output it to our file
*/
public void takePhoto(View view){
try (FileOutputStream outputPhoto = new FileOutputStream(createImageFile(galleryFolder))) {
lock();
textureView.getBitmap().compress(Bitmap.CompressFormat.PNG, 100, outputPhoto);
} catch (IOException ioe) {
ioe.printStackTrace();
} finally {
unlock();
}
}
/**
* Lock our camera preview, as if the shutter of a camera
*/
private void lock(){
try {
// Lock the screen for a second
cameraCaptureSession.capture(captureRequestBuilder.build(), null, backgroundHandler);
} catch (CameraAccessException cae){
cae.printStackTrace();
}
}
/**
* Unlock our camera preview, allowing the user to see freely again
*/
private void unlock(){
try {
// Go back to the repeating preview request
cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(),
null, backgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* Do some calculations to figure out how to show the correct
* aspect ratio for our camera preview
*
* @param outputSizes The array of possible output sizes
* @param width The width of our device screen
* @param height The height of our device screen
* @return The size to set our display to
*/
private Size chooseOptimalSize(Size[] outputSizes, int width, int height) {
double preferredRatio = height / (double) width;
Size currentOptimalSize = outputSizes[0];
double currentOptimalRatio = currentOptimalSize.getWidth() / (double) currentOptimalSize.getHeight();
for (Size currentSize : outputSizes) {
double currentRatio = currentSize.getWidth() / (double) currentSize.getHeight();
if (Math.abs(preferredRatio - currentRatio) <
Math.abs(preferredRatio - currentOptimalRatio)) {
currentOptimalSize = currentSize;
currentOptimalRatio = currentRatio;
}
}
return currentOptimalSize;
}
}
在崩溃日志中,我看到:
E/CameraDeviceState: Cannot call configure while in state: 0
E/AndroidRuntime: FATAL EXCEPTION: Camera Background Thread
Process: com.example.cameratest, PID: 21313
java.lang.IllegalStateException: Session has been closed; further changes are illegal.
at android.hardware.camera2.impl.CameraCaptureSessionImpl.checkNotClosed(CameraCaptureSessionImpl.java:627)
at android.hardware.camera2.impl.CameraCaptureSessionImpl.setRepeatingRequest(CameraCaptureSessionImpl.java:234)
at com.example.cameratest.SecondCamera.onConfigured(SecondCamera.java:263)
at java.lang.reflect.Method.invoke(Native Method)
at android.hardware.camera2.dispatch.InvokeDispatcher.dispatch(InvokeDispatcher.java:39)
at android.hardware.camera2.dispatch.HandlerDispatcher.run(HandlerDispatcher.java:65)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.os.HandlerThread.run(HandlerThread.java:61)
这让我现在相信我的 CameraCaputreSession 有问题,但我不知道是什么。
编辑
换行
cameraManager.openCamera(cameraId, stateCallback, backgroundHandler);
在我的 openCamera() 方法中传递 null 作为第三个参数而不是我的 backgroundHandler 似乎已经修复了它,但老实说,我完全不知道为什么。谁能给我解释一下?这是正确的解决方案吗?
一种可能 - 您在 onResume() 中启动后台线程,但您也尝试在 TextureViewListener 的 onSurfaceTextureAvailable 中打开相机。
在第一次启动时,您只在 onResume 中设置后台线程后连接 TextureViewListener,但您永远不会注销该侦听器。第二次启动时,onSurfaceTextureAvailable 回调可能在 onResume() 之前 运行,因为应用 UI 在 onStart() 中可见,而不是在 onResume() 中可见。
这将使用空后台线程(转到主线程)打开相机,然后 onResume 将触发,并再次打开相机,从 onSurfaceTextureAvailable 驱逐第一个相机对象。对第一个相机对象进行进一步操作时导致错误。
一个解决方法是在 onPaused 中取消注册 TextureViewListener,或者重组事物,以便尽早获得 onSurfaceTextureAvailable 回调并设置后台线程。