Camera.open() 阻塞 UI 线程

Camera.open() blocking UI thread

我查看了我能找到的关于此的所有 SO 文章,但 none 的解决方案对我有用。

调用 Camera.open() 时,UI 线程被阻塞时会有 3 秒(或多或少)的延迟。我试图把它放在后台线程中。我目前正在使用找到的解决方案 here(粘贴在下面),但是 '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);
                        }
                    });
                }
            }
        }
    }
}

你能不能继续你的线程并调用 ui 命令组和 yourContext.runOnUiThread() 方法?然后将任何阻塞代码置于后台,等待相机准备就绪,并从后台线程更新 ui。

例如:

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

那么你可以简单地new CameraHandlerThread().start();