Android系统提供了两种使用手机相机资源实现拍摄功能的方法, : - 一种是直接经过Intent调用系统相机组件.这种方法快速方便,适用于直接得到照片的场景,如上传相册,微博、朋友圈发照片等。 : - 一种是使用相机API来定制自定义相机.这种方法适用于须要定制相机界面或者开发特殊相机功能的场景,如须要对照片作裁剪、滤镜处理,添加贴纸,表情,地点标签等。这篇文章主要是从如何使用相机API来定制自定义相机这个方向展开的。java
若是你的App的需求只是调用摄像头拍照并拿到照片,老司机的建议是别本身实现拍照模块,这里面坑多水深。你彻底可使用Intent来调用系统相机或第三方具有拍照功能的App来拍照并获取返回照片数据。 建立一个Intent,指定两个拍摄类型之一android
MediaStore.ACTION_IMAGE_CAPTURE
拍摄照片;MediaStore.ACTION_VIDEO_CAPTURE
拍摄视频;Intent intent = new Intent(MediaStore.ACTION_IMAGE/VIDEO_CAPTURE);
复制代码
通用流程startActivityForResult()
和onActivityResult()
就不表述了。说说拍摄照片的Intent参数吧。canvas
首先是设置拍摄后返回数据的地址:数组
intent.putExtra(MediaStore.EXTRA_OUTPUT, your-store-uri);
复制代码
MediaStore.EXTRA_OUTPUT
参数用于指定拍摄完成后的照片/视频的储存路径。你可使用Android默认的储存照片目录来保存:异步
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURE)
复制代码
public class MainActivity extends Activity {
private ImageView show_iv;
private String mFilePath = "";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
show_iv = (ImageView) findViewById(R.id.show_iv);
}
public void onClick(View view) {
Intent intent = new Intent();
// 1. 直接调用系统相机 没有返回值
// intent.setAction("android.media.action.STILL_IMAGE_CAMERA");
// startActivity(intent);
// 2 调用系统相机 有返回值 可是返回值是 缩略图
// intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
// startActivityForResult(intent, 100);
// 3 .返回原图
mFilePath =
Environment.getExternalStorageDirectory().getAbsolutePath() +
"/picture.png";
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
Uri uri = Uri.fromFile(new File(mFilePath));
// 指定路径
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
startActivityForResult(intent, 300);
// 4. 打开系统相册
// intent.setAction(Intent.ACTION_PICK);
// intent.setType("image/*");
// startActivityForResult(intent, 500);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// TODO Auto-generated method stub
super.onActivityResult(requestCode, resultCode, data);
// 返回缩略图
if (requestCode == 100) {
if (data != null) {
Bitmap bitmap = (Bitmap) data.getExtras().get("data");
if (bitmap != null) {
show_iv.setImageBitmap(bitmap);
}
}
}
// 原图
if (requestCode == 300) {
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream(mFilePath);
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
if (bitmap != null) {
show_iv.setImageBitmap(rotateBitmap(bitmap));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// 相册
if (requestCode == 500) {
Uri uri = data.getData();
try {
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri));
show_iv.setImageBitmap(bitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
// 三星机型拍照的时候照片会旋转90度 因此须要转回来
public Bitmap rotateBitmap(Bitmap bitmap) {
Matrix matrix = new Matrix();
matrix.postRotate(90);
Bitmap map = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
return map;
}
}
复制代码
相机的权限问题和Android7.0以上的拍照返回图片文件Uri问题,参考Android踩坑日记(一):android7.0动态相机权限ide
1.2.1 相机API关键类解析函数
Camera: 最主要的类,用于管理和操做camera资源。它提供了完整的相机底层接口,支持相机资源切换,设置预览、拍摄尺寸,设定光圈,曝光,聚焦等相关参数,获取预览,拍摄帧数据等功能,主要一下方法:工具
/ **
*建立一个新的Camera对象来访问特定的硬件摄像机。若是
*相同的相机被其余应用程序打开,这会抛出一个
* RuntimeException。
*
* <p>完成使用相机后,您必须致电{@link #release()},
*不然它将保持锁定状态并对其余应用程序不可用。
*
* <p>您的应用程序一次只能有一个Camera对象处于活动状态
*用于特定的硬件摄像机。
*
* <p>来自其余方法的回调被传递给调用open()的线程。若是这个线程没有事件循 * 环loop,那么
*回调传递到主应用程序事件循环loop。若是有
*没有主应用程序事件循环,回调不会传递。
*
* <p class =“caution”> <b>注意:</ b>
* 在某些设备上,此方法可能会发生
* 须要很长时间才能完成。最好从一个工做线程
*(可能使用{@link android.os.AsyncTask})调用这个方法,来避免
*阻塞主应用程序UI线程。
*
* @参数cameraId硬件摄像头访问,介于0和
* {@link #getNumberOfCameras()} - 1。
* @返回一个新的Camera对象,链接,锁定并准备使用。
*若是打开相机失败,则引起RuntimeException(例如,若是
*相机正在被其余进程或设备策略管理器使用
*禁用相机)
public static Camera open(int cameraId) {
return new Camera(cameraId);
}
/**
*建立一个新的摄像头对象,以访问摄像头上的第一个后置摄像头
*设备。 若是设备没有后置摄像头,则返回
* 空值。
* @see #open(int)
*/
public static Camera open() {
int numberOfCameras = getNumberOfCameras();
CameraInfo cameraInfo = new CameraInfo();
for (int i = 0; i < numberOfCameras; i++) {
getCameraInfo(i, cameraInfo);
if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
return new Camera(i);
}
}
return null;
}
复制代码
/ **
*设置{@link Surface}用于实时预览。
*预览时须要surface or surface texture,而且
*预览对拍照是必要的。相同的surface能够毫无影响的从新设置
*设置预览surface将取消设置任何经过{@link#setPreviewTexture}设置预览surface texture
*
* <p>此时,当这个方法被调用的时候,{@link SurfaceHolder}必须已经包含一个surface
*若是您使用{@link android.view.SurfaceView},
*您须要注册{@link SurfaceHolder.Callback}
*经过调用{@link SurfaceHolder#addCallback(SurfaceHolder.Callback)}并等待
* {@link SurfaceHolder.Callback#surfaceCreated(SurfaceHolder)}而且在调用
*调用setPreviewDisplay()或开始预览。
*
* <p>必须在{@link #startPreview()}以前调用此方法。
*有一个异常Exception的状况是,在调用startPreview()以前,若是预览surafce未设置(或设置为空)
*,则能够调用此方法一次 ,使用非空参数设置预览surface(这容许相机设置和表面建立并行发生,节省时间。)
* 预览运行时,预览surface可能不会改变。
*
* @param 持有者包含放置预览的Surface,
*或null删除预览表面
*若是方法失败,则引起IOException(例如,若是表面
*不可用或不适用)。
* /
public final void setPreviewDisplay(SurfaceHolder holder) throws IOException {
if (holder != null) {
setPreviewSurface(holder.getSurface());
} else {
setPreviewSurface((Surface)null);
}
}
复制代码
/** *更改此相机服务的设置。 * * @参数params用于此Camera服务的参数 *若是任何参数无效或不受支持,则引起RuntimeException。 * @see #getParameters() */
public void setParameters(Parameters params) {
// If using preview allocations, don't allow preview size changes
if (mUsingPreviewAllocation) {
Size newPreviewSize = params.getPreviewSize();
Size currentPreviewSize = getParameters().getPreviewSize();
if (newPreviewSize.width != currentPreviewSize.width ||
newPreviewSize.height != currentPreviewSize.height) {
throw new IllegalStateException("Cannot change preview size" +
" while a preview allocation is configured.");
}
}
native_setParameters(params.flatten());
}
复制代码
/** *开始捕捉和绘制预览帧到屏幕上。 *预览不会实际开始,直到提供surface *使用{@link #setPreviewDisplay(SurfaceHolder)}或 * {@link #setPreviewTexture(SurfaceTexture)}。 * * <p>若是经过{@link #setPreviewCallback(Camera.PreviewCallback)}, * {@link #setOneShotPreviewCallback(Camera.PreviewCallback)}或 * {@link #setPreviewCallbackWithBuffer(Camera.PreviewCallback)}设置了回调callback *,那么回调的{@ Camera.PreviewCallback#onPreviewFrame(byte [],Camera)} *在预览数据可用时将被调用。即预览时onPreviewFrame时一直被回调的 */
public native final void startPreview();
复制代码
/ **
*触发异步图像捕捉。相机服务将启动
*随着图像捕捉的进行,对应用程序进行一系列回调。
*拍摄图像后发生快门回调。这可使用
*触发声音让用户知道图像已被捕捉。该
*当原始图像数据可用时发生原始回调(注意:数据
*若是没有可用的原始图像回调缓冲区,则它将为空
*原始图像回调缓冲区不足以容纳原始图像)。
* postview回调时发生缩放,彻底处理的postview
*图像可用(注意:并不是全部硬件都支持此功能)。 jpeg
*当压缩图像可用时发生回调。若是
*应用程序不须要特定的回调,能够传递null
*而不是回调方法。
*
* <p>此方法仅在预览处于活动状态时有效(以后
* {@link #startPreview()})。预览将在图像后中止
*采起;调用者必须再次调用{@link #startPreview()}
*从新开始预览或拍摄更多照片。这不该该被称为之间
* {@link android.media.MediaRecorder#start()}和
* {@link android.media.MediaRecorder#stop()}。
*
* <p>调用此方法后,您不得调用{@link #startPreview()}
*或拍摄另外一张照片,直到JPEG回调已返回。
*
* @参数快门图像捕捉时刻的回调,或为空
* @参数为原始(未压缩)图像数据的回调,或为空
* postview图像数据的@param postview回调,可能为空
* @param jpeg JPEG图像数据的回调,或者为null
* /
public final void takePicture(ShutterCallback shutter, PictureCallback raw,
PictureCallback postview, PictureCallback jpeg) {
mShutterCallback = shutter;
mRawImageCallback = raw;
mPostviewCallback = postview;
mJpegCallback = jpeg;
// If callback is not set, do not send me callbacks.
int msgType = 0;
if (mShutterCallback != null) {
msgType |= CAMERA_MSG_SHUTTER;
}
if (mRawImageCallback != null) {
msgType |= CAMERA_MSG_RAW_IMAGE;
}
if (mPostviewCallback != null) {
msgType |= CAMERA_MSG_POSTVIEW_FRAME;
}
if (mJpegCallback != null) {
msgType |= CAMERA_MSG_COMPRESSED_IMAGE;
}
native_takePicture(msgType);
mFaceDetectionRunning = false;
}
复制代码
/** * 关闭camra底层的帧数据传递以及surface上的绘制, and * 重置相机,为了之后调用 {@link #startPreview()} */
public final void stopPreview() {
_stopPreview();
mFaceDetectionRunning = false;
mShutterCallback = null;
mRawImageCallback = null;
mPostviewCallback = null;
mJpegCallback = null;
synchronized (mAutoFocusCallbackLock) {
mAutoFocusCallback = null;
}
mAutoFocusMoveCallback = null;
}
private native final void _stopPreview();
复制代码
/** * Disconnects and releases the Camera object resources. * * <p>You must call this as soon as you're done with the Camera object.</p> */
public final void release() {
native_release();
mFaceDetectionRunning = false;
}
private native final void native_release();
复制代码
SurfaceView:这个以前写过文章oop
SurfaceView: : 用于绘制相机预览图像的类,提供给用户实时的预览图像。普通的view以及派生类都是共享同一个surface的,全部的绘制都必须在UI线程中进行。 而surfaceview是一种比较特殊的view,它并不与其余普通view共享surface,而是在内部持有了一个独立的surface,surfaceview负责管理这个surface的格式、尺寸以及显示位置。因为UI线程还要同时处理其余交互逻辑,所以对view的更新速度和帧率没法保证,而surfaceview因为持有一个独立的surface,于是能够在独立的线程中进行绘制,所以能够提供更高的帧率。自定义相机的预览图像因为对更新速度和帧率要求比较高,因此比较适合用surfaceview来显示。post
SurfaceHolder:surfaceholder是控制surface的一个抽象接口,它可以控制surface的尺寸和格式,修改surface的像素,监视surface的变化等等,surfaceholder的典型应用就是用于surfaceview中。surfaceview经过getHolder()方法得到surfaceholder 实例,经过后者管理监听surface 的状态。
SurfaceHolder.Callback 接口:负责监听surface状态变化的接口,有三个方法:
surfaceCreated(SurfaceHolder holder)
:在surface建立后当即被调用。在开发自定义相机时,能够经过重载这个函数调用camera.open()、camera.setPreviewDisplay(),来实现获取相机资源、链接camera和surface等操做。surfaceChanged(SurfaceHolder holder, int format, int width, int height)
:在surface发生format或size变化时调用。在开发自定义相机时,能够经过重载这个函数调用camera.startPreview来开启相机预览,使得camera预览帧数据能够传递给surface,从而实时显示相机预览图像。surfaceDestroyed(SurfaceHolder holder)
:在surface销毁以前被调用。在开发自定义相机时,能够经过重载这个函数调用camera.stopPreview(),camera.release()来实现中止相机预览及释放相机资源等操做。1.2.2 相机开发流程
开发第一步是在 Android Manifest.xml 文件中声明使用相机的权限:
<uses-permission android:name="android.permission.CAMERA" />
复制代码
有些同窗在开发时忘了声明权限,运行时应用可能会崩溃掉。另外也要增长如下两个特性声明:
<uses-feature android:name="android.hardware.camera" android:required="true"/>
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
复制代码
required
属性是说明这个特性是否必须知足。比方说示例的设置就是要求必须拥有相机设备但能够没有自动对焦功能。
这两个声明是可选的,它们用于应用商店(GooglePlay)过滤不支持相机和不支持自动对焦的设备。
另外在保存照片时须要写入储存器的权限,也须要加上读写储存器的权限声明:
<uses-permission android:name="android.permission.WEITE_EXTERNAL_STORAGE" />
复制代码
在市面上销售的手机/平板等消费产品基本标配两个摄像头。如华为P9,更有前置双摄像头。讲真,我很好奇开发双摄像头的App是怎样的体验。在打开相机设备前,先获取当前设备有多少个相机设备。若是你的开发需求里包含切换先后摄像头功能,能够获取摄像头数量来判断是否存在后置摄像头。
int cameras = Camera.getNumberOfCameras();
复制代码
这个接口返回值为摄像头的数量:非负整数。对应地,摄像头序号为: cameras - 1。例如在拥有先后摄像头的手机设备上,其返回结果是2,则第一个摄像头的cameraId是0,一般对应手机背后那个大摄像头;第二个摄像头的cameraId是1,一般对应着手机的前置自拍摄像头;
相机是一个硬件设备资源,在使用设备资源前须要将它打开,能够经过接口**Camera.open(cameraId)**来打开。参考如下代码:
public static Camera openCamera(int cameraId) {
try{
return Camera.open(cameraId);
}catch(Exception e) {
return null;
}
}
复制代码
注意
打开相机设备可能会失败,你必定要检查打开操做是否成功。打开失败的可能缘由有两种: : 一是安装App的设备上根本没有摄像头,例如某些平板或特殊Android设备; 二是cameraId对应的摄像头正被使用,可能某个App正在后台使用它录制视频。
在打开相机设备后,你将得到一个Camera对象,并独占相机设备资源。 经过**Camera.getParameters()**接口能够获取当前相机设备的默认配置参数。下面列举一些我能理解的参数:
闪光灯配置参数,能够经过Parameters.getFlashMode()
接口获取当前相机的闪光灯配置参数:
对焦模式配置参数,能够经过Parameters.getFocusMode()
接口获:
场景模式配置参数,能够经过Parameters.getSceneMode()
接口获取:
相机预览图须要设置正确的预览方向才能正常地显示预览画面,不然预览画面会被挤压得很惨。 在一般状况下,若是咱们须要知道设备的屏幕方向,能够经过Resources.Configuration.orientation
来获取。Android屏幕方向有“竖屏”和“横屏”两种,对应的值分别是ORIENTATION_PORTRAIT和ORIENTATION_LANDSCAPE
。但相机设备的方向却有些特别,设置预览方向的接口Camera.setDisplayOrientaion(int)
的参数是以角度为单位的,并且只能是0,90,180,270
其中之一,``默认为0,是指手机的左侧为摄像头顶部画面。记得只能是[0、90、180、270]其中之一,输入其它角度数值会报错`。
若是你想让相机跟随设备的方向,预览界面顶部一直保持正上方,如下代码供参考:
public static void followScreenOrientation(Context context, Camera camera){
final int orientation = context.getResources().getConfiguration().orientation;
if(orientation == Configuration.ORIENTATION_LANDSCAPE) {
camera.setDisplayOrientation(180);
}else if(orientation == Configuration.ORIENTATION_PORTRAIT) {
camera.setDisplayOrientation(90);
}
}
复制代码
咱们通常使用SurfaceView做为相机预览View,你也可使用Texture。在SurfaceView中获取得SurfaceHolder,并经过setPreviewDisplay()接口设置预览。在设置预览View后,必定要记得如下两点:
Camera接受一个SurfaceHolder接口,这个接口能够经过SurfaceHolder.Callback得到。咱们能够经过继承SurfaceView来实现相机预览效果。在NextQRCode项目中,实现了LiveCameraView类,它内部已实现了相机预览所须要的处理过程,很简洁的类,如下是它的所有源码:
ublic class LiveCameraView extends SurfaceView implements SurfaceHolder.Callback {
private final static String TAG = LiveCameraView.class.getSimpleName();
private Camera mCamera;
private SurfaceHolder mSurfaceHolder;
public LiveCameraView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mSurfaceHolder = this.getHolder();
mSurfaceHolder.addCallback(this);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.d(TAG, "Start preview display[SURFACE-CREATED]");
startPreviewDisplay(holder);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if (mSurfaceHolder.getSurface() == null){
return;
}
Cameras.followScreenOrientation(getContext(), mCamera);
Log.d(TAG, "Restart preview display[SURFACE-CHANGED]");
stopPreviewDisplay();
startPreviewDisplay(mSurfaceHolder);
}
public void setCamera(Camera camera) {
mCamera = camera;
final Camera.Parameters params = mCamera.getParameters();
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
params.setSceneMode(Camera.Parameters.SCENE_MODE_BARCODE);
}
private void startPreviewDisplay(SurfaceHolder holder){
checkCamera();
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (IOException e) {
Log.e(TAG, "Error while START preview for camera", e);
}
}
private void stopPreviewDisplay(){
checkCamera();
try {
mCamera.stopPreview();
} catch (Exception e){
Log.e(TAG, "Error while STOP preview for camera", e);
}
}
private void checkCamera(){
if(mCamera == null) {
throw new IllegalStateException("Camera must be set when start/stop preview, call <setCamera(Camera)> to set");
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d(TAG, "Stop preview display[SURFACE-DESTROYED]");
stopPreviewDisplay();
}
}
复制代码
从上面代码能够看出LiveCameraView的核心代码是SurfaceHolder.Callback
的回调:在建立/销毁时启动/中止预览动做。在LiveCameraView类中,咱们利用了View的生命周期回调来实现自动管理预览生命周期控制:
预览View须要注意预览输出画面的尺寸。相机输出画面只支持部分尺寸。关于尺寸部分,后面再更新
在启用预览View后,就能够经过Camera.takePicture()
方法拍摄一张照片,返回的照片数据经过Callback接口获取
。takePicture()
接口能够获取三个类型的照片:
ShutterCallback
接口,在拍摄瞬间瞬间被回调,一般用于播放“咔嚓”这样的音效;PictureCallback
接口,返回未经压缩的RAW类型照片;PictureCallback
接口,返回通过压缩的JPEG类型照片;咱们使用第三个参数,JPEG类型的照片的图片精度便可知足识别二维码的需求。在NextQRCode项目中,ZXing识别二维码的数据格式为Bitmap,经过BitmapFactory能够很方便方便地将byte数组转换成Bitmap。
public abstract class BitmapCallback implements Camera.PictureCallback {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
onPictureTaken(BitmapFactory.decodeByteArray(data, 0, data.length));
}
public abstract void onPictureTaken(Bitmap bitmap);
}
复制代码
在打开一个相机设备后,意味着你的App就独占了这个设备,其它App将没法使用它。所以在你不须要相机设备时,记得调用**release()方法释放设备,再使用时能够从新打开,这并不须要多大的成本。能够选择在stopPreview()**后即释放相机设备。
附加工具性代码实现
public static boolean hasCameraDevice(Context ctx) {
return ctx.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_CAMERA);
}
复制代码
public static boolean isAutoFocusSupported(Camera.Parameters params) {
List<String> modes = params.getSupportedFocusModes();
return modes.contains(Camera.Parameters.FOCUS_MODE_AUTO);
}
复制代码