您的位置:首页 > 移动开发 > Android开发

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

2016-12-06 11:14 676 查看
转载:http://blog.csdn.net/yjp19871013/article/details/53461774

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

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

       封装后的MyCamera类如下

[java]
view plain
copy





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类:

[java]
view plain
copy





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中添加如下代码:

[java]
view plain
copy





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张,布局及相关代码如下:

[html]
view plain
copy





<?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:
1077e
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类的代码修改如下:

[java]
view plain
copy





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中添加如下代码:

[java]
view plain
copy





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添加的内容:

[html]
view plain
copy





<?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>  

添加了一个新的按钮

[java]
view plain
copy





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.再简单的代码开发,也不像想象的那么简单。

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

项目源码点击这里
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Android多媒体
相关文章推荐