public class MainActivity extends AppCompatActivity {
private static final String TAG = "camera2api";
private static final int REQUEST_CAMERA_PERMISSION = 100;
private TextureView textureView;
private Button captureButton;
private CameraDevice cameraDevice;
private CameraCaptureSession cameraCaptureSession;
private CaptureRequest.Builder captureRequestBuilder;
private String cameraId;
private Handler backgroundHandler;
private boolean isSessionClosed;
private HandlerThread backgroundThread;
private ImageReader imageReader;
private boolean isTextureAvailable = false; // 跟踪Surface状态
private CameraManager manager;
private volatile boolean isCapturing = false;
private StreamConfigurationMap map;
private long lastClickTime = 0;
private static final long MIN_CLICK_INTERVAL = 1000; // 最小点击间隔1秒
private ContentResolver resolver;
private ContentValues values;
private Uri imageUri;
private Surface previewSurface;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "onCreate: Activity创建————————");
textureView = findViewById(R.id.textureView);
captureButton = findViewById(R.id.btnCapture);
//初始化相机参数
initCamera();
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume——————————");
// 检查相机权限
// ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA):用于检测this(当前应用)是否有Manifest.permission.CAMERA权限
//有则返回PackageManager.PERMISSION_GRANTED(有权限),没有则返回PackageManager.PERMISSION_DENIED(无权限)
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "没有相机权限——>开始请求相机权限");
//ActivityCompat.requestPermissions():请求权限的方法
//this:当前应用
//new String[]{权限1,权限2,...}:请求的权限集合
//code:对应编码名,发送请求后会调用回调函数 onRequestPermissionsResult(),该回调的第一个参数就是这个code,用于给我们做请求权限后的自定义操作。
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);
}
//注意:以上的权限请求是异步操作
//设置预览监听
textureView.setSurfaceTextureListener(surfaceTextureListener);
//设置拍照按钮监听
captureButton.setOnClickListener(v -> {
long currentTime = SystemClock.elapsedRealtime();
if (currentTime - lastClickTime > MIN_CLICK_INTERVAL) {//防止 连点抛出异常
lastClickTime = currentTime;
takePicture();
} else {
Log.d(TAG, "点击过快,已忽略");
}
});
//启动后台线程
startBackgroundThread();
// 如果Surface已经可用,重新打开相机
if (textureView.isAvailable()) {
openCamera();
}
}
@Override
protected void onPause() {
super.onPause();
// 检查拍照状态,延迟释放避免冲突
if (isCapturing) {
Log.w(TAG, "拍照中,延迟释放相机");
new Handler().postDelayed(() -> {
if (!isCapturing) {
closeCamera();
stopBackgroundThread();
}
}, 1000); // 延迟1秒,实际需根据业务调整
} else {
closeCamera();
stopBackgroundThread();
}
}
private final TextureView.SurfaceTextureListener surfaceTextureListener = new TextureView.SurfaceTextureListener() {
//触发时机:SurfaceTexture 初始化后
@Override
public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
Log.d(TAG, "SurfaceTexture初始完毕: SurfaceTexture的尺寸为:" + width + "x" + height);
if (textureView.getWidth()!=width||textureView.getHeight()!=height){
surface.setDefaultBufferSize(width,height);
}
isTextureAvailable = true; // 设置可用标志
}
@Override
public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {
Log.d(TAG, "onSurfaceTextureSizeChanged: " + width + "x" + height);
}
@Override
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
Log.d(TAG, "onSurfaceTextureDestroyed");
isTextureAvailable = false;
return false;
}
@Override
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
}
};
private void initCamera() {
Log.d(TAG, "initCamera: 初始化相机配置");
try {
//1.初始化CameraManager,获取相机配置map
manager = (CameraManager) getSystemService(CAMERA_SERVICE);
cameraId = manager.getCameraIdList()[0];
Log.i(TAG, "使用相机ID: " + cameraId);
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map == null) {
Log.e(TAG, "错误: StreamConfigurationMap为空!!");
}
//2.图片保存参数配置
resolver = getContentResolver();
values = new ContentValues();
values.put(MediaStore.Images.Media.DISPLAY_NAME, "pic_" + System.currentTimeMillis() + ".jpg");
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES);
} catch (CameraAccessException e) {
Log.e(TAG, "相机访问异常: " + e.getMessage());
} catch (NullPointerException e) {
Log.e(TAG, "NPE: " + e.getMessage());
}
}
private Size chooseOptimalSize(Size[] choices, int width, int height) {
List<Size> bigEnough = new ArrayList<>();
for (Size option : choices) {
float ratio = (float) option.getWidth() / option.getHeight();
float viewRatio = (float) width / height;
if (Math.abs(ratio - viewRatio) <= 0.1 &&
option.getWidth() <= width &&
option.getHeight() <= height) {
bigEnough.add(option);
}
}
if (!bigEnough.isEmpty()) {
return Collections.max(bigEnough, new CompareSizesByArea());
}
Log.w(TAG, "未找到完美匹配尺寸,使用默认");
return choices[0];
}
static class CompareSizesByArea implements Comparator<Size> {
@Override
public int compare(Size lhs, Size rhs) {
return Long.signum((long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight());
}
}
private void openCamera() {
if (!isTextureAvailable) { // 检查Surface是否可用
Log.w(TAG, "Surface不可用,延迟打开相机");
return;
}
Log.d(TAG, "openCamera: 尝试打开相机");
try {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "1.打开相机: " + cameraId);
manager.openCamera(cameraId, stateCallback, backgroundHandler);
} else {
Log.w(TAG, "相机权限未授予");
}
} catch (CameraAccessException e) {
Log.e(TAG, "打开相机失败: " + e.getMessage());
} catch (SecurityException e) {
Log.e(TAG, "安全异常: " + e.getMessage());
}
}
private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
Log.i(TAG, "相机已打开");
cameraDevice = camera;
Log.i(TAG, "2.1 开始配置预览流");
createCameraPreviewSession();
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
Log.w(TAG, "相机断开连接");
cameraDevice.close();
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
Log.e(TAG, "相机错误: " + error);
cameraDevice.close();
cameraDevice = null;
}
};
//2.1 开始配置预览流
private void createCameraPreviewSession() {
if (cameraDevice == null || !isTextureAvailable) { // 双重检查
Log.e(TAG, "创建预览会话失败: 相机或Surface不可用");
return;
}
try {
Log.i(TAG, "2.开始配流——配置预览流和拍照流");
//1.1 配置预览尺寸
Size previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), textureView.getWidth(), textureView.getHeight());
Size[] outputSizes = map.getOutputSizes(SurfaceTexture.class);
for (Size outputSize : outputSizes) {
Log.i(TAG,"尺寸::————————————"+outputSize);
}
Log.i(TAG, "选择预览尺寸: " + previewSize.getWidth() + "x" + previewSize.getHeight());
//1.2 配置预览Surface
SurfaceTexture previewTexture = textureView.getSurfaceTexture();
previewTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
previewSurface = new Surface(previewTexture);
// 2.配置拍照Surface
//优化尺寸选择(兼容性处理) 配置jpegSizes
Size[] jpegSizes = map.getOutputSizes(ImageFormat.JPEG);
Size captureSize = chooseOptimalSize(jpegSizes);
Log.i(TAG, "使用拍照尺寸: " + captureSize.getWidth() + "x" + captureSize.getHeight());
// 优化:检查尺寸变化,仅当尺寸变更时重建
if (imageReader == null || imageReader.getWidth() != captureSize.getWidth()) {
if (imageReader != null) imageReader.close();
imageReader = ImageReader.newInstance(
captureSize.getWidth(),
captureSize.getHeight(),
ImageFormat.JPEG,
2 // 缓冲区数量
);
}
// 3.配置双输出Surface(预览 + 拍照)
List<Surface> outputSurfaces = new ArrayList<>(2);
outputSurfaces.add(previewSurface);
outputSurfaces.add(imageReader.getSurface());
//3.创建会话
cameraDevice.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
Log.i(TAG, "2.2 预览会话配置成功");
cameraCaptureSession = session;
try {
//3.下发请求添加预览流
captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
captureRequestBuilder.addTarget(previewSurface);
//配置预览 自动对焦
captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
Log.i(TAG, "3.开始下发预览请求");
cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), null, backgroundHandler);
} catch (CameraAccessException e) {
Log.e(TAG, "设置预览请求失败: " + e.getMessage());
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
Log.e(TAG, "预览会话配置失败");
Toast.makeText(MainActivity.this, "配置失败", Toast.LENGTH_SHORT).show();
}
}, backgroundHandler);
} catch (CameraAccessException e) {
Log.e(TAG, "创建预览会话异常: " + e.getMessage());
}
}
// 优化尺寸选择
private Size chooseOptimalSize(Size[] choices) {
// 优先选择16:9的比例
final double ASPECT_RATIO = 16.0 / 9.0;
Size optimalSize = null;
for (Size size : choices) {
double ratio = (double) size.getWidth() / size.getHeight();
if (Math.abs(ratio - ASPECT_RATIO) <= 0.1) {
if (optimalSize == null ||
size.getWidth() > optimalSize.getWidth()) {
optimalSize = size;
}
}
}
// 若无匹配则选择最大尺寸
if (optimalSize == null) {
for (Size size : choices) {
if (optimalSize == null ||
size.getWidth() > optimalSize.getWidth()) {
optimalSize = size;
}
}
}
return optimalSize != null ? optimalSize : new Size(1920, 1080);
}
private void saveImage(Image image) {
// 1. 创建目标文件
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
if (imageUri != null) {
try (OutputStream os = resolver.openOutputStream(imageUri)) {
os.write(bytes);
Log.i(TAG, "图像保存成功, 大小: " + bytes.length + " bytes");
// 发送媒体扫描广播,写入系统相册 /storage/emulated/0/Pictures/pic_1735868378859.jpg
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
mediaScanIntent.setData(imageUri);
sendBroadcast(mediaScanIntent);
} catch (IOException e) {
Log.e(TAG, "保存文件失败: " + e.getMessage());
}
}
}
//触发时机:用户点击授权后调用
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
Log.d(TAG, "权限请求结果: " + requestCode);
if (requestCode == REQUEST_CAMERA_PERMISSION) {
if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
Log.w(TAG, "用户拒绝相机权限");
Toast.makeText(this, "需要相机权限", Toast.LENGTH_SHORT).show();
finish();
} else {
Log.i(TAG, "用户授予相机权限");
}
}
}
private void startBackgroundThread() {
if (backgroundThread == null) {
backgroundThread = new HandlerThread("CameraBackground");
backgroundThread.start();
backgroundHandler = new Handler(backgroundThread.getLooper());
Log.d(TAG, "后台线程启动");
}
}
private void stopBackgroundThread() {
if (backgroundThread != null) {
Log.d(TAG, "停止后台线程");
backgroundThread.quitSafely();
try {
backgroundThread.join();
backgroundThread = null;
backgroundHandler = null;
} catch (InterruptedException e) {
Log.e(TAG, "停止线程失败: " + e.getMessage());
}
}
}
private void closeCamera() {
Log.d(TAG, "关闭相机资源");
if (isCapturing) {
Log.w(TAG, "正在拍照中,等待完成或取消...");
// 可以尝试等待一段时间或取消请求
try {
cameraCaptureSession.abortCaptures(); // 取消所有进行中的捕获
} catch (CameraAccessException e) {
throw new RuntimeException(e);
}
}
if (cameraCaptureSession != null) {
cameraCaptureSession.close();
cameraCaptureSession = null;
}
if (cameraDevice != null) {
cameraDevice.close();
cameraDevice = null;
}
if (imageReader != null) {
imageReader.close();
imageReader = null;
}
isSessionClosed = true;
}
private boolean checkTakePicture() {
if (cameraDevice == null) {
Log.w(TAG, "拍照失败: 相机未初始化");
return false;
}
// 1. 检查会话有效性
if (cameraCaptureSession == null) {
Log.e(TAG, "拍照错误: CameraCaptureSession为空");
return false;
}
// 2. 检查后台Handler
if (backgroundHandler == null) {
Log.e(TAG, "拍照错误: backgroundHandler未初始化");
startBackgroundThread(); // 初始化方法见下方
return false;
}
if (isSessionClosed) {
Log.e(TAG, "当前会话已关闭");
}
return true;
}
private void takePicture() {
Log.i(TAG, "4.开始拍照流程——————————");
try {
//1.检查是否可以拍照
boolean checkFlag = checkTakePicture();
if (!checkFlag) {
Log.i(TAG, "拍照流程————检查未通过!退出拍照!");
return;
}
// 2. 创建拍照请求
CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(imageReader.getSurface());
//3.设置拍照下发参数
//自动曝光
captureBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
// 图片旋转角度跟随手机默认
int rotation = getWindowManager().getDefaultDisplay().getRotation();
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, rotation);
// 4. 图像可用监听器
imageReader.setOnImageAvailableListener(reader -> {
Log.d(TAG, "图像数据可用");
try (Image image = reader.acquireLatestImage()) {
if (image != null) {
saveImage(image);
}
} catch (Exception e) {
Log.e(TAG, "保存图像错误: " + e.getMessage());
}
}, backgroundHandler);
// 11. 拍照回调
CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull TotalCaptureResult result) {
// 处理拍照完成
isCapturing = false;
// 可以在这里进行后续操作,但注意检查资源是否仍然有效
super.onCaptureCompleted(session, request, result);
Log.i(TAG, "拍照完成,保存至: " + imageUri);
runOnUiThread(() ->
Toast.makeText(MainActivity.this, "保存至: " + imageUri, Toast.LENGTH_SHORT).show()
);
createCameraPreviewSession(); // 恢复预览
}
@Override
public void onCaptureFailed(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull CaptureFailure failure) {
super.onCaptureFailed(session, request, failure);
Log.e(TAG, "拍照失败: " + failure.getReason());
}
};
// 12. 执行拍照(添加会话状态检查)
Log.d(TAG, "停止预览");
cameraCaptureSession.stopRepeating();
Log.d(TAG, "播放黑闪动画");
flash_mode();
Log.d(TAG, "4.下发拍照");
isCapturing = true;
cameraCaptureSession.capture(captureBuilder.build(), captureCallback, backgroundHandler);
} catch (CameraAccessException | IllegalStateException | SecurityException e) {
Log.e(TAG, "拍照过程异常: " + e.getClass().getSimpleName(), e);
}
}
//播放黑闪动画
private void flash_mode() {
// 在拍照按钮点击事件中触发
View flashView = findViewById(R.id.flashview);
flashView.setVisibility(View.VISIBLE); // 先显示黑色视图
// 创建动画:透明度从0到1再到0,模拟闪烁
AlphaAnimation flashAnim = new AlphaAnimation(0.0f, 1.0f);
flashAnim.setDuration(100); // 动画时长100ms,根据需求调整
flashAnim.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
flashView.setVisibility(View.INVISIBLE); // 动画结束后隐藏视图
// 在此处调用实际拍照逻辑(如Camera API)
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
flashView.startAnimation(flashAnim);
}
}
首先那这个demo练手,来搞懂具体细节,有个问题,为什么onresume方法中授权是异步的,执行权限检查时会有弹窗,但是如果我一直不点,那么 // 如果Surface已经可用,重新打开相机
if (textureView.isAvailable()) {
openCamera();
}这段代码不就执行不了么,相机打不开,也就结束了,此时程序算是结束了么