Android拍照保存

1.获取权限

获取相机和读写权限:

1
2
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

2.创建layout

一个简单的layout用来实时显示内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<SurfaceView
android:id="@+id/sfv_preview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1" />

<Button
android:id="@+id/btn_take"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="照相" />

</LinearLayout>

3.创建java代码调用相机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package com.example.pageview.careme;

import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import com.example.pageview.R;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class camera extends Activity {

// 常量,用于传递相机图片的路径和名称
public static final String CAMERA_PATH = "path";
public static final String CAMERA_IMG = "img";

private SurfaceView mSurfaceView; // 用于显示相机预览的SurfaceView
private Button mTakePhoto; // 拍照按钮
private Camera mCamera = null; // 相机实例

// SurfaceHolder.Callback的实现,用于管理相机预览的生命周期
private SurfaceHolder.Callback mCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
startPreview(); // 当Surface创建时,开始相机预览
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// 可在此处理Surface的变化(如尺寸变化)
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
stopPreview(); // 当Surface被销毁时,停止相机预览
}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.camera); // 设置该活动的布局

getPermission(); // 请求相机和存储的权限

bindViews(); // 绑定UI组件
}

/**
* 请求相机和存储权限(如果尚未授予)。
*/
private void getPermission() {
if (Build.VERSION.SDK_INT > 22) { // 检查Android版本是否在6.0以上
if (checkSelfPermission(android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
// 如果没有权限,则请求权限
requestPermissions(new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
} else {
// 已经获得权限的日志
Log.i("Camera", "已经获取了权限");
}
} else {
// 在6.0以下的版本不需要动态请求权限
Log.i("Camera", "这个说明系统版本在6.0之下,不需要动态获取权限。");
}
}

/**
* 绑定该活动的视图组件。
*/
private void bindViews() {
mSurfaceView = (SurfaceView) findViewById(R.id.sfv_preview); // 初始化SurfaceView
mTakePhoto = (Button) findViewById(R.id.btn_take); // 初始化拍照按钮
mSurfaceView.getHolder().addCallback(mCallback); // 为SurfaceHolder添加回调

// 设置拍照按钮的点击事件监听器
mTakePhoto.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 调用takePicture方法拍照
mCamera.takePicture(null, null, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
String path = savePhoto(data); // 保存照片并获取文件路径
if (!TextUtils.isEmpty(path)) {
// 如果照片保存成功,则启动预览活动
Intent it = new Intent(camera.this, PreviewActivity.class);
it.putExtra(CAMERA_PATH, path);
startActivity(it);
} else {
// 如果保存失败,则显示错误消息
Toast.makeText(getApplicationContext(), "拍照失败", Toast.LENGTH_SHORT).show();
}
}
});
}
});
}

/**
* 将照片保存到外部存储。
*
* @param bytes 图片数据的字节数组。
* @return 保存的图片路径,如果失败则返回空字符串。
*/
private String savePhoto(byte[] bytes) {
try {
// 创建时间戳用于生成图片文件名
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_"; // 图片文件前缀
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); // 获取存储目录
Log.d("Camera", "Storage directory: " + storageDir.getAbsolutePath());

// 在指定目录中创建临时文件
File image = File.createTempFile(imageFileName, ".jpg", storageDir);

Log.d("Camera", "Image file path: " + image.getAbsolutePath());

// 将图片数据写入文件
FileOutputStream fos = new FileOutputStream(image);
fos.write(bytes);
fos.flush();
fos.close();
return image.getAbsolutePath(); // 返回保存的图片路径
} catch (IOException e) {
e.printStackTrace();
Log.e("Camera", "Failed to save photo", e);
return ""; // 失败时返回空字符串
}
}

/**
* 开始相机预览。
*/
private void startPreview() {
mCamera = Camera.open(); // 打开相机
try {
mCamera.setPreviewDisplay(mSurfaceView.getHolder()); // 设置SurfaceView作为预览显示
mCamera.setDisplayOrientation(90); // 将相机预览旋转90度
mCamera.startPreview(); // 开始相机预览
} catch (IOException e) {
e.printStackTrace(); // 处理异常
}
}

/**
* 停止相机预览并释放资源。
*/
private void stopPreview() {
if (mCamera != null) {
mCamera.stopPreview(); // 停止相机预览
mCamera.release(); // 释放相机资源
mCamera = null; // 将相机实例设为null
}
}
}

4. 实时显示图片

创建一个java代码,直接打开刚刚拍照的图片,可以减少layout的创建和调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.example.pageview.careme;

import android.net.Uri;
import android.os.Bundle;
import android.widget.ImageView;

import androidx.appcompat.app.AppCompatActivity;

import java.io.File;

public class PreviewActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ImageView img = new ImageView(this);
String path = getIntent().getStringExtra("path");
if(path != null){
img.setImageURI(Uri.fromFile(new File(path)));
}
setContentView(img);
}
}

5. 查看图片

图片保存在/Android/data/com.包名/files/Pictures/JPEG_事件.jpg

6. 代码优化

以上调用的是camera1,有更加优化性能的camera2版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package com.example.pageview.careme;

import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import com.example.pageview.R;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class CameraActivity extends Activity {

// 常量,用于传递相机图片的路径和名称
public static final String CAMERA_PATH = "path";
public static final String CAMERA_IMG = "img";

private SurfaceView mSurfaceView; // 用于显示相机预览的SurfaceView
private Button mTakePhoto; // 拍照按钮
private Camera mCamera = null; // 相机实例

// SurfaceHolder.Callback的实现,用于管理相机预览的生命周期
private SurfaceHolder.Callback mCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
startPreview(); // 当Surface创建时,开始相机预览
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// 可在此处理Surface的变化(如尺寸变化)
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
stopPreview(); // 当Surface被销毁时,停止相机预览
}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.camera); // 设置该活动的布局

checkPermissions(); // 检查并请求必要的权限
bindViews(); // 绑定UI组件
}

/**
* 检查并请求相机和存储权限。
*/
private void checkPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // 检查Android版本
if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED ||
checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
// 请求权限
requestPermissions(new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
} else {
Log.i("Camera", "权限已获得");
}
} else {
Log.i("Camera", "不需要动态请求权限");
}
}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 100) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.i("Camera", "权限已授予");
} else {
Toast.makeText(this, "权限被拒绝", Toast.LENGTH_SHORT).show();
}
}
}

/**
* 绑定该活动的视图组件。
*/
private void bindViews() {
mSurfaceView = findViewById(R.id.sfv_preview); // 初始化SurfaceView
mTakePhoto = findViewById(R.id.btn_take); // 初始化拍照按钮
mSurfaceView.getHolder().addCallback(mCallback); // 为SurfaceHolder添加回调

// 设置拍照按钮的点击事件监听器
mTakePhoto.setOnClickListener(v -> {
// 调用takePicture方法拍照
mCamera.takePicture(null, null, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
String path = savePhoto(data); // 保存照片并获取文件路径
if (!TextUtils.isEmpty(path)) {
// 如果照片保存成功,则启动预览活动
Intent it = new Intent(CameraActivity.this, PreviewActivity.class);
it.putExtra(CAMERA_PATH, path);
startActivity(it);
} else {
// 如果保存失败,则显示错误消息
Toast.makeText(getApplicationContext(), "拍照失败", Toast.LENGTH_SHORT).show();
}
}
});
});
}

/**
* 将照片保存到外部存储。
*
* @param bytes 图片数据的字节数组。
* @return 保存的图片路径,如果失败则返回空字符串。
*/
private String savePhoto(byte[] bytes) {
File image = null;
try {
// 创建时间戳用于生成图片文件名
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_"; // 图片文件前缀
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); // 获取存储目录
Log.d("Camera", "存储目录: " + storageDir.getAbsolutePath());

// 创建临时文件
image = File.createTempFile(imageFileName, ".jpg", storageDir);
Log.d("Camera", "图片路径: " + image.getAbsolutePath());

// 使用 try-with-resources 自动关闭流
try (FileOutputStream fos = new FileOutputStream(image)) {
fos.write(bytes);
fos.flush();
}
return image.getAbsolutePath(); // 返回保存的图片路径
} catch (IOException e) {
Log.e("Camera", "保存照片失败", e);
return ""; // 失败时返回空字符串
}
}

/**
* 开始相机预览。
*/
private void startPreview() {
mCamera = Camera.open(); // 打开相机
try {
mCamera.setPreviewDisplay(mSurfaceView.getHolder()); // 设置SurfaceView作为预览显示
mCamera.setDisplayOrientation(90); // 将相机预览旋转90度
mCamera.startPreview(); // 开始相机预览
} catch (IOException e) {
e.printStackTrace(); // 处理异常
}
}

/**
* 停止相机预览并释放资源。
*/
private void stopPreview() {
if (mCamera != null) {
mCamera.stopPreview(); // 停止相机预览
mCamera.release(); // 释放相机资源
mCamera = null; // 将相机实例设为null
}
}
}

7. 版本差异

Camera1Camera2 是 Android 中用于访问相机的两种 API,具有一些显著的区别。以下是它们之间的主要差异:

1. API 设计与架构

  • Camera1:
    • 设计较早,结构较简单,主要用于基本的相机功能。
    • 提供的功能有限,难以处理复杂的相机操作。
    • 主要依赖于 Camera 类及其回调接口。
  • Camera2:
    • 设计更现代,提供了更灵活和全面的功能。
    • 支持更复杂的相机功能,如手动对焦、曝光控制和高分辨率图像等。
    • 采用了更模块化的设计,使用 CameraManagerCameraCaptureSessionCameraDevice 等类进行操作。

2. 功能支持

  • Camera1:
    • 不支持高级特性,如 RAW 图像捕获、慢动作视频、深度相机等。
    • 对于图像的控制和处理较为有限。
  • Camera2:
    • 支持高级特性,如 RAW 图像、运动跟踪、面部识别和高动态范围(HDR)图像。
    • 允许更细粒度的控制,开发者可以实现自定义的相机行为。

3. 异步处理

  • Camera1:
    • 主要使用同步方法进行相机操作,容易导致 UI 阻塞。
    • 处理图像数据时的灵活性较低。
  • Camera2:
    • 支持异步处理,通过回调机制来处理相机操作,提供更好的用户体验。
    • 支持设置多个捕获请求,允许并行处理。

4. 支持的设备和版本

  • Camera1:
    • 在 Android 5.0(API 级别 21)之前广泛使用,适用于大多数设备。
    • 随着 Android 版本更新,其使用逐渐减少。
  • Camera2:
    • 从 Android 5.0(API 级别 21)开始引入,逐渐取代 Camera1
    • 需要设备支持 Camera2 API 才能使用其高级功能。

5. 使用复杂性

  • Camera1:
    • 使用简单,适合快速实现基本相机功能。
    • 适合初学者,但功能受限。
  • Camera2:
    • 使用复杂,学习曲线较陡峭,但功能强大。
    • 适合需要实现复杂相机应用的开发者。

总结

  • 如果只需要基本的相机功能,Camera1 可能更容易实现。
  • 如果需要更高级的功能和更好的控制,建议使用 Camera2 API。它提供了更灵活的接口和更强大的功能,适合现代应用开发。