NXP

车载倒车后视--利用 WindowManager 预览 camera 数据

2019-07-12 12:24发布

需求分析
在车载系统中,倒车后视一般是属于标配应用,为了能快速响应倒车事件,主要是基于 windowmanager 加载 surfaceView 来预览摄像头数据,在前两篇文章中,主要介绍了 WindowManager 和 Camera 的概念,未看过的,可以参考前两文。
Android 使用 WindowManager 实现悬浮窗监控 cpu 温度
Android Camera 开发之基础知识篇 车载系统分类
根据项目的不同,车载导航系统有前装与后装之分,前装是指原车在出厂的时候就带有车载导航系统,而后装指的是车载导航系统卖给 4s 店,由用户去购买替换原有的前装导航系统。前装主要是跟车厂合作,汽车上的协议是能获取到的,比如倒车就能通过协议告诉各个终端我要倒车啦,各个终端模块做出相应的回应,比如多媒体停止播放,倒车界面显示倒车画面;而后装主要是卖给 4s 店,获取不到原车的协议(能获取,比较麻烦),像倒车这种是不经过协议通知的,而是由 mcu 监测倒车 GPIO 口,继而知道倒车事件是否发生,从而通知应用做出反应,显示倒车界面。
无论是前装项目还是后装项目,倒车事件都会经过 mcu 处理,继而通知 os 系统,再上报给 app 处理,当 app 收到倒车信号后,就要显示倒车界面。
通过上述分析,倒车画面的显示主要是预览摄像头的数据,首先想到的就是利用 surfaceView 预览 camera 数据;其次为了能快速响应倒车事件,当应用监测到有倒车事件发生时,能立即弹出界面,如果采用 Activity 的方式去做,activity 在启动过程中,会有短暂的黑屏现象,这一点是不能接受的,继而优先采用 WindowManager 来加载界面。 下面就来实现我们的预览画面:
1. 悬浮窗的创建 // windowmanager 要加载的布局 mFloatview = LayoutInflater.from(mContext).inflate(R.layout.surface_activity,null); mLayoutParams = new WindowManager.LayoutParams(); /** * Window type: The boot progress dialog, goes on top of everything * in the world. * In multiuser systems shows on all users' windows. * @hide * 这个 TYPE_BOOT_PROGRESS 是未公开的,能覆盖在状态栏之上 */ mLayoutParams.type = WindowManager.LayoutParams.TYPE_BOOT_PROGRESS; /** * Window flag: hide all screen decorations (such as the status bar) while this window is displayed. * This allows the window to use the entire display space for itself -- * the status bar will be hidden when an app window with this flag set is on the top layer. * A fullscreen window will ignore a value of SOFT_INPUT_ADJUST_RESIZE for the window's softInputMode * field; the window will stay fullscreen and will not resize. This flag can be controlled in your theme through the windowFullscreen attribute; this attribute is automatically set for you in the standard fullscreen themes such as Theme_NoTitleBar_Fullscreen, Theme_Black_NoTitleBar_Fullscreen, Theme_Light_NoTitleBar_Fullscreen, Theme_Holo_NoActionBar_Fullscreen, Theme_Holo_Light_NoActionBar_Fullscreen, Theme_DeviceDefault_NoActionBar_Fullscreen, and Theme_DeviceDefault_Light_NoActionBar_Fullscreen. */ /** * 窗口显示时,隐藏所有的屏幕装饰(例如状态条)。使窗口占用整个显示区域 * 允许窗口扩展到屏幕之外。 */ mLayoutParams.flags =WindowManager.LayoutParams.FLAG_FULLSCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |FLAG_TRANSLUCENT_STATUS |FLAG_TRANSLUCENT_NAVIGATION; mLayoutParams.format = PixelFormat.TRANSLUCENT; Point p = DisplayUtil.getScreenMetrics(mContext); mLayoutParams.x = 0; /** * 如果存在屏幕下方还存在导航栏的话 * y 的值需要更改,更改为 -导航栏高度 */ mLayoutParams.y = 0; /** * 设置长和宽 * 如果统一设置为 MATCH_PARENT 会引发不全屏的问题,如果当前界面存在状态栏,那么设置MATCH_PARENT , * 它的高度不是设置的高度,而是 (设备的高度-状态栏的高度) */ if (p.x == 1024) { mLayoutParams.width = 1024; mLayoutParams.height = 600; } else if (p.x >= 1200) { mLayoutParams.width = 1280; mLayoutParams.height = 480; } else { mLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT; mLayoutParams.height = WindowManager.LayoutParams.MATCH_PARENT; } /** * 同理,要设置全屏,如果设置为 LEFT | TOP,参考系仍然在状态栏之下,不能覆盖状态栏 */ / mLayoutParams.gravity = Gravity.BOTTOM; mWindowManager.addView(mFloatview, mLayoutParams); mSurfaceView = (CameraSurfaceView) mFloatview.findViewById(R.id.sufaceView); 2.自定义 CameraSurfaceView ,并在布局中引用 public class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback { private static final String TAG = "Bradley"; Context mContext; SurfaceHolder mSurfaceHolder; private Camera mCamera; public CameraSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; mSurfaceHolder = getHolder(); mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); mSurfaceHolder.setFormat(PixelFormat.TRANSLUCENT);//translucent半透明 transparent透明 mSurfaceHolder.addCallback(this); } public CameraSurfaceView(Context context) { super(context); } @Override public void surfaceCreated(SurfaceHolder holder) { // TODO Auto-generated method stub Log.i(TAG, "surfaceCreated..."); startCamera(holder); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // TODO Auto-generated method stub if (holder.getSurface() == null){ // preview surface does not exist return; } // stop preview before making changes try { mCamera.stopPreview(); } catch (Exception e){ // ignore: tried to stop a non-existent preview } // set preview size and make any resize, rotate or // reformatting changes here // start preview with new settings try { mCamera.setPreviewDisplay(holder); mCamera.startPreview(); } catch (Exception e){ } } @Override public void surfaceDestroyed(SurfaceHolder holder) { // TODO Auto-generated method stub Log.i(TAG, "surfaceDestroyed..."); if(mCamera!=null) mCamera.release(); } public SurfaceHolder getSurfaceHolder(){ Log.i(TAG, "getSurfaceHolder...mSurfaceHolder = " + mSurfaceHolder); return mSurfaceHolder; } /** Check if this device has a camera */ private boolean checkCameraHardware(Context context) { if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){ // this device has a camera return true; } else { // no camera on this device return false; } } /** A safe way to get an instance of the Camera object. */ private Camera getCameraInstance(){ Camera c = null; try { c = Camera.open(); // attempt to get a Camera instance } catch (Exception e){ // Camera is not available (in use or does not exist) } return c; // returns null if camera is unavailable } private void startCamera(SurfaceHolder holder) { if(checkCameraHardware(mContext)){ mCamera = getCameraInstance(); if(mCamera!=null){ try { mCamera.setPreviewDisplay(holder); mCamera.startPreview(); } catch (IOException e) { e.printStackTrace(); } } } } 代码分析:
当 CameraSurfaceView 被加载进视图后,会进行 mSurfaceHolder 的配置,同时对 SurfaceView 进行设置监听,但 SurfaceView 被创建时,打开摄像头,并对摄像头画面预览。 总结
倒车后视整体代码比较简单,但难点在于全屏显示。
1. type 的值不设置为 TYPE_SYSTEM_ALERT,主要是为了兼容考虑,及时能覆盖在状态栏之上,但在视频全屏时,有些系统状态栏会先显示出来,再缩回去,也就是状态栏会有抖动现象。
2. gravity 设置的值为 Gravity.BOTTOM ,以底部右下角为参考点,这样布局的时候能将 状态栏顶上去。
happy a nice day!