主体结构:行车记录仪的代码逻辑全部放在PreviewService中,UI显示也在其中,UI的显示只是在Service中,通过WindowManager实现的,MainActivvity没有什么作用。
//在onCreate中启动服务,在服务里面根据action来显示UI
Intent intent = new Intent(MainActivity.this,
PreviewService.class);
intent.putExtra("setup_ui", true);
startService(intent);
//收到该广播后,将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();
}
}
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;
}
//将摄像头的一帧数据传入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();
}
}
@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);
}
});
}
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();
}
随录的入口和随拍一样,应该可以确切的说,行车记录仪的绝大多数的业务逻辑都在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();
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的处理等