转载:http://blog.youkuaiyun.com/yjp19871013/article/details/53461774
上一篇中完成了拍照功能,但是美中不足的是,相机的操作分散在MainActivity类的各个角落,包括回调的设置等等,为了方便以后的使用,应当将我们的Camera封装。现在先进行一轮简单的重构,对Camera进行必要的封装。
我们创建一个MyCamera类,使用has-a的方式,在类内包含一个Camera的成员变量,借助于该成员变量,我们将上层Activity对Camera的调用封装为接口。
封装后的MyCamera类如下
- package com.yjp.camera;
- import android.hardware.Camera;
- import android.os.Environment;
- import android.view.SurfaceHolder;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.util.Date;
- import java.util.List;
- @SuppressWarnings("deprecation")
- public class MyCamera implements Camera.PictureCallback, Camera.ShutterCallback {
- private Camera mCamera;
- public void openCamera() {
- if (null == mCamera) {
- mCamera = Camera.open();
- }
- }
- public void releasecamera() {
- if (null != mCamera) {
- mCamera.release();
- mCamera = null;
- }
- }
- public void takePicture() {
- mCamera.takePicture(this, null, this);
- }
- public void onSurfaceCreated(SurfaceHolder holder) {
- try {
- //surface创建成功能够拿到回调的holder
- //holder中包含有成功创建的Surface
- //从而交给摄像机预览使用
- mCamera.setPreviewDisplay(holder);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
- //surface的尺寸发生变化
- //配置预览参数,如分辨率等
- //这里使用的分辨率简单选取了支持的预览分辨率的第一项
- //网上可以查找对应的优选算法
- Camera.Parameters parameters = mCamera.getParameters();
- List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();
- Camera.Size selected = sizes.get(0);
- parameters.setPreviewSize(selected.width, selected.height);
- parameters.setPictureSize(selected.width, selected.height);
- //给摄像机设置参数,开始预览
- mCamera.setParameters(parameters);
- mCamera.startPreview();
- }
- public void onSurfaceDestroyed(SurfaceHolder holder) {
- }
- @Override
- public void onPictureTaken(byte[] data, Camera camera) {
- try {
- FileOutputStream out;
- String filename = new Date().getTime() + ".jpg";
- String filePathname = Environment.getExternalStorageDirectory() + "/"
- + Environment.DIRECTORY_PICTURES + "/" + filename;
- out = new FileOutputStream(filePathname);
- out.write(data);
- out.flush();
- out.close();
- //重新启动预览
- mCamera.startPreview();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- @Override
- public void onShutter() {
- }
- }
可以看到,就是使用封装函数的重构方法,将上一篇中Camera的操作分别封装,如果还想进行进一步的重构,可以为我们的MyCamera创建一个抽象基类CameraBase类,然后将onSurfaceCreated,onSurfaceChanged和onSurfaceDestoryed三个函数定义为抽象方法,由MyCamera继承CameraBase,然后实现三个方法,因为这三个方法针对不同的Camera实现可能会不同。另外,在takePicture中可以加入路径参数,指示相机将文件保存在哪里,由于暂时我们没有这个需求,先不调整接口,需要时进一步重构。下面看一下重构之后的MainActivity类:
- package com.yjp.takepicture;
- import android.os.Bundle;
- import android.support.v7.app.AppCompatActivity;
- import android.view.SurfaceHolder;
- import android.view.SurfaceView;
- import android.view.View;
- import android.widget.Button;
- import com.yjp.camera.MyCamera;
- @SuppressWarnings("deprecation")
- public class MainActivity extends AppCompatActivity
- implements SurfaceHolder.Callback {
- private MyCamera mCamera = new MyCamera();
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- //配置SurfaceView
- //setType使用外来数据源
- //设置SurfaceHolder.Callback
- SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surfaceView);
- SurfaceHolder surfaceHolder = surfaceView.getHolder();
- surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
- surfaceHolder.addCallback(this);
- Button takePictureButton = (Button) findViewById(R.id.takePictureButton);
- takePictureButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- mCamera.takePicture();
- }
- });
- }
- @Override
- protected void onStart() {
- super.onStart();
- mCamera.openCamera();
- }
- @Override
- protected void onStop() {
- mCamera.releasecamera();
- super.onStop();
- }
- @Override
- public void surfaceCreated(SurfaceHolder holder) {
- mCamera.onSurfaceCreated(holder);
- }
- @Override
- public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
- mCamera.onSurfaceChanged(holder, format, width, height);
- }
- @Override
- public void surfaceDestroyed(SurfaceHolder holder) {
- mCamera.onSurfaceDestroyed(holder);
- }
- }
和上一篇文章中的类比较,已经简洁了很多了,Activity中只剩下了界面的跳转逻辑,所用功能性的内容,全部委托给了我们实现的MyCamera类。
下面就可以基于我们封装后的Camera进行连拍的开发,连拍无非就是点击连拍时,多次调用takePicture,在MyCamera中添加如下代码:
- public void continuousShooting(final int shootingTimes) {
- Executor executor = Executors.newSingleThreadExecutor();
- executor.execute(new Runnable() {
- @Override
- public void run() {
- for (int i = 0; i < shootingTimes; i++) {
- takePicture();
- try {
- SECONDS.sleep(1);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- });
- }
逻辑很简单,启动线程,然后多次调用拍照,但要注意添加休眠,否则可能无法进行连拍,没有看底层代码,怀疑是硬件响应需要时间。另外,严谨的做法应该保证线程可取消,并做相应的回收处理,那个时候可以使用ExecutorService执行线程和取消执行。
在MainAvtivity中添加一个Button,连拍5张,布局及相关代码如下:
- <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/activity_main"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:paddingBottom="@dimen/activity_vertical_margin"
- android:paddingLeft="@dimen/activity_horizontal_margin"
- android:paddingRight="@dimen/activity_horizontal_margin"
- android:paddingTop="@dimen/activity_vertical_margin"
- tools:context="com.yjp.takepicture.MainActivity">
- <SurfaceView
- android:id="@+id/surfaceView"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:layout_centerInParent="true"
- android:layout_alignBottom="@id/surfaceView">
- <Button
- android:id="@+id/takePictureButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/takePictureButtonText"/>
- <Button
- android:id="@+id/continuousShootingButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/continuousShootingText"/>
- </LinearLayout>
- </RelativeLayout>
添加了一个LinearLayout并在其中添加一个Button,MainActivity类的代码修改如下:
- public class MainActivity extends AppCompatActivity
- implements SurfaceHolder.Callback {
- private final int CONTINUOUS_SHOOTING_TIMES = 5;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- ...
- Button continuousShootingButton = (Button) findViewById(R.id.continuousShootingButton);
- continuousShootingButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- mCamera.continuousShooting(CONTINUOUS_SHOOTING_TIMES);
- }
- });
- }
- ...
- }
可以看到,封装后的代码,添加新功能如此简单,只是重新定义一个函数即可,下面再看看定时拍照功能。无非设置定时器,在定时器中调用MyCamera.takePicture()。下面完成定时拍照,在MyCamera中添加如下代码:
- public class MyCamera implements Camera.PictureCallback, Camera.ShutterCallback {
- private ScheduledThreadPoolExecutor mTimerShootingExecutor;
- public void releaseCamera() {
- if (null != mCamera) {
- if (isTimerShootingStart()) {
- stopTimerShooting();
- }
- ...
- }
- }
- ...
- public synchronized void startTimerShooting(int timeMs) {
- if (null == mTimerShootingExecutor) {
- mTimerShootingExecutor = new ScheduledThreadPoolExecutor(1);
- mTimerShootingExecutor.scheduleWithFixedDelay(new Runnable() {
- @Override
- public void run() {
- takePicture();
- }
- }, 0, timeMs, TimeUnit.MILLISECONDS);
- }
- }
- public synchronized void stopTimerShooting() {
- if (null != mTimerShootingExecutor) {
- mTimerShootingExecutor.shutdown();
- mTimerShootingExecutor = null;
- }
- }
- public synchronized boolean isTimerShootingStart() {
- if (null != mTimerShootingExecutor) {
- return true;
- } else {
- return false;
- }
- }
- ...
- }
上面注意,考虑线程安全,函数采用了同步机制,另外使用ScheduledThreadPoolExecutor实现定时运行任务,可以网上查阅资料,了解一下,Java目前不太建议采用TimerTask,主要是由于Timertask缺少必要的异常机制,同时操控性较差。下面是MainActivity添加的内容:
- <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/activity_main"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:paddingBottom="@dimen/activity_vertical_margin"
- android:paddingLeft="@dimen/activity_horizontal_margin"
- android:paddingRight="@dimen/activity_horizontal_margin"
- android:paddingTop="@dimen/activity_vertical_margin"
- tools:context="com.yjp.takepicture.MainActivity">
- <SurfaceView
- android:id="@+id/surfaceView"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:layout_centerInParent="true"
- android:layout_alignBottom="@id/surfaceView">
- <Button
- android:id="@+id/takePictureButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/takePictureButtonText"/>
- <Button
- android:id="@+id/continuousShootingButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/continuousShootingText"/>
- <Button
- android:id="@+id/timerShootingButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/timerShootingText"/>
- </LinearLayout>
- </RelativeLayout>
添加了一个新的按钮
- public class MainActivity extends AppCompatActivity
- implements SurfaceHolder.Callback {
- private final int TIMER_SHOOTING_INTERVAL_MS = 5000;
- ...
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- ...
- Button timerShootingButton = (Button) findViewById(R.id.timerShootingButton);
- timerShootingButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Button button = (Button) v;
- if (mCamera.isTimerShootingStart()) {
- mCamera.stopTimerShooting();
- button.setText(getResources().getString(R.string.timerShootingText));
- } else {
- mCamera.startTimerShooting(TIMER_SHOOTING_INTERVAL_MS);
- button.setText(getResources().getString(R.string.cancelTimerShootingText));
- }
- }
- });
- }
- }
添加必要的逻辑,点击该按钮可以启动定时拍照和停止定时拍照。现在可以运行程序,测试一下。
总结一下,除了技术层面的东西,从上面的内容应该关注以下几点:
1.关注代码质量,进行适当的封装,可以极大限度的简化代码,同时给后期维护和迭代带来很大的方便。
2.基于一套功能逻辑,如本文的拍照,可以延伸出很多不同的功能,但是要借助于系统以及语言提供的各种机制把积木搭好,则是考验一个人的编码内功,对机制和操作系统的理解是写好代码的内功。
3.再简单的代码开发,也不像想象的那么简单。
拍照大家有点玩烦了,下面一篇开始新的内容——录音。
项目源码点击这里