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

需求分析
在车载系统中,倒车后视一般是属于标配应用,为了能快速响应倒车事件,主要是基于 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!

猜你喜欢

转载自blog.csdn.net/liqianwei1230/article/details/78408954