前言:相机应用预览界面切换到后台后,通常会释放相机资源,这样其他应用能够及时打开摄像头。我的做法是在Acitivity生命周期的onStart()时打开摄像头,在onStop()释放摄像头资源。这样做存在一个小问题,预览界面切换后台再切前台,此时预览画面卡住。
原因:onStop()时,SurfaceView或TextureView没有销毁,所以重新打开摄像头时,view的生命周期不会重新执行,也就无法刷新。
解决方法:SurfaceView或TextureView通过new实例化,而不是在布局中定义。并且在onStart() 通过addview方式把view加到布局中,在onStop()时通过removeView()方式移除view。
具体实现,布局定义layout/activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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" >
<LinearLayout
android:id="@+id/preview_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- add or remove textureview on activity-->
</LinearLayout>
<!-- others view -->
</RelativeLayout>
Activity中
private TextureView textureView;
private LinearLayout textureViewParent;
private Camera camera;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textureViewParent = findViewById(R.id.preview_parent);
textureView = new TextureView(this);
LinearLayout.LayoutParams lp = new
LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
textureView.setLayoutParams(lp);
}
@Override
protected void onStart() {
super.onStart();
textureViewParent.addView(textureView);
openCamera(textureView, 0);
}
@Override
protected void onStop() {
super.onStop();
textureViewParent.removeView(textureView);
if (camera != null) {
camera.stopPreview();
camera.setPreviewCallback(null);
camera.release();
}
}
private void openCamera(final TextureView textureView, final int cameraId) {
if (textureView == null || cameraId < 0) {
return;
}
textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
try {
camera = Camera.open(cameraId);
camera.setPreviewTexture(surfaceTexture);
camera.startPreview();
} catch (IOException localIOException) {
Log.e(TAG, "onSurfaceTextureAvailable open camera localIOException cameraId=" + cameraId + ", error=" + localIOException.getMessage(), localIOException);
} catch (Exception e) {
Log.e(TAG, "onSurfaceTextureAvailable open camera cameraId=" + cameraId + ", error=" + e.getMessage(), e);
}
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
}
});
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2021.4.14 方法二:参照谷歌的做法,不需要每次都添加和移除textureview。
具体实现,布局定义layout/activity_main2.xml:
<?xml version="1.0" encoding="utf-8"?>
<TextureView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/textureView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
MainActivity2中
// package ...
import android.app.Activity;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.os.Bundle;
import android.util.Log;
import android.view.Surface;
import android.view.TextureView;
import androidx.appcompat.app.AppCompatActivity;
import com.zzh.camera2.R;
import java.io.IOException;
public class MainActivity2 extends AppCompatActivity {
private final static String TAG = MainActivity2.class.getSimpleName();
private TextureView textureView;
private Camera camera;
private final int cameraId = 0;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
textureView = findViewById(R.id.textureView);
}
@Override
protected void onResume() {
super.onResume();
if (textureView.isAvailable())
openCamera(textureView.getSurfaceTexture(), cameraId);
else
textureView.setSurfaceTextureListener(surfaceTextureListener);
}
@Override
protected void onPause() {
super.onPause();
if (camera != null) {
camera.stopPreview();
camera.setPreviewCallback(null);
camera.release();
}
}
private void openCamera(SurfaceTexture surfaceTexture, final int cameraId) {
try {
camera = Camera.open(cameraId);
assert camera != null;
camera.setDisplayOrientation(getCameraDisplayOrientation(this, cameraId));
camera.setPreviewTexture(surfaceTexture);
camera.startPreview();
} catch (IOException localIOException) {
Log.e(TAG, "onSurfaceTextureAvailable open camera localIOException cameraId=" + cameraId + ", error=" + localIOException.getMessage(), localIOException);
} catch (Exception e) {
Log.e(TAG, "onSurfaceTextureAvailable open camera cameraId=" + cameraId + ", error=" + e.getMessage(), e);
}
}
private final TextureView.SurfaceTextureListener surfaceTextureListener = new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
openCamera(surfaceTexture, cameraId);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
}
};
// add by zzh
public static int getCameraDisplayOrientation(Activity activity, int cameraId) {
if (activity == null) {
Log.e(TAG, "setCameraDisplayOrientation failed activity is null");
return 0;
}
android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
android.hardware.Camera.getCameraInfo(cameraId, info);
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
int degrees = 0;
switch (rotation) {
case Surface.ROTATION_0:
degrees = 0;
break;
case Surface.ROTATION_90:
degrees = 90;
break;
case Surface.ROTATION_180:
degrees = 180;
break;
case Surface.ROTATION_270:
degrees = 270;
break;
}
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360; // compensate the mirror
} else { // back-facing
result = (info.orientation - degrees + 360) % 360;
}
Log.i(TAG, "setCameraDisplayOrientation result=" + result);
return result;
}
}
--------------------------------------------------------------------------------------------------------------------------------
2021.08.31补充
经自己验证,在某些平台上,相机打开后(使用camera api2 同时打开两个摄像头),按Home键退出,再打开应用,此时会出现相机打开错误,于是改成第一种方式,则不会出现此问题。所以说如果使用第二种方式会出现打开相机错误,则第一种方法可能解决问题。