CarRecorder源码分析(一)随拍随录

主体结构:行车记录仪的代码逻辑全部放在PreviewService中,UI显示也在其中,UI的显示只是在Service中,通过WindowManager实现的,MainActivvity没有什么作用。


MainActivity中的业务逻辑:

  • ### 1
//在onCreate中启动服务,在服务里面根据action来显示UI
    Intent intent = new Intent(MainActivity.this,
                PreviewService.class);
        intent.putExtra("setup_ui", true);
        startService(intent);
  • ### 2
//收到该广播后,将activity销毁,该广播在PreviewService中发出,隐藏UI返回到主界面
@Override
        public void onReceive(Context context, Intent intent) {
            String msg = intent.getAction();
            if (msg.equals(ACTION_FINISH_SELF)) {
                if (context instanceof Activity) {
                    ((Activity) context).finish();
                }
            }
        }

一:随拍的流程:

入口是在onStartCommand中,启动随拍的代码如下:

else if (ACTION_GET_PICTURE_SHARE.equals(intent.getAction())) {
            if (checkState_window() > 0) {
                return super.onStartCommand(intent, flags, startId);
            }
            state_window = 2;
            try {
                if (mIsSharing)
                    return Service.START_STICKY_COMPATIBILITY;;
                mIsSharing = true;
                //更新UI,当随拍的时候可以看见类似一个对话框弹出来,实际是用了WindowManager的updateViewLayout方法
                updateViewPosition(SW_SHOW_SHARE_PICTURE);
                recorder.startShotPicture(mShareCallBack);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } 
  • 可以看到,更新完UI之后调用了VideoRecorder的startShotPicture(mShareCallBack)方法,并传入EventCallBack接口的引用;EventCallBack接口的定义如下:
interface IShareCallBack {
    void onVideoComplete(String strFile);
    void onPictureShareComplete(String strFile);
}

public class EventCallBack {
    private IShareCallBack mCallBack; 
    public void onPictureShareComplete(String strFile) {
        mCallBack.onPictureShareComplete(strFile);
    }

    public void onVideoComplete(String strFile) {
        mCallBack.onVideoComplete(strFile);
    }

    public void setCallBack(IShareCallBack callback) {  
        this.mCallBack = callback;  
    }  

}
  • 由上可见在EventCallBack类有三个方法,分别是当随拍完成时的回调,当随录完成时的回调,这两个方法中具体业务的实现,是在IShareCallBack接口中定义的,第三个方法是setCallBack,并传入了一个IShareCallBack对象,传入这个对象的同时,要实现 onVideoComplete(String strFile);onPictureShareComplete(String strFile);两个方法,这两个方法即是最终实现完成随拍随录的业务逻辑的。

  • 继续来看VideoRecorder的startShotPicture(mShareCallBack)做了什么事情

public void startShotPicture(EventCallBack shareCallBack) {
        File fileDir = new File(CAMERA_SHARING_PATH);
        if (!fileDir.exists()) {
            fileDir.mkdirs();
        }
        mIsSharePicture = true;
        persistUtils.setPreviewPushEnable(true);
        mShareCallBack = shareCallBack;

    }
  • 此方法可看出三个有用的信息,①: persistUtils.setPreviewPushEnable(true);即是让onPreviewCallBack的onPreviewFrame开始出数据,出来的数据用于随拍随录等
    ②mShareCallBack = shareCallBack;在VideoRecorder;即将EventCallBack传过来。
    ③mIsSharePicture为true,即当前的状态为随拍的状态,数据从onPreviewFrame出来,再来看onPreviewFrame中做的事情。
//将摄像头的一帧数据传入getSharePictureFile
getSharePictureFile(data);
//getSharePictureFile的实现如下:
private void getSharePictureFile(byte[] data) {
        Camera.Parameters parameters = mCamera.getParameters();
    //定义文件名
        SimpleDateFormat storeDate = new SimpleDateFormat("yyyyMMddHHmmss");
        String times = storeDate.format(new Date(System.currentTimeMillis()));
        mShareFile = CAMERA_SHARING_PATH + times + ".jpg";
        File imageFile = new File(mShareFile);
//将一个byte[]数组变成一张图片,并存储在磁盘中
        VideoEncoder.getFrameJpegFileWithTime(mShareFile, data,
                parameters.getPreviewFormat(), PREVIEW_WIDTH, PREVIEW_HEIGHT, 1f, isRecording());

        mIsSharePicture = false;
        persistUtils.setPreviewPushEnable(false);
        //随拍完成
        mShareCallBack.onPictureShareComplete(mShareFile);
    }

接着我们来看VideoEncoder.getFrameJpegFileWithTime方法具体的实现逻辑

    public static void getFrameJpegFileWithTime(String jpgFile, byte[] srcData,
            int format, int width, int height, float zoom, boolean isrec) {

        File imageFile = new File(jpgFile);
        BufferedOutputStream bos = null;
        try {
            YuvImage yuv = new YuvImage(srcData, format, width, height, null);
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            yuv.compressToJpeg(new Rect(0, 0, width, height), 100, stream);
            Bitmap bitmap = BitmapFactory.decodeByteArray(stream.toByteArray(),
                    0, stream.size());
            stream.close();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
            bitmap = drawStringToBitmap(bitmap,
                    sdf.format(System.currentTimeMillis()), 40);
            Matrix matrix = new Matrix();
            matrix.postScale(zoom, zoom);
            Bitmap newBmp = Bitmap.createBitmap(bitmap, 0, 0,
                    bitmap.getWidth(), bitmap.getHeight(), matrix, true);
                    //将处理后的bitmap转化为jpg图片保存在磁盘中
            newBmp.compress(Bitmap.CompressFormat.JPEG, 80, bos);
            srcData = null;
            bitmap.recycle();
            bitmap = null;
            newBmp.recycle();
            newBmp = null;
            bos.flush();
            bos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
  • 可以看到该方法将传过来的byte数组压缩成一个jpg文件并保存在磁盘中,并在图片的右上角将当前的时间写了进去,处理完成之后调用随拍完成的方法onPictureShareComplete(file),这时界面就将处理后的照片显示出来,1S之后退回到主界面,相关代码如下:
 @Override
            public void onPictureShareComplete(final String strFile) {
                x.task().post(new Runnable() {

                    @Override
                    public void run() {
                        if (pool != null && sourceid != -1)
                            pool.play(sourceid, 1, 1, 0, 0, 1);
                        FileInputStream fs = null;
                        try {
                            fs = new FileInputStream(strFile);
                        } catch (FileNotFoundException e) {
                            e.printStackTrace();
                        }

                        if (fs != null) {
                            Bitmap bitmap = BitmapFactory.decodeStream(fs);
                            mSharePictureView.setImageBitmap(bitmap);
                            mSharePictureView.setVisibility(View.VISIBLE);
                        }

                        mShareFile = strFile;
                        Message msg = new Message();
                        msg.what = 2;
                        handlerShare.sendMessage(msg);

                    }
                });

            }
  • 当随拍的图片处理完毕之后,显示已经处理的图片显示在界面上,显示完成之后通过handlerShare发送消息,我们来看看handler是怎么处理的
else if (msg.what == 2) {
                try {
                    Thread.sleep(1000);
                    updateViewPosition(SW_HIDE_SHARE_PICTURE);
                    if (mIsSharing)
                        sendShareBroadcast("com.cxb.sharepicture", mShareFile);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                closeActivity();
            }
  • 先是将显示的随拍照片隐藏,然后发送一个携带随拍文件路径的广播出去,最后关闭activity,发送出去之后,在Launcher中接收,并通过Mqtt的前台代理将图片发送至服务器。至此,随拍的流程分析完毕

二:随录的流程分析

随录的入口和随拍一样,应该可以确切的说,行车记录仪的绝大多数的业务逻辑都在ostartCommand中,循环录像除外,入口代码如下:

else if (ACTION_GET_VIDEO_SHARE.equals(intent.getAction())) {
            if (checkState_window() > 0) {
                return super.onStartCommand(intent, flags, startId);
            }
            state_window = 3;
            try {
                if (mIsSharing)
                    return Service.START_STICKY_COMPATIBILITY;;
                mIsSharing = true;
                updateViewPosition(SW_SHOW_SHARE_VIDEO);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

在此方法中调用了updateViewPosition,直接更新UI

 //随录时,显示6S的倒计时
                shareTimeLayout.setVisibility(View.VISIBLE);
                //显示底部的取消按钮
                shareButtonLayout.setVisibility(View.VISIBLE);
                   // 在第4秒发送一个检查信号,如果encode在4秒前已结束,则可能由异常引起。如果4前时没结束,但是录制时间没有变化,也可能是encode出现问题
                handlerShare.postDelayed(new Runnable() {

                    @Override
                    public void run() {
                        Message msg = new Message();
                        msg.what = 201601181;
                        handlerShare.sendMessage(msg);
                    }
                }, 4000);
     //该方法启动随录,处理具体的业务逻辑           
                  if (recorder != null)
                    recorder.startEncodeVideo(mShareCallBack);
                    //该方法更新UI每300毫秒更新一下时间
                new Thread(new ThreadShareTime()).start();


  • ### 问题:VideoEncoder的getEncodeTime方法,时间具体是如何计算的,逻辑是怎样的?

recorder.startEncodeVideo(mShareCallBack)方法具体做了哪些事?

public void startEncodeVideo(EventCallBack shareCallBack) {
        //以当前的时间戳为随录文件的名称
        File fileDir = new File(CAMERA_SHARING_PATH);
        if (!fileDir.exists())
            fileDir.mkdirs();
        SimpleDateFormat storeDate = new SimpleDateFormat("yyyyMMddHHmmss");
        String times = storeDate.format(new Date(System.currentTimeMillis()));
        mShareCallBack = shareCallBack;
        //构造方法的作用是初始化VideoReccorder中的参数
        mVideoEncoder = new VideoEncoder(PREVIEW_WIDTH, PREVIEW_HEIGHT, 10, 250000);
        //为视频文件命名
        mShareFile = CAMERA_SHARING_PATH + times + ".mp4";
        //准备编码
        mVideoEncoder.startEncode(mShareFile);
        //允许onPreviewFrame出数据
        persistUtils.setPreviewPushEnable(true);
        if (state == STATE_IDLE)
            new Thread(new ThreadShare()).start();
    }

我们来看看这个方法具体执行了哪些逻辑:①首先是为随录的文件命名;②然后初始化编码VideoRecorder中的参数,即是VideoEnconder的实例化;③startEncode,这个方法是准备开始编码,将参数都设置好,算是初始化的第二步,④ persistUtils.setPreviewPushEnable(true),此方法允许onPreviewFrame开始出数据,有了数据才可以进行编码;⑤new Thread(new ThreadShare()).start(),将摄像头出来的数据丢给VideoEncoder进行编码,编完6S的视频之后交由Mqtt发送至服务器,随录的主要流程至此完结,之后会对循环录像进行一个剖析

阅读代码涉及的知识点:

WindowManager的使用,SurfaceView的生命周期,AnimationDrawable的使用,Bitmap的处理等

你可能感兴趣的:(Android应用开发)