Camera.open() 阻塞UI线程

5
我已经查看了所有与此相关的 SO 文章,但没有任何一种解决方案适用于我。
调用 Camera.open() 时,会有约 3 秒(或更长)的延迟,阻塞 UI 线程。我尝试将其放在后台线程中。我目前使用的解决方案是在这里找到的(粘贴如下),但 'wait' 方法是同步的,因此它也会阻塞 UI 线程。
我想要做的是加载这个片段,在摄像头准备好之前显示一个进度旋转器,然后在屏幕上显示摄像头,但这个延迟让我无法忍受,并且似乎找不到任何真正好的解决方案。
我的片段:
public class BarcodeFinderFragment extends Fragment implements View.OnClickListener, Camera.AutoFocusCallback, Camera.PreviewCallback {

    private static final String CAMERA_THREAD_NAME = "CAMERA_THREAD_NAME";
    private Camera mCamera;

    private CamViewFinder mPreview;
    private Handler autoFocusHandler;
    private boolean previewing = true;
    private Button noScan;
    private Button noBarcode;
    private FrameLayout preview;
    private BarcodeFinderCallback callBack;
    private ImageScanner scanner;

    private CameraHandlerThread mThread = null;


    private BarcodeFinderCallback dummyCallback = new BarcodeFinderCallback() {
        @Override
        public void onNoScanClicked() {

        }

        @Override
        public void onNoBarcodeClicked() {

        }

        @Override
        public void finishActivity() {

        }

        @Override
        public void setActivityResult(Bundle bundle) {

        }

        @Override
        public void showProgressDialog(boolean showProgress) {

        }
    };

    public static BarcodeFinderFragment newInstance() {
        return new BarcodeFinderFragment();
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        try {
            callBack = (BarcodeFinderActivity) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                    + " must implement OnHeadlineSelectedListener");
        }
    }

    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);


    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_barcode_finder, container, false);

        noScan = (Button) view.findViewById(R.id.btnNoScan);
        noBarcode = (Button) view.findViewById(R.id.btnNobarcode);
        preview = (FrameLayout) view.findViewById(R.id.cameraPreview);
        noScan.setOnClickListener(this);
        noBarcode.setOnClickListener(this);

        return view;
    }

    @Override
    public void onResume() {
        super.onResume();

        autoFocusHandler = new Handler();

        //Instance barcode scanner
        scanner = new ImageScanner();
        scanner.setConfig(0, Config.X_DENSITY, 3);
        scanner.setConfig(0, Config.Y_DENSITY, 3);

        openCamera();

        mPreview = new CamViewFinder(getActivity(), mCamera, BarcodeFinderFragment.this, BarcodeFinderFragment.this);
        preview.addView(mPreview);
        callBack.showProgressDialog(false);
    }

    private void getCamera() {
        mCamera = null;
        try {
            mCamera = Camera.open();
        } catch (final Exception e) {
            Log.d("BarcodeFinderFragment", e.toString());
        }
    }

    private void openCamera() {
        if (mThread == null)
            mThread = new CameraHandlerThread(CAMERA_THREAD_NAME);

        synchronized (mThread) {
            mThread.openCamera();
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        releaseCamera();
    }

    @Override
    public void onDetach() {
        super.onDetach();

        callBack = dummyCallback;
    }

    private Runnable doAutoFocus() {
        return new Runnable() {
            @Override
            public void run() {
                if (previewing) {
                    mCamera.autoFocus(BarcodeFinderFragment.this);
                }
            }
        };
    }

    private void releaseCamera() {
        if (mCamera != null) {
            previewing = false;
            mCamera.stopPreview();
            mCamera.setPreviewCallback(null);
            mCamera.release();
            mCamera = null;
        }

        callBack.finishActivity();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btnNoScan:
                callBack.onNoScanClicked();
                break;

            case R.id.btnNobarcode:
                callBack.onNoBarcodeClicked();
                break;
        }
    }

    @Override
    public void onAutoFocus(boolean success, Camera camera) {
        autoFocusHandler.postDelayed(doAutoFocus(), 1000);
    }

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {

        final Camera.Parameters parameters = camera.getParameters();
        final Camera.Size size = parameters.getPreviewSize();

        final Image barcode = new Image(size.width, size.height, "Y800");
        barcode.setData(data);

        final int result = scanner.scanImage(barcode);

        if (result != 0) {
            previewing = false;
            mCamera.setPreviewCallback(null);
            mCamera.stopPreview();

            final SymbolSet syms = scanner.getResults();
            for (final Symbol sym : syms) {
                final Bundle bundle = new Bundle();
                bundle.putString("result", sym.getData());
                bundle.putString("codeType", "" + sym.getType());

                callBack.setActivityResult(bundle);
            }
        }
    }

    public interface BarcodeFinderCallback {
        void onNoScanClicked();

        void onNoBarcodeClicked();

        void finishActivity();

        void setActivityResult(Bundle bundle);

        void showProgressDialog(boolean showProgress);
    }

    private class CameraHandlerThread extends HandlerThread {

        Handler mHandler = null;

        public CameraHandlerThread(String name) {
            super(name);
            callBack.showProgressDialog(true);
            start();

            mHandler = new Handler(getLooper());
        }

        synchronized void notifyCameraOpened() {
            notify();
        }

        void openCamera() {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    getCamera();
                    notifyCameraOpened();
                }
            });

            try {
                wait();
            } catch (InterruptedException e) {
                Log.d("BarcodeFinderFragment", "wait was interrupted");
            }
        }
    }
}

更新

感谢MeetTitan,通过将所有内容保持在后台线程中并在需要时发布到UI,我能够使其非常顺利地运行。以下是可供未来需要的人使用的工作代码 :)

public class BarcodeFinderFragment extends Fragment implements View.OnClickListener {

    private static final String CAMERA_THREAD_NAME = "CAMERA_THREAD_NAME";
    private Camera mCamera;
    private CamViewFinder mPreview;
    private Handler autoFocusHandler;
    private FrameLayout preview;
    private ImageScanner scanner;
    private boolean previewing = true;
    private CameraHandlerThread mThread = null;

    private BarcodeFinderCallback callBack;
    private BarcodeFinderCallback dummyCallback = new BarcodeFinderCallback() {
        @Override
        public void onNoScanClicked() {
        }

        @Override
        public void onNoBarcodeClicked() {
        }

        @Override
        public void finishActivity() {
        }

        @Override
        public void setActivityResult(int resultCode, Bundle bundle) {
        }

        @Override
        public void showProgressDialog(boolean showProgress) {
        }
    };

    public static BarcodeFinderFragment newInstance() {
        return new BarcodeFinderFragment();
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        try {
            callBack = (BarcodeFinderActivity) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                    + " must implement BarcodeFinderCallback");
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_barcode_finder, container, false);

        Button noScan = (Button) view.findViewById(R.id.btnNoScan);
        Button noBarcode = (Button) view.findViewById(R.id.btnNobarcode);
        preview = (FrameLayout) view.findViewById(R.id.cameraPreview);
        noScan.setOnClickListener(this);
        noBarcode.setOnClickListener(this);

        return view;
    }

    @Override
    public void onResume() {
        super.onResume();

        autoFocusHandler = new Handler();

        //Instance barcode scanner
        scanner = new ImageScanner();
        scanner.setConfig(0, Config.X_DENSITY, 3);
        scanner.setConfig(0, Config.Y_DENSITY, 3);

        callBack.showProgressDialog(true);
        openCamera();

    }

    private void openCamera() {
        if (mThread == null) {
            try {
                mThread = new CameraHandlerThread(CAMERA_THREAD_NAME);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        synchronized (mThread) {
            mThread.openCamera();
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        releaseCamera();

        if (mThread != null && mThread.isAlive())
            mThread.interrupt();
    }

    @Override
    public void onDetach() {
        super.onDetach();

        callBack = dummyCallback;
    }

    private void releaseCamera() {
        if (mCamera != null) {
            previewing = false;
            mCamera.stopPreview();
            mCamera.setPreviewCallback(null);
            mCamera.release();
            mCamera = null;
        }

        callBack.finishActivity();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btnNoScan:
                callBack.onNoScanClicked();
                break;

            case R.id.btnNobarcode:
                callBack.onNoBarcodeClicked();
                break;
        }
    }

    public interface BarcodeFinderCallback {
        void onNoScanClicked();

        void onNoBarcodeClicked();

        void finishActivity();

        void setActivityResult(int resultCode, Bundle bundle);

        void showProgressDialog(boolean showProgress);
    }

    private class CameraHandlerThread extends HandlerThread implements Camera.AutoFocusCallback, Camera.PreviewCallback {

        Handler mHandler = null;

        public CameraHandlerThread(String name) throws InterruptedException {
            super(name);
            callBack.showProgressDialog(true);
            start();

            mHandler = new Handler(getLooper());
        }

        void openCamera() {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    mCamera = null;
                    try {
                        mCamera = Camera.open();
                    } catch (final Exception e) {
                        Log.d("BarcodeFinderFragment", e.toString());
                        callBack.setActivityResult(Activity.RESULT_CANCELED, null);
                        interrupt();
                    }
                    notifyCameraOpened();

                    getActivity().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            mPreview = new CamViewFinder(getActivity(), mCamera, CameraHandlerThread.this, CameraHandlerThread.this);
                            preview.addView(mPreview);

                            new Handler().postDelayed(new Runnable() {
                                @Override
                                public void run() {
                                    callBack.showProgressDialog(false);
                                }
                            }, 500);
                        }
                    });
                }
            });
        }

        synchronized void notifyCameraOpened() {
            notify();

        }

        @Override
        public void onAutoFocus(boolean success, Camera camera) {
            autoFocusHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    if (previewing) {
                        getActivity().runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                mCamera.autoFocus(CameraHandlerThread.this);
                            }
                        });
                    }
                }
            }, 1000);
        }

        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {

            final Camera.Parameters parameters = camera.getParameters();
            final Camera.Size size = parameters.getPreviewSize();

            final Image barcode = new Image(size.width, size.height, "Y800");
            barcode.setData(data);

            final int result = scanner.scanImage(barcode);

            if (result != 0) {
                previewing = false;
                mCamera.setPreviewCallback(null);
                mCamera.stopPreview();

                final SymbolSet syms = scanner.getResults();
                for (final Symbol sym : syms) {
                    final Bundle bundle = new Bundle();
                    bundle.putString("result", sym.getData());
                    bundle.putString("codeType", "" + sym.getType());

                    getActivity().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            callBack.setActivityResult(Activity.RESULT_OK, bundle);
                        }
                    });
                }
            }
        }
    }
}

2
嘿,还记得我吗?您将不再需要 notify(),因为没有任何 wait()。您还可以简化线程,对我来说,处理程序有点笨重,适用于我所谓的“静态线程”,即每次执行相同任务的线程。我会使用我在答案中展示的方法,覆盖您的线程的 run() 方法,然后调用 new CameraHandlerThread().start() 会在新线程中运行您的 run() 方法,避免向其发送任何内容,并让它自己完成工作并退出。 - MeetTitan
我已经有那段代码了,只是将其他东西适配到它里面。你完全正确。再次感谢。 - Psest328
1个回答

8

你可以使用yourContext.runOnUiThread()方法来同时执行一组UI命令,以及在后台处理任何阻塞代码、等待相机准备好并从后台线程更新UI。

例如:

private class CameraHandlerThread extends ... {
    public void run() {
        getCamera();
        yourContext.runOnUiThread(new Runnable(){
            public void run()
            {
                ...
            }
        });
    }
}

然后你只需简单地执行 new CameraHandlerThread().start();

我仍然需要等待Camera.open,这样mPreview就不会返回null,而wait()会阻塞。 - Psest328
@WizardKnight,请将其放入您的线程中,以便按顺序运行。不要使用wait(),让camera.open()在后台线程上阻塞,然后在调用camera.open()之后在后台线程中更新mPreview(以及任何其他需要在相机打开后更新的对象)。然后它应该按顺序运行。 - MeetTitan
太疯狂了,但可能会奏效,哈哈。我现在就去试试。 - Psest328
@WizardKnight,这应该可以实现,这是我完成大多数“等待”目标的方式,除非我在等待另一个线程,那么我使用wait()(永远不要在主线程中使用!)。如果您需要代码示例,请告诉我,我一直在尝试偷懒并避免它 :-) - MeetTitan
1
只是让你知道它确实起作用了!我会更新我的原始帖子,以防将来有人需要。非常感谢您的帮助。享受这个检查 :) - Psest328

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接