序言
由于前段时间在准备跳槽,所以一直没有更新。不过,从这个月开始,我会继续开始记录自己在android开发中遇到的一些坑,或写一些比较有意思的文章。希望大家继续关注。好了,开始切入正题。
概述
这段时间开始接触到Camera相关的东西,所以就打算自己写一个小demo来熟悉一下流程和要点。当然,本文使用SurfaceView来实现一个Camera,同时适配6.0权限(开始没6.0动态权限,后来因为身边很多都是6.0,所以简单的做了一下6.0权限),以及sd卡的读写,图片显示不全等一些相关的知识点。
相关知识的介绍
SurfaceView :使用场景界面迅速更新对帧率要求较高的情况。SurfaceView继承 View,SurfaceView和View最本质的区别在于,SurfaceView是在一个新起的单独线程中可以重新绘制画面,而View必须在UI的主线程中更新画面。因本文主要讲的是怎么使用,所以详细介绍可以看SurfaceView或者Google查看。
RxPermissions Github地址:本文使用了原生的6.0权限请求和RxPermissions。RxPermissions是一个6.0动态权限管理的一个library库,它的使用需要结合Rxjava一起,因为RxPermissions返回的是一个Observable,所以如果不准备使用Rxjava,可以去尝试一下其他的library。可以参考一下弘洋的6.0权限管理
还有读写文件的基本使用方法以及一些图片的简单处理
实现
一 :主要逻辑在MainActivity,在onCreate的时候申请权限处理,在onResume的时候startPreview
开启预览,在onPause的时候releasePreview关闭预览并释放Camera(由于一直持有会出现oom)。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
frameLayout = (FrameLayout) findViewById(R.id.activity_main);
btn_capture = (ImageView) findViewById(R.id.btn_capture);
btn_capture.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
capture();
}
});
/**
* 使用系统API请求相机权限
*/
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
isCamera = false;
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, MY_PERMISSIONS_REQUEST_CAMERA);
} else {
isCamera = true;
initCamera();
initDefult();
}
}
/**
* 获得Camera,开启预览
*
*/
@Override
protected void onResume() {
super.onResume();
if (isCamera == false) return;
if (mCamera == null) {
mCamera = getCamera();
if (sHolder != null) {
setStartPreview(mCamera, sHolder);
}
}
}
/**
* 停止预览,销毁Camera
*/
@Override
protected void onPause() {
super.onPause();
releasePreview();
}
二:initCamera()中主要是初始化Camera和SurfaceView,并且获得SurfaceHolder,然后SurfaceHolder添加回调,并调用setStartPreview,开启预览。
/**
* 初始化Camera相关
*/
private void initCamera() {
mCamera = getCamera();
surface_camera = (SurfaceView) findViewById(R.id.surface_camera);
frameLayout.bringChildToFront(surface_camera);
frameLayout.bringChildToFront(btn_capture);
sHolder = surface_camera.getHolder();
sHolder.addCallback(this);
surface_camera.setOnClickListener(this);
setStartPreview(mCamera,sHolder); //由于APP在第一次安装时,onResume不会执行,所以重新获得cemera权限以后重新start
}
注:大家会看到,在onCeate和onResume都调用了 mCamera = getCamera(),原因是在于,当app第一次安装时,系统会依次执行Activity的生命周期,如果只在onResume中调用,会发现并没有使用相机。原因是在权限申请时,是另起了一个线程,所以获得Camera权限后,onResume已经执行完成。因此添加isCamera字段,来标记是否已经获取权限,同时在取得权限后,调用了 mCamera = getCamera()。
三:当添加了SurfaceHolder回调后,会重写三个方法:surfaceCreated(),surfaceChanged(),surfaceDestroyed()。分别是创建,变化和销毁。
@Override
public void surfaceCreated(SurfaceHolder holder) {
setStartPreview(mCamera, sHolder);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
mCamera.stopPreview();
setStartPreview(mCamera, sHolder);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
releasePreview();
}
四:接下来看最重要的setStartPreview()和 releasePreview()。这两个方法中,setStartPreview中主要是做一下初始化Preview的分辨率,调整一下预览的成像角度。releasePreview中主要是给setPreviewCallback置null,停止预览并释放Camera。
/**
* 开启Camera预览
*/
private void setStartPreview(Camera camera, SurfaceHolder holder) {
try {
Camera.Parameters parameters = camera.getParameters();
List<Camera.Size> size2 = parameters.getSupportedPreviewSizes(); //得到手机支持的预览分辨率
parameters.setPreviewSize(size2.get(0).width,size2.get(0).height);
camera.setPreviewDisplay(holder);//绑定holder
camera.setDisplayOrientation(getPreviewDegree(MainActivity.this));//将系统Camera角度进行调整
camera.startPreview();//开启预览
camera.setParameters(parameters);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 释放Camera
*/
private void releasePreview() {
if (mCamera == null) return;
mCamera.setPreviewCallback(null);
mCamera.stopPreview();//停止预览
mCamera.release();
mCamera = null;
}
五:拍照和点击屏幕实现对焦。点击拍照前,会设置一下Picture相关的参数。当onAutoFocus返回true时,说明对焦成功,然后调用Camera的takePicture实现拍照。
Camera.Parameters parameters = mCamera.getParameters();
List<Camera.Size> supportedPictureSizes = parameters.getSupportedPictureSizes();
parameters.setPictureFormat(ImageFormat.JPEG);//设置图片样式
parameters.setPictureSize(supportedPictureSizes.get(0).width, supportedPictureSizes.get(0).height);//设置图片大小
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);//自动对焦
mCamera.setParameters(parameters);
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public void onAutoFocus(boolean success, Camera camera) {
if (success) {
mCamera.takePicture(null, null, pictureCallback);
}
}
});
六:拍照成功后,使用RxPermissions申请写入sd权限。然后完成跳转到预览界面。其中返回的data是一个拍照完成后,没有压缩过完整的图片byte[]。
//保存图片
String absolutePath = FileUtil.createIfNotExist(path);
FileUtil.writeBytes(path, data);
Intent intent = new Intent(MainActivity.this,ImageActivity.class);
intent.putExtra("path",absolutePath);
startActivity(intent);
总结
由于本人原来并没有涉及到相关模块,但是在刚接触的时候,感觉挺简单,就是按部就班的实现一些方法和生命周期,但是当一步步做下来的时候,发现其中涉及到的细节还是挺多。比如:
1.在我要设置setPreviewSize和setPictureSize时,我发现很容易导致程序崩溃,所以调用getSupportedPictureSizes,获取当前支持的各种分辨率,然后使用最高的分辨来设置。
2.由于本人没有6.0以上的测试机,所以很多问题难以定位。在添加6.0权限后,发现原有的逻辑需要重新思考,所以花费了一些时间和精力。
3.是大家经常会遇到的图片翻转或者角度问题。
4.由于安卓机型实在太多,所以还要考虑多种屏幕下的显示和预览问题。
源码
源码下载地址源码中注释写的很清楚,本文只是把关键代码贴出来,如有需要,欢迎大家下载。
如果大家在学习时有问题,欢迎大家随时联系或者留言,我看见后会第一时间回复并解决。最后,愿大家在小长假中玩得开心,祝愿你我gaygayup,在编码的路上坚挺下去。