LJ的Blog

学海无涯苦做舟

0%

详记Android打开相机拍照流程

写在前面

本文并不是基于Camera2的,所以想要了解Camera2的同学可以先散了。文题加了详记二字,因为相机整个打开的流程的确是比较复杂的,稍有疏忽可能就会引发一系列问题。我也是看了一下Android的文档才整理了这篇文章,想看原文的戳这。不得不说,文档还是详细啊~

本文主要会涉及以下内容:

  • 相机的使用流程
  • 拍照及拍照期间的聚焦
  • 保存图片

先放一下最终效果图吧,做的比较简单,各位不用担心:

最终也就这样~

主要功能就是拍照保存,多的也没啥了,项目地址在文末有。

使用流程

在详细的研究相机之前,首先熟悉一下使用相机的整个流程:

  • 检测和访问相机:创建代码检测相机的存在和请求访问
  • 创建预览类:创建继承自SurfaceView和实现SurfaceHolder接口的预览类。这个类展示来自相机的实时图片
  • 创建一个预览布局:如果你有相机预览类,你就需要创建一个和用户交互的界面布局
  • 为拍照或者录像设置监听:对用户的行为作出响应,你要为你的控件设置监听去开始拍照或者录像,比如你设置了一个拍照的按钮,用户点击之后就要开始拍照。监听用户行为只是其一,还有就是拍照的监听,这个放到后文讨论
  • 捕获和保存文件:无论是拍照还是录像,都需要有保存的功能
  • 释放相机:在不使用相机时候,你的应用一定要释放相机。

那么为什么一定要释放相机资源呢?因为相机硬件是一个共享临界资源,不仅你的应用会使用,其他的应用也会使用相机。所以在不用相机的时候,一定要释放相机,不然你自己的应用和后续其他要使用相机的应用都无法使用相机。

流程大致就是这样,接下来一步一步的跟进,看看这个相机到底特么的是怎么用的。

申请权限

Android 6.0之前 & targetSdkVersion < 23 只需要在清单文件中声明一下权限就行

1
2
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />

上面那个uses-feature,文档中说如果声明了这个,在Google Play中会阻止没有相机的设备下载你的应用。国内的应用商店就不知道了= =。在Android 6.0之后且targetSdkVersion >= 23就需要申请相机权限了。我们可以在Activity的onResume中检测是否拥有相机权限,如果拥有权限就进行下一步的操作,如果没有就申请权限,并在回调中检测是否申请成功,成功的话就进行下一步操作。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
protected void onResume() {
super.onResume();
// 检查权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) !=
PackageManager.PERMISSION_GRANTED) {
// 申请权限
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 1);
} else {
// 已有权限
startCameraPre();
}
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 如果权限申请成功
startCameraPre();
} else {
Toast.makeText(this, "您已拒绝打开相机,想要使用此功能请手动打开相机权限", Toast.LENGTH_SHORT).show();
}
}

检测相机是否存在

在使用之前需要先检测一下设备是否有相机:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 检查是否拥有相机
*
* @return 如果有返回true,没有返回false
*/
public static boolean checkCameraHardware(Context context) {
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
// 有相机
Log.i(TAG, "有相机");
return true;
} else {
// 没有相机
Log.i(TAG, "没有相机");
return false;
}
}

代码比较简单,比较坑爹的是什么呢?有一些嵌入式设备的Android系统通过这个方法是无法获取到到底特么的是有没有相机的。获取的可能是错误的信息,我在我们的设备上用这个代码检测,明明没有相机也判断成有相机了。如果一定要判断……还是有办法的,直接Camera.open试一试,成功就说明有,失败就……就失败了呗。

访问相机

访问相机的代码比较简单,就是通过Camera.open方法拿到一个Camera的实例,需要注意的是这个open方法可能会引发异常,最好还是要try catch一下的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 获取前置相机实例,注意6.0以上的系统需要动态申请权限(如果
* target >= 23)则必须动态申请,否则无法打开相机
*
* @return 打开成功则返回相机实例,失败则返回null
*/
public static Camera getCameraInstance() {
Camera c;
try {
c = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
} catch (Exception e) {
e.printStackTrace();
// 相机正在使用或者不存在
Log.e(TAG, "相机打开失败,正在使用或者不存在,或者,没有权限?");
return null;
}
return c;
}

Android 2.3版本以后可以通过Camera.open(int)来打开指定的相机,我这里打开了后置摄像头。

创建预览类

预览类就是用来播放相机画面的类,预览类是继承自SurfaceView的。普通的View以及其子类都是共享同一个surface的,所有的绘制都必须在UI线程进行。而SurfaceView是一种比较特殊的view,他并不与其他view共享surface,而是在内部持有了一个独立的surface,SurfaceView负责管理这个surface的格式、尺寸以及显示位置。由于UI线程还要同事处理其他交互逻辑,因此对View的更新速度和帧率无法保证,而surfaceview由于持有一个独立的surface,因而可以在独立的线程中进行绘制,因此可以提供更高的帧率。自定义相机的预览图像由于对更新速度和帧率要求比较高,所以比较适合用surfaceview来显示。(关于surfaceview的介绍摘自Android自定义相机详细讲解

介绍了这么多,对SurfaceView大概有个了解就可以了,这个类和相机的声明周期息息相关,需要实现SurfaceHolder.Callback接口来接收View创建和销毁的事件。代码如下:

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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
package com.xiasuhuei321.cameradieorme.camera;

/**
* Created by xiasuhuei321 on 2017/8/22.
* author:luo
* e-mail:xiasuhuei321@163.com
*/

import android.content.Context;
import android.hardware.Camera;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.text.format.DateFormat;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;


public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback, Camera.AutoFocusCallback, Camera.PictureCallback {
public static final String TAG = "CameraPreview";
public static final String DIRNAME = "MyCamera";

private SurfaceHolder mHolder;
private Camera mCamera;
private boolean canTake = false;
private Context context;

public CameraPreview(Context context) {
super(context);
this.context = context;
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed.
mHolder = getHolder();
// deprecated setting, but required on Android versions prior to 3.0
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

Log.i(TAG, "CameraPreview被创建 " + this.hashCode());
}

/**
* surface在很多情况下都会被销毁,这个时候相机也会被释放。
* 而这个类的camera就无法再使用了,所以需要外部再传入一个
* 正确的Camera实例
*
* @param mCamera Camera实例
*/
public void setCamera(Camera mCamera) {
this.mCamera = mCamera;
mHolder.addCallback(this);
surfaceCreated(getHolder());
Log.i(TAG, "serCamera" + " release = " + CameraUtil.getInstance().isRelease());
}

@Override
public void surfaceCreated(SurfaceHolder holder) {
// surface创建完毕,camera设置预览
Log.i(TAG, "surface view被创建");
if (CameraUtil.getInstance().isRelease()) return;
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (IOException e) {
Log.d(TAG, "Error setting camera preview: " + e.getMessage());
}
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// 在这里可以释放相机资源
// 也可以在Activity中释放
Log.i(TAG, "surface 被销毁 ");
holder.removeCallback(this);
// 停止回调,以防释放的相机再被使用导致异常
mCamera.setPreviewCallback(null);
// 停止预览
mCamera.stopPreview();
mCamera.lock();
// 释放相机资源
CameraUtil.getInstance().releaseCamera();
mCamera = null;

}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
// If your preview can change or rotate, take care of those events here.
// Make sure to stop the preview before resizing or reformatting it.
if (mHolder.getSurface() == null) {
// preview surface does not exist
return;
}

// stop preview before making changes
try {
mCamera.stopPreview();
} catch (Exception e) {
// ignore: tried to stop a non-existent preview
}

// set preview size and make any resize, rotate or
// reformatting changes here

// start preview with new settings
try {
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();

} catch (Exception e) {
Log.d(TAG, "Error starting camera preview: " + e.getMessage());
}

}

/**
* 给外部调用,用来拍照的方法
*/
public void takePhoto() {
// 因为设置了聚焦,这里又设置了回调对象,所以重新开始预览之后
// 需要一个标志判断是否是拍照的聚焦回调
canTake = true;
// 首先聚焦
mCamera.autoFocus(this);
// mCamera.takePicture(null, null, this);
}

@Override
public void onAutoFocus(boolean success, final Camera camera) {
Log.i(TAG, "聚焦: " + canTake);
// 不管聚焦成功与否,都开始拍照
if (canTake) {
camera.takePicture(null, null, CameraPreview.this);
}
canTake = false;
// 延时一秒,重新开始预览
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
mCamera.startPreview();
}
}, 1000);
}

@Override
public void onPictureTaken(final byte[] data, Camera camera) {
Log.i(TAG, "onPictureTaken");
// 在子线程中进行io操作
new Thread(new Runnable() {
@Override
public void run() {
saveToSd(data);
}
}).start();
}


/**
* 将照片保存至sd卡
*/
private void saveToSd(byte[] data) {
// 创建位图,这一步在图片比较大的时候可能会抛oom异常,所以跳过这一步,直接将byte[]
// 数据写入文件,而且如果有进行图片处理的需求,尽量不要另外再申请内存,不然很容易
// oom。所以尽量避免在这里处理图片
// Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
// 系统时间
long dateTaken = System.currentTimeMillis();
// 图像名称
String fileName = DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken).toString() + ".jpg";

FileOutputStream fos = null;
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
String filePath = Environment.getExternalStorageDirectory() + File.separator +
DIRNAME + File.separator + fileName;
Log.i(TAG, "文件路径:" + filePath);
File imgFile = new File(filePath);
if (!imgFile.getParentFile().exists()) {
imgFile.getParentFile().mkdirs();
}
try {
if (!imgFile.exists()) {
imgFile.createNewFile();
}

fos = new FileOutputStream(imgFile);
// bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);
fos.write(data);
fos.flush();
insertIntoMediaPic();
} catch (Exception e) {

} finally {
try {
if (fos != null) {
fos.close();//关闭
}
} catch (Exception e) {
e.printStackTrace();
}

}
} else {
// sd卡状态异常,直接插入系统相册
// 暂时是空实现
insertIntoMediaPic();
}

}

private void insertIntoMediaPic() {

}
}

这个类可以说是字字血泪,各位看的时候可以结合注释看……每一个我被坑过的地方我都详细的注释了出来。真的是都在代码里了。关于图片处理那一块我是没什么比较好的办法,内存所限,在拿到byte[] data 这个图片数据数组,我直接在转成Bitmap那一步就OOM了,后来看了一下我这里选取的是4160 * 2340的分辨率,直接写入文件一张图也有4~5M,这个时候的问题就是生成一个Bitmap需要申请很大的内存,而原来的data数组因为这个方法还没结束也无法释放(Java参数传递是引用拷贝传递,所以这时候依然有引用指向内存中的data对象,GC无法回收这块内存),所以就算后续你不额外申请内存,有方法在原有的Bitmap对象上进行操作,也是不行的,因为在生成的时候就OOM了。

创建预览布局

这里Android文档中的建议是在布局中放置一个FrameLayout作为相机预览类的父容器,我采用了这种做法,布局如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal">

<FrameLayout
android:id="@+id/camera_preview"
android:layout_width="match_parent"
android:layout_height="match_parent" />

<ImageView
android:id="@+id/iv_take"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="16dp"
android:src="@drawable/takephoto" />
</RelativeLayout>

布局比较简单,就一个FrameLayout和一个ImageView,点击ImageView开始拍照。

接下来看一下Activity的代码:

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
package com.xiasuhuei321.cameradieorme.camera;

import android.Manifest;
import android.animation.ObjectAnimator;
import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.Toast;

import com.xiasuhuei321.cameradieorme.R;

/**
* Created by xiasuhuei321 on 2017/8/22.
* author:luo
* e-mail:xiasuhuei321@163.com
*/

public class CameraActivity extends AppCompatActivity {

private Camera camera;
private FrameLayout preview;
private CameraPreview mPreview;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_camera);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
View iv_take = findViewById(R.id.iv_take);

final ObjectAnimator scaleX = ObjectAnimator.ofFloat(iv_take, "scaleX", 1f, 0.8f);
final ObjectAnimator scaleY = ObjectAnimator.ofFloat(iv_take, "scaleY", 1f, 0.8f);


iv_take.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
v.setScaleX(0.9f);
v.setScaleY(0.9f);
scaleX.start();
scaleY.start();
break;
case MotionEvent.ACTION_UP:
v.setScaleX(1f);
v.setScaleY(1f);
scaleX.reverse();
scaleY.reverse();
break;
}
return false;
}
});
iv_take.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPreview.takePhoto();
}
});

mPreview = new CameraPreview(this);
CameraUtil.getInstance().init(this);
}

@Override
protected void onResume() {
super.onResume();
// 检查权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) !=
PackageManager.PERMISSION_GRANTED) {
// 申请权限
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 1);
} else {
// 已有权限
startCameraPre();
}
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 如果权限申请成功
startCameraPre();
} else {
Toast.makeText(this, "您已拒绝打开相机,想要使用此功能请手动打开相机权限", Toast.LENGTH_SHORT).show();
}
}

private void startCameraPre() {
if (CameraUtil.checkCameraHardware(this)) {
camera = CameraUtil.getInstance().getCameraInstance();
}
mPreview.setCamera(camera);
preview = (FrameLayout) findViewById(R.id.camera_preview);
if (preview.getChildCount() == 0)
preview.addView(mPreview);
}
}

在开始的时候写了两个属性动画,用户在点击的时候有点交互的感觉(貌似并没有什么luan用)。在onResume中检查是否拥有权限打开相机,因为6.0以上需要动态申请啊,蛋疼。拥有权限或者用户给了权限就执行startCameraPre方法,这个方法通过我自己写的CameraUtil获取并初始化了一个Camera实例。并且最后判断FrameLayout中是否有子View,如果没有就将我们自己的相机预览类添加进去。这样打开相机和拍照的整个流程就完成了,当然了,最后还得贴一下CameraUtil代码:

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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
package com.xiasuhuei321.cameradieorme.camera;

import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.hardware.Camera;
import android.util.Log;
import android.view.WindowManager;

import java.util.List;

/**
* Created by xiasuhuei321 on 2017/8/21.
* author:luo
* e-mail:xiasuhuei321@163.com
*/

public class CameraUtil {
public static final String TAG = "CameraUtil";

private Camera camera;
private int cameraId;

private int mScreenWidth;
private int mScreenHeight;


// private Callback callback;
private boolean release = false;
private Camera.Parameters params;

private CameraUtil() {
}


private static class CameraUtilHolder {
private static CameraUtil instance = new CameraUtil();
}

public static CameraUtil getInstance() {
return CameraUtilHolder.instance;
}

public void init(Context context) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Point p = new Point();
wm.getDefaultDisplay().getSize(p);
mScreenWidth = p.x;
mScreenHeight = p.y;
}

/**
* 检查是否拥有相机
*
* @return 如果有返回true,没有返回false
*/
public static boolean checkCameraHardware(Context context) {
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
// 有相机
return true;
} else {
// 没有相机
return false;
}
}

/**
* 获取前置相机实例,注意6.0以上的系统需要动态申请权限(如果
* target >= 23)则必须动态申请,否则无法打开相机
*
* @return 打开成功则返回相机实例,失败则返回null
*/
public Camera getCameraInstance() {
if (camera != null) {
Log.i(TAG, "camera已经打开过,返回前一个值");
return camera;
}
try {
camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
cameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
} catch (Exception e) {
e.printStackTrace();
// 相机正在使用或者不存在
Log.i(TAG, "相机打开失败,正在使用或者不存在,或者,没有权限?");
return null;
}
initParam();
release = false;
return camera;
}

public void initParam() {
if (camera == null) {
return;
}
if (params != null) {
camera.setParameters(params);
} else {
camera.setParameters(generateDefaultParams(camera));
}
}

/**
* 允许从外部设置相机参数
*
* @param params 相机参数
*/
public void setParams(Camera.Parameters params) {
this.params = params;
}

/**
* 生成默认的相机参数
*
* @param camera 使用该参数的相机
* @return 生成的参数
*/
public Camera.Parameters generateDefaultParams(Camera camera) {
Camera.Parameters parameters = camera.getParameters();
// 设置聚焦
if (parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);// 连续对焦模式
}
camera.cancelAutoFocus();//自动对焦。
// 设置图片格式
parameters.setPictureFormat(PixelFormat.JPEG);
// 设置照片质量
parameters.setJpegQuality(100);
if (cameraId == Camera.CameraInfo.CAMERA_FACING_BACK) {
// 默认打开前置摄像头,旋转90度即可
camera.setDisplayOrientation(90);
parameters.setRotation(90);
} else if (cameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
// 打开后置摄像头,旋转270,这个待验证
camera.setDisplayOrientation(270);
parameters.setRotation(180);
}

// 获取摄像头支持的PictureSize列表
List<Camera.Size> picSizeList = parameters.getSupportedPictureSizes();
for (Camera.Size size : picSizeList) {
Log.i(TAG, "pictureSizeList size.width=" + size.width + " size.height=" + size.height);
}
Camera.Size picSize = getProperSize(picSizeList, ((float) mScreenHeight / mScreenWidth));
parameters.setPictureSize(picSize.width, picSize.height);

// 获取摄像头支持的PreviewSize列表
List<Camera.Size> previewSizeList = parameters.getSupportedPreviewSizes();
for (Camera.Size size : previewSizeList) {
Log.i(TAG, "previewSizeList size.width=" + size.width + " size.height=" + size.height);
}
Camera.Size preSize = getProperSize(previewSizeList, ((float) mScreenHeight) / mScreenWidth);
Log.i(TAG, "final size is: " + picSize.width + " " + picSize.height);
if (null != preSize) {
Log.i(TAG, "preSize.width=" + preSize.width + " preSize.height=" + preSize.height);
parameters.setPreviewSize(preSize.width, preSize.height);
}

return parameters;
}

private Camera.Size getProperSize(List<Camera.Size> pictureSizeList, float screenRatio) {
Log.i(TAG, "screenRatio=" + screenRatio);
Camera.Size result = null;
for (Camera.Size size : pictureSizeList) {
float currentRatio = ((float) size.width) / size.height;
if (currentRatio - screenRatio == 0) {
result = size;
break;
}
}

if (null == result) {
for (Camera.Size size : pictureSizeList) {
float curRatio = ((float) size.width) / size.height;
if (curRatio == 4f / 3) {// 默认w:h = 4:3
result = size;
break;
}
}
}

return result;
}

/**
* 释放相机资源
*/
public void releaseCamera() {
if (camera != null) {
camera.release();
}
camera = null;
release = true;
}

/**
* 现在是否处于释放状态
*
* @return true释放,false没释放
*/
public boolean isRelease() {
return release;
}

}

这里需要注意的就是生成相机参数那一块了,Android中的相机默认是横向的,我们平时用的时候肯定不是那么用的,所以通过 camera.setDisplayOrientation(90)旋转90度调整一下。不过设置了这个之后,如果不设置parameters.setRotation(90)那么保存的图片方向也不对,设置了这个之后就可以了。不过我看网上很多都是采用自己生成Bitmap然后自己旋转……如果parameters.setRotation(90)这种方式可以完成的话,最好不要采用自己处理的方式了,内存开销太大了。关于资源的释放啊什么的,都在预览类里面注释写好了= = ,这里就不再赘述了。

小结

最开始研究相机是因为项目里一个用到相机三方总是报错,在有空研究了一下相机之后,添了一行代码,测试到现在还算比较稳定,没有出现崩溃了,有的时候真的是,一行代码能改变的东西却是非常多的。跑题了跑题了,现在突然感觉相机可以玩的东西很多……以后这个demo可能会继续完善

代码地址:https://github.com/ForgetAll/CameraDieOrMe

参考资料