转载: Android多媒体应用开发系列(二) 项目重构以及连拍和定时自动拍照的实现

本文通过封装Camera操作,实现了拍照功能的简化与扩展,包括连拍及定时拍照等功能。

转载:http://blog.youkuaiyun.com/yjp19871013/article/details/53461774

上一篇中完成了拍照功能,但是美中不足的是,相机的操作分散在MainActivity类的各个角落,包括回调的设置等等,为了方便以后的使用,应当将我们的Camera封装。现在先进行一轮简单的重构,对Camera进行必要的封装。

       我们创建一个MyCamera类,使用has-a的方式,在类内包含一个Camera的成员变量,借助于该成员变量,我们将上层Activity对Camera的调用封装为接口。

       封装后的MyCamera类如下

  1. package com.yjp.camera;  
  2.   
  3. import android.hardware.Camera;  
  4. import android.os.Environment;  
  5. import android.view.SurfaceHolder;  
  6.   
  7. import java.io.FileNotFoundException;  
  8. import java.io.FileOutputStream;  
  9. import java.io.IOException;  
  10. import java.util.Date;  
  11. import java.util.List;  
  12.   
  13. @SuppressWarnings("deprecation")  
  14. public class MyCamera implements Camera.PictureCallback, Camera.ShutterCallback {  
  15.     private Camera mCamera;  
  16.   
  17.     public void openCamera() {  
  18.         if (null == mCamera) {  
  19.             mCamera = Camera.open();  
  20.         }  
  21.     }  
  22.   
  23.     public void releasecamera() {  
  24.         if (null != mCamera) {  
  25.             mCamera.release();  
  26.             mCamera = null;  
  27.         }  
  28.     }  
  29.   
  30.     public void takePicture() {  
  31.         mCamera.takePicture(thisnullthis);  
  32.     }  
  33.   
  34.     public void onSurfaceCreated(SurfaceHolder holder) {  
  35.         try {  
  36.             //surface创建成功能够拿到回调的holder  
  37.             //holder中包含有成功创建的Surface  
  38.             //从而交给摄像机预览使用  
  39.             mCamera.setPreviewDisplay(holder);  
  40.         } catch (IOException e) {  
  41.             e.printStackTrace();  
  42.         }  
  43.     }  
  44.   
  45.     public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {  
  46.         //surface的尺寸发生变化  
  47.         //配置预览参数,如分辨率等  
  48.         //这里使用的分辨率简单选取了支持的预览分辨率的第一项  
  49.         //网上可以查找对应的优选算法  
  50.         Camera.Parameters parameters = mCamera.getParameters();  
  51.         List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();  
  52.         Camera.Size selected = sizes.get(0);  
  53.         parameters.setPreviewSize(selected.width, selected.height);  
  54.         parameters.setPictureSize(selected.width, selected.height);  
  55.   
  56.         //给摄像机设置参数,开始预览  
  57.         mCamera.setParameters(parameters);  
  58.         mCamera.startPreview();  
  59.     }  
  60.   
  61.     public void onSurfaceDestroyed(SurfaceHolder holder) {  
  62.   
  63.     }  
  64.   
  65.     @Override  
  66.     public void onPictureTaken(byte[] data, Camera camera) {  
  67.         try {  
  68.             FileOutputStream out;  
  69.             String filename = new Date().getTime() + ".jpg";  
  70.             String filePathname = Environment.getExternalStorageDirectory() + "/"  
  71.                     + Environment.DIRECTORY_PICTURES + "/" + filename;  
  72.             out = new FileOutputStream(filePathname);  
  73.             out.write(data);  
  74.             out.flush();  
  75.             out.close();  
  76.   
  77.             //重新启动预览  
  78.             mCamera.startPreview();  
  79.         } catch (FileNotFoundException e) {  
  80.             e.printStackTrace();  
  81.         } catch (IOException e) {  
  82.             e.printStackTrace();  
  83.         }  
  84.     }  
  85.   
  86.     @Override  
  87.     public void onShutter() {  
  88.     }  
  89. }  

       可以看到,就是使用封装函数的重构方法,将上一篇中Camera的操作分别封装,如果还想进行进一步的重构,可以为我们的MyCamera创建一个抽象基类CameraBase类,然后将onSurfaceCreated,onSurfaceChanged和onSurfaceDestoryed三个函数定义为抽象方法,由MyCamera继承CameraBase,然后实现三个方法,因为这三个方法针对不同的Camera实现可能会不同。另外,在takePicture中可以加入路径参数,指示相机将文件保存在哪里,由于暂时我们没有这个需求,先不调整接口,需要时进一步重构。下面看一下重构之后的MainActivity类:

  1. package com.yjp.takepicture;  
  2.   
  3. import android.os.Bundle;  
  4. import android.support.v7.app.AppCompatActivity;  
  5. import android.view.SurfaceHolder;  
  6. import android.view.SurfaceView;  
  7. import android.view.View;  
  8. import android.widget.Button;  
  9.   
  10. import com.yjp.camera.MyCamera;  
  11.   
  12. @SuppressWarnings("deprecation")  
  13. public class MainActivity extends AppCompatActivity  
  14.         implements SurfaceHolder.Callback {  
  15.   
  16.     private MyCamera mCamera = new MyCamera();  
  17.   
  18.     @Override  
  19.     protected void onCreate(Bundle savedInstanceState) {  
  20.         super.onCreate(savedInstanceState);  
  21.         setContentView(R.layout.activity_main);  
  22.   
  23.         //配置SurfaceView  
  24.         //setType使用外来数据源  
  25.         //设置SurfaceHolder.Callback  
  26.         SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surfaceView);  
  27.         SurfaceHolder surfaceHolder = surfaceView.getHolder();  
  28.         surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);  
  29.         surfaceHolder.addCallback(this);  
  30.   
  31.         Button takePictureButton = (Button) findViewById(R.id.takePictureButton);  
  32.         takePictureButton.setOnClickListener(new View.OnClickListener() {  
  33.             @Override  
  34.             public void onClick(View v) {  
  35.                 mCamera.takePicture();  
  36.             }  
  37.         });  
  38.     }  
  39.   
  40.     @Override  
  41.     protected void onStart() {  
  42.         super.onStart();  
  43.         mCamera.openCamera();  
  44.     }  
  45.   
  46.     @Override  
  47.     protected void onStop() {  
  48.         mCamera.releasecamera();  
  49.         super.onStop();  
  50.     }  
  51.   
  52.     @Override  
  53.     public void surfaceCreated(SurfaceHolder holder) {  
  54.         mCamera.onSurfaceCreated(holder);  
  55.     }  
  56.   
  57.     @Override  
  58.     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {  
  59.         mCamera.onSurfaceChanged(holder, format, width, height);  
  60.     }  
  61.   
  62.     @Override  
  63.     public void surfaceDestroyed(SurfaceHolder holder) {  
  64.         mCamera.onSurfaceDestroyed(holder);  
  65.     }  
  66. }  

和上一篇文章中的类比较,已经简洁了很多了,Activity中只剩下了界面的跳转逻辑,所用功能性的内容,全部委托给了我们实现的MyCamera类。

        下面就可以基于我们封装后的Camera进行连拍的开发,连拍无非就是点击连拍时,多次调用takePicture,在MyCamera中添加如下代码:

  1. public void continuousShooting(final int shootingTimes) {  
  2.     Executor executor = Executors.newSingleThreadExecutor();  
  3.     executor.execute(new Runnable() {  
  4.         @Override  
  5.         public void run() {  
  6.             for (int i = 0; i < shootingTimes; i++) {  
  7.                 takePicture();  
  8.                 try {  
  9.                     SECONDS.sleep(1);  
  10.                 } catch (InterruptedException e) {  
  11.                     e.printStackTrace();  
  12.                 }  
  13.             }  
  14.         }  
  15.     });  
  16. }  

逻辑很简单,启动线程,然后多次调用拍照,但要注意添加休眠,否则可能无法进行连拍,没有看底层代码,怀疑是硬件响应需要时间。另外,严谨的做法应该保证线程可取消,并做相应的回收处理,那个时候可以使用ExecutorService执行线程和取消执行。

        在MainAvtivity中添加一个Button,连拍5张,布局及相关代码如下:

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     xmlns:tools="http://schemas.android.com/tools"  
  4.     android:id="@+id/activity_main"  
  5.     android:layout_width="match_parent"  
  6.     android:layout_height="match_parent"  
  7.     android:paddingBottom="@dimen/activity_vertical_margin"  
  8.     android:paddingLeft="@dimen/activity_horizontal_margin"  
  9.     android:paddingRight="@dimen/activity_horizontal_margin"  
  10.     android:paddingTop="@dimen/activity_vertical_margin"  
  11.     tools:context="com.yjp.takepicture.MainActivity">  
  12.   
  13.     <SurfaceView  
  14.         android:id="@+id/surfaceView"  
  15.         android:layout_width="match_parent"  
  16.         android:layout_height="match_parent" />  
  17.   
  18.     <LinearLayout  
  19.         android:layout_width="wrap_content"  
  20.         android:layout_height="wrap_content"  
  21.         android:orientation="horizontal"  
  22.         android:layout_centerInParent="true"  
  23.         android:layout_alignBottom="@id/surfaceView">  
  24.   
  25.         <Button  
  26.             android:id="@+id/takePictureButton"  
  27.             android:layout_width="wrap_content"  
  28.             android:layout_height="wrap_content"  
  29.             android:text="@string/takePictureButtonText"/>  
  30.   
  31.         <Button  
  32.             android:id="@+id/continuousShootingButton"  
  33.             android:layout_width="wrap_content"  
  34.             android:layout_height="wrap_content"  
  35.             android:text="@string/continuousShootingText"/>  
  36.     </LinearLayout>  
  37.   
  38. </RelativeLayout>  

添加了一个LinearLayout并在其中添加一个Button,MainActivity类的代码修改如下:

  1. public class MainActivity extends AppCompatActivity  
  2.         implements SurfaceHolder.Callback {  
  3.   
  4.     private final int CONTINUOUS_SHOOTING_TIMES = 5;  
  5.   
  6.     @Override  
  7.     protected void onCreate(Bundle savedInstanceState) {  
  8.         ...  
  9.         Button continuousShootingButton = (Button) findViewById(R.id.continuousShootingButton);  
  10.         continuousShootingButton.setOnClickListener(new View.OnClickListener() {  
  11.             @Override  
  12.             public void onClick(View v) {  
  13.                 mCamera.continuousShooting(CONTINUOUS_SHOOTING_TIMES);  
  14.             }  
  15.         });  
  16.     }  
  17.     ...  
  18. }  
运行代码测试即可。

        可以看到,封装后的代码,添加新功能如此简单,只是重新定义一个函数即可,下面再看看定时拍照功能。无非设置定时器,在定时器中调用MyCamera.takePicture()。下面完成定时拍照,在MyCamera中添加如下代码:

  1. public class MyCamera implements Camera.PictureCallback, Camera.ShutterCallback {  
  2.   
  3.     private ScheduledThreadPoolExecutor mTimerShootingExecutor;  
  4.   
  5.     public void releaseCamera() {  
  6.         if (null != mCamera) {  
  7.             if (isTimerShootingStart()) {  
  8.                 stopTimerShooting();  
  9.             }  
  10.             ...  
  11.         }  
  12.     }  
  13.       
  14.     ...  
  15.   
  16.     public synchronized void startTimerShooting(int timeMs) {  
  17.         if (null == mTimerShootingExecutor) {  
  18.             mTimerShootingExecutor = new ScheduledThreadPoolExecutor(1);  
  19.             mTimerShootingExecutor.scheduleWithFixedDelay(new Runnable() {  
  20.                 @Override  
  21.                 public void run() {  
  22.                     takePicture();  
  23.                 }  
  24.             }, 0, timeMs, TimeUnit.MILLISECONDS);  
  25.         }  
  26.     }  
  27.   
  28.     public synchronized void stopTimerShooting() {  
  29.         if (null != mTimerShootingExecutor) {  
  30.             mTimerShootingExecutor.shutdown();  
  31.             mTimerShootingExecutor = null;  
  32.         }  
  33.     }  
  34.   
  35.     public synchronized boolean isTimerShootingStart() {  
  36.         if (null != mTimerShootingExecutor) {  
  37.             return true;  
  38.         } else {  
  39.             return false;  
  40.         }  
  41.     }  
  42.   
  43.     ...  
  44. }  

       上面注意,考虑线程安全,函数采用了同步机制,另外使用ScheduledThreadPoolExecutor实现定时运行任务,可以网上查阅资料,了解一下,Java目前不太建议采用TimerTask,主要是由于Timertask缺少必要的异常机制,同时操控性较差。下面是MainActivity添加的内容:

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     xmlns:tools="http://schemas.android.com/tools"  
  4.     android:id="@+id/activity_main"  
  5.     android:layout_width="match_parent"  
  6.     android:layout_height="match_parent"  
  7.     android:paddingBottom="@dimen/activity_vertical_margin"  
  8.     android:paddingLeft="@dimen/activity_horizontal_margin"  
  9.     android:paddingRight="@dimen/activity_horizontal_margin"  
  10.     android:paddingTop="@dimen/activity_vertical_margin"  
  11.     tools:context="com.yjp.takepicture.MainActivity">  
  12.   
  13.     <SurfaceView  
  14.         android:id="@+id/surfaceView"  
  15.         android:layout_width="match_parent"  
  16.         android:layout_height="match_parent" />  
  17.   
  18.     <LinearLayout  
  19.         android:layout_width="wrap_content"  
  20.         android:layout_height="wrap_content"  
  21.         android:orientation="horizontal"  
  22.         android:layout_centerInParent="true"  
  23.         android:layout_alignBottom="@id/surfaceView">  
  24.   
  25.         <Button  
  26.             android:id="@+id/takePictureButton"  
  27.             android:layout_width="wrap_content"  
  28.             android:layout_height="wrap_content"  
  29.             android:text="@string/takePictureButtonText"/>  
  30.   
  31.         <Button  
  32.             android:id="@+id/continuousShootingButton"  
  33.             android:layout_width="wrap_content"  
  34.             android:layout_height="wrap_content"  
  35.             android:text="@string/continuousShootingText"/>  
  36.   
  37.         <Button  
  38.             android:id="@+id/timerShootingButton"  
  39.             android:layout_width="wrap_content"  
  40.             android:layout_height="wrap_content"  
  41.             android:text="@string/timerShootingText"/>  
  42.     </LinearLayout>  
  43.   
  44. </RelativeLayout>  

添加了一个新的按钮

  1. public class MainActivity extends AppCompatActivity  
  2.         implements SurfaceHolder.Callback {  
  3.   
  4.     private final int TIMER_SHOOTING_INTERVAL_MS = 5000;  
  5.     ...  
  6.   
  7.     @Override  
  8.     protected void onCreate(Bundle savedInstanceState) {  
  9.         ...  
  10.   
  11.         Button timerShootingButton = (Button) findViewById(R.id.timerShootingButton);  
  12.         timerShootingButton.setOnClickListener(new View.OnClickListener() {  
  13.             @Override  
  14.             public void onClick(View v) {  
  15.                 Button button = (Button) v;  
  16.   
  17.                 if (mCamera.isTimerShootingStart()) {  
  18.                     mCamera.stopTimerShooting();  
  19.                     button.setText(getResources().getString(R.string.timerShootingText));  
  20.                 } else {  
  21.                     mCamera.startTimerShooting(TIMER_SHOOTING_INTERVAL_MS);  
  22.                     button.setText(getResources().getString(R.string.cancelTimerShootingText));  
  23.                 }  
  24.             }  
  25.         });  
  26.     }  
  27. }  

添加必要的逻辑,点击该按钮可以启动定时拍照和停止定时拍照。现在可以运行程序,测试一下。

        总结一下,除了技术层面的东西,从上面的内容应该关注以下几点:

        1.关注代码质量,进行适当的封装,可以极大限度的简化代码,同时给后期维护和迭代带来很大的方便。

        2.基于一套功能逻辑,如本文的拍照,可以延伸出很多不同的功能,但是要借助于系统以及语言提供的各种机制把积木搭好,则是考验一个人的编码内功,对机制和操作系统的理解是写好代码的内功。

        3.再简单的代码开发,也不像想象的那么简单。

拍照大家有点玩烦了,下面一篇开始新的内容——录音。

项目源码点击这里

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值