【问题标题】:Two SurfaceView with Thread - Camera Preview Cannot Resume两个带有线程的 SurfaceView - 相机预览无法恢复
【发布时间】:2013-10-23 16:58:34
【问题描述】:

我正在创建一个在相机预览上显示动态图片的应用。我这样做的方式是在我的主要活动内的框架布局中添加两个 SurfaceView,一个持有相机预览,一个持有我的动态图片。所以基本上有3个公共类,动图类里面有1个内线程类来控制动画。

启动应用程序时效果很好 - 相机正在预览并且图片正在移动。但是如果我暂停活动,通过转到主屏幕或通过单击图片重定向到另一个活动,然后继续,相机预览会变黑。奇怪的部分是如果我旋转手机进入不同的模式(横向/纵向),一切恢复正常。

我已经阅读了几篇关于相机无法恢复的帖子,但解决方案都是关于打开相机。经过检查,我很确定我的问题与相机实例无关。实际上,如果我通过转到主屏幕来暂停活动,当我恢复时,相机会出现一秒钟然后黑屏。

我一直在尝试各种事情,包括在 OnPause() 中从我的布局中删除所有视图,并在添加视图时指定索引号。但唯一取得一点进展的方法是我在下面的代码块中注释掉画布锁。没有锁,图片会随机移动,但相机可以恢复。事实上,如果我忽略所有关于线程的事情,只显示一张静态图片,相机也可以正常工作。所以我感觉我的线程有问题,但我无法弄清楚。

这是线程的运行方法:

    public void run() {
        Canvas canvas;
        while (isRunning) {         //When setRunning(false) occurs, isRunning is 
            canvas = null;          //set to false and loop ends, stopping thread
            try {
                canvas = surfaceHolder.lockCanvas(null);        //Lock
                        synchronized (surfaceHolder) {
                                //Insert methods to modify positions of items in onDraw()
                                animation();
                                postInvalidate();
                            }
            } finally {
                if (canvas != null) {
                    surfaceHolder.unlockCanvasAndPost(canvas);  //Unlock
                            }
                    }
        }
    }

这是开始线程的部分:

    public void surfaceCreated(SurfaceHolder arg0) {
        setWillNotDraw(false); //Allows us to use invalidate() to call onDraw()

        thread = new BubbleThread(getHolder(), this);   //Start the thread that
        thread.setRunning(true);                        //will make calls to 
        thread.start();                                 //onDraw()
    }

这是完成线程的部分:

    public void surfaceDestroyed(SurfaceHolder arg0) {
        try {
            thread.setRunning(false);                //Tells thread to stop
        thread.join();                              //Removes thread from mem.
        } catch (InterruptedException e) {}
    }

[更新]主要活动代码:

    public class MainActivity extends Activity {
    ... // Declarations

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            /* Adjust app settings */
            ...

            //Load();
        }

        public void Load(){
            /* Try to get the camera */
            Camera c = getCameraInstance();

            /* If the camera was received, create the app */
            if (c != null){
                // Create the parent layout to layer the
                // camera preview and bubble layer
                parentLayout = new FrameLayout(this);
                parentLayout.setLayoutParams(new LayoutParams(
                    LayoutParams.MATCH_PARENT,
                    LayoutParams.MATCH_PARENT));

                // Create a new camera view and add it to the layout
                cameraPreview = new CameraPreview(this, c);
                parentLayout.addView(cameraPreview, 0);

                // Create a new draw view and add it to the layout
                bubbleLayer = new BubbleLayer(this);
                parentLayout.addView(bubbleLayer, 1);

                // Set the layout as the apps content view 
                setContentView(parentLayout);
            }
            /* If the camera was not received, close the app */
            else {
                Toast toast = Toast.makeText(getApplicationContext(), 
                    "Unable to find camera. Closing.", Toast.LENGTH_SHORT);
                    toast.show();
                finish();
            }
        }

        /** A safe way to get an instance of the Camera object. */
        /** This method is strait from the Android API */
        public static Camera getCameraInstance(){
            Camera c = null;
            try {
                // Attempt to get a Camera instance
                c = Camera.open();
            }
            catch (Exception e){
                // Camera is not available (in use or does not exist)
                e.printStackTrace();
            }
            return c;
        }

        /* Override the onPause method so that we 
        * can release the camera when the app is closing.
        */
        @Override
        protected void onPause() {
            super.onPause();
            if (cameraPreview != null){
                cameraPreview.onPause();
                cameraPreview = null;
            }
        }

        /* We call Load in our Resume method, because 
        * the app will close if we call it in onCreate
        */
        @Override 
        protected void onResume(){
            super.onResume();
            Load();
        }
    }

[/更新]

[UPDATE2]相机预览代码:

    public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
        ... // Declarations
        public CameraPreview(Context context, Camera camera) {
            super(context);
            this.context = context;
            mCamera = camera;
            // Install a SurfaceHolder.Callback so we get notified when the
            // underlying surface is created and destroyed.
            mHolder = getHolder();
            mHolder.addCallback(this);
            // deprecated setting, but required on Android versions prior to 3.0
            mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        }
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width,
        int height) {
            if (mHolder.getSurface() == null){
                // preview surface does not exist
                return;
            }

            Camera.Parameters parameters = mCamera.getParameters();
            Size bestSize = getBestSize(parameters.getSupportedPreviewSizes(),
                    width,height);
            parameters.setPreviewSize(bestSize.width, bestSize.height);
            mCamera.setParameters(parameters);
            // 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(mHolder);
                setCameraDisplayOrientation();
                mCamera.startPreview();
            } catch (Exception e){
                Log.d("CameraView", "Error starting camera preview: "
                    + e.getMessage());
            }
        }

        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            // The Surface has been created, now tell the
            // camera where to draw the preview.
            try {
                mCamera.setPreviewDisplay(holder);
                mCamera.startPreview();
            } catch (IOException e) {
                Log.d("CameraView", "Error setting camera preview: "
                    + e.getMessage());
            }
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            // empty. Take care of releasing the Camera preview in your activity.
        }

        /* Find the best size for camera */
        private Size getBestSize(List<Size> sizes, int w, int h) {
            final double ASPECT_TOLERANCE = 0.05;
            double targetRatio = (double) w / h;
            if (sizes == null) return null;

            Size bestSize = null;
            double minDiff = Double.MAX_VALUE;

            int targetHeight = h;

            // Try to find an size match aspect ratio and size
            for (Size size : sizes) {
                double ratio = (double) size.width / size.height;
                if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
                if (Math.abs(size.height - targetHeight) < minDiff) {
                    bestSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                }
            }

            // Cannot find the one match the aspect ratio, ignore the requirement
            if (bestSize == null) {
                minDiff = Double.MAX_VALUE;
                for (Size size : sizes) {
                    if (Math.abs(size.height - targetHeight) < minDiff) {
                        bestSize = size;
                        minDiff = Math.abs(size.height - targetHeight);
                    }
                }
            }
            return bestSize;
        }


        private void setCameraDisplayOrientation() {        
            if (mCamera == null) return;

            Camera.CameraInfo info = new Camera.CameraInfo();
            Camera.getCameraInfo(0, info);
            WindowManager winManager = (WindowManager)
                        context.getSystemService(Context.WINDOW_SERVICE);
        int rotation = winManager.getDefaultDisplay().getRotation();
        int degrees = 0;
        switch (rotation) {
            case Surface.ROTATION_0: degrees = 0; break;
            case Surface.ROTATION_90: degrees = 90; break;
            case Surface.ROTATION_180: degrees = 180; break;
            case Surface.ROTATION_270: degrees = 270; break;
        }

        int result;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360;  // compensate the mirror
        }
        else {  // back-facing
            result = (info.orientation - degrees + 360) % 360;
        }
        mCamera.setDisplayOrientation(result);
        }

    public void onPause() {
        if (mCamera == null) return;
            mCamera.stopPreview();
            mCamera.release();
            mCamera = null;
        }    
    }

[/UPDATE2]

我发现的另一件事是,当我暂停活动时,很少执行解锁。虽然当它被执行时相机仍然没有回来,但这种行为对我来说似乎很奇怪,因为 thread.join() 被执行了所以我认为 finally 块也应该被执行。

抱歉,我无法用更少的词来描述我的问题,但请留下您的任何线索。提前致谢!

【问题讨论】:

  • 您所描述的似乎是,您在onPause 停止了“某事”,该“某事”从onCreate 开始。如果您暂停并恢复应用程序,onPause 代码将运行。如果您旋转屏幕,则重新创建 Activity 并再次运行 onCreate。你能把你的活动代码发给我们吗?
  • @jboi 非常感谢您的回复!我刚刚发布了活动代码。我将在onResume 中启动所有员工,因为根据我的理解,每次调用onCreate 时都会调用onResume。我想我是通过从 CameraPreview 类调用 onPause 来停止 onPause 中的摄像头,该类是为了停止预览和释放摄像头而实现的。
  • 这部分代码似乎是直截了当的。由于onResume 的调用频率与onPause 一样频繁(只要现在抛出异常),这应该没问题。你是如何实现CameryPreview 类的?里面有什么东西,在你再次打开相机之前不会停止并释放吗? (好吧,我现在只是在猜测。我认为找出答案的最好方法是Log 你所有的电话,看看有什么不平衡的)
  • @jboi 我把CameraPreview 类放在[UPDATE2][/UPDATE2] 中。我不认为问题出在这里,因为当我省略线程并简单地绘制静态图片时,相机预览工作得很好。我试过记录很多地方,但我发现的唯一不平衡是在线程类中,unlockCanvasAndPost 大部分时间都没有运行。因此,无论何时暂停,画布都处于锁定状态。这对我来说没有意义,因为 thread.join() 已被执行,所以我认为 finally 块也应该被执行,不是吗?
  • 好问题。 isRunning 变量是否声明为 volatile?这是让 Java 识别值中的变化所必需的。

标签: android multithreading camera surfaceview


【解决方案1】:

我仍然不知道我的程序到底出了什么问题。但是经过研究,我发现 SurfaceView 的 z 顺序不遵循正常规则似乎有些棘手。因此,我没有使用两个 SurfaceView,一个用于相机,一个用于绘图,而是改用一个 SurfaceView 用于两者,现在它工作正常。

但根据我的实验,无论是哪种实现,在屏幕上绘图并开启相机预览无论如何都是一个缓慢的实现,因为线程之间的所有切换。所以应该尽量避免在设计中这样做......

【讨论】:

  • 我认为引入SurfaceTexture正是为了解决这类问题。如果您仍然必须支持Gingerbread..,那就太糟糕了
  • 当相机显示在表面支架上时,尝试使用run 方法将其锁定时会出现错误。你是怎么解决这个问题的?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多