辛苦堆砌,转载请注明出处,谢谢!
上一篇中完成了拍照功能,但是美中不足的是,相机的操作分散在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.再简单的代码开发,也不像想象的那么简单。
拍照大家有点玩烦了,下面一篇开始新的内容——录音。
项目源码点击这里