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

辛苦堆砌,转载请注明出处,谢谢! 


       上一篇中完成了拍照功能,但是美中不足的是,相机的操作分散在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.再简单的代码开发,也不像想象的那么简单。

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

项目源码点击这里


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值