マサルの備忘録

ハイオク仕様のトルクフルな備忘録

AndroidでQRコードを読むアプリをつくったんだけど、NPEが起こったので泣きそうになってる

スポンサーリンク

ガッしたくなります。

前回まで。

AndroidでQRコードを読むアプリをつくった - マサルの備忘録

Cameraオブジェクトは使い終わったらリリース

してるはずだったんですが。
Camera.open()時に、他のアプリがrelease()してないとErrorが発生します。
それ起因でNPEが発生しててちょっと面倒でした。

明示的にrelease()してみた

もともとSurfaceHolder.CallbackのsurfaceDestroyed()で、

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		if (mCamera != null) {
			mCamera.stopPreview();
			mCamera.release();
			mCamera = null;
		}
	}

release()をしてたものの、Activity回転時やアプリ再起動時とかになぜかリリースされず。
アプリを縦固定とかにしてもいいんですが、今回は以下のようにしました。

  • ActivityからSurfaceViewを明示的に殺す
  • 画面回転時などはSurfaceViewを明示的に再生成する。

結果的にいい感じ

onResume()、onPause()で若干時間がかかってる感はありますが、まぁご愛嬌。

クソコードはこちら

MainActivity.java

package com.masaru.qrcodereader;

import android.app.Activity;
import android.os.Bundle;
import android.view.SurfaceView;
import android.widget.RelativeLayout;

public class MainActivity extends Activity {

	private SurfaceView mSurfaceView;

	private RelativeLayout mRelativeLayout;

	private SurfaceViewCallback mCallback;

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

		setContentView(R.layout.activity_main);
	}

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

		mRelativeLayout = (RelativeLayout) findViewById(R.id.relative_layout);
		mSurfaceView = new SurfaceView(this);
		mRelativeLayout.addView(mSurfaceView);

		mCallback = new SurfaceViewCallback(this, mSurfaceView);
		mSurfaceView.getHolder().addCallback(mCallback);
	}

	public void onPause() {
		super.onPause();

		mCallback.destroy();
		mRelativeLayout.removeAllViews();
	}
}

SurfaceViewCallback.java

package com.masaru.qrcodereader;

import java.io.IOException;
import java.util.List;

import android.app.Activity;
import android.hardware.Camera;
import android.hardware.Camera.AutoFocusCallback;
import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.Size;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.google.zxing.BinaryBitmap;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.PlanarYUVLuminanceSource;
import com.google.zxing.Reader;
import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer;

public class SurfaceViewCallback implements SurfaceHolder.Callback {

	private Camera mCamera = null;

	private RelativeLayout mRelativeLayout;

	private SurfaceView mSurfaceView;

	private TextView mTextView;

	private Activity mActivity;

	public SurfaceViewCallback(Activity activity, SurfaceView surfaceView) {
		super();
		mActivity = activity;
		mSurfaceView = surfaceView;
		mCamera = Camera.open();
	}

	public void destroy() {
		if (mCamera != null) {
			mCamera.stopPreview();
			mCamera.release();
			mCamera = null;
		}
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		if (mCamera != null) {
			mCamera.stopPreview();
			mCamera.release();
			mCamera = null;
		}
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		mTextView = (TextView) (mActivity.findViewById(R.id.result_textview));
		mRelativeLayout = (RelativeLayout) (mActivity
				.findViewById(R.id.relative_layout));

		setParameters();
		setDisplayOrientation();
		setSurfaceViewSize();

		try {
			mCamera.setPreviewDisplay(holder);
		} catch (IOException e) {
			mCamera.release();
			mCamera = null;
			e.printStackTrace();
		}

		mSurfaceView.setOnClickListener(mOnClickListener);
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width,
			int height) {
		if (mCamera != null) {
			mCamera.startPreview();
		}
	}

	private void setParameters() {
		Camera.Parameters parameters = mCamera.getParameters();
		List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();
		Camera.Size size = sizes.get(0);
		parameters.setPreviewSize(size.width, size.height);
		mCamera.setParameters(parameters);
		mTextView.setText(size.width + " x " + size.height);
	}

	private void setDisplayOrientation() {
		int degree;

		switch (mActivity.getWindowManager().getDefaultDisplay().getRotation()) {
		case Surface.ROTATION_0:
			degree = 0;
			break;
		case Surface.ROTATION_90:
			degree = 90;
			break;
		case Surface.ROTATION_180:
			degree = 180;
			break;
		case Surface.ROTATION_270:
			degree = 270;
			break;
		default:
			degree = 0;
		}

		CameraInfo info = new CameraInfo();
		Camera.getCameraInfo(0, info);
		mCamera.setDisplayOrientation((info.orientation - degree + 360) % 360);
	}

	private void setSurfaceViewSize() {
		float relativeWidth = mRelativeLayout.getWidth();
		float relativeHeight = mRelativeLayout.getHeight();
		float previewWidth;
		float previewHeight;

		Size size = mCamera.getParameters().getSupportedPreviewSizes().get(0);
		if (relativeHeight > relativeWidth) {
			previewHeight = size.width;
			previewWidth = size.height;
		} else {
			previewHeight = size.height;
			previewWidth = size.width;
		}

		int surfaceWidth;
		int surfaceHeight;
		if (previewHeight / relativeHeight > previewWidth / relativeWidth) {
			surfaceWidth = (int) (previewWidth * relativeHeight / previewHeight);
			surfaceHeight = (int) (relativeHeight);
		} else {
			surfaceWidth = (int) (relativeWidth);
			surfaceHeight = (int) (previewHeight * relativeWidth / previewWidth);
		}

		RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
				surfaceWidth, surfaceHeight);
		params.addRule(RelativeLayout.CENTER_IN_PARENT);
		mSurfaceView.setLayoutParams(params);
	}

	View.OnClickListener mOnClickListener = new OnClickListener() {

		@Override
		public void onClick(View v) {
			mCamera.autoFocus(new AutoFocusCallback() {

				@Override
				public void onAutoFocus(boolean success, Camera camera) {
					if (success) {
						camera.setOneShotPreviewCallback(mPreviewCallback);
					}
				}
			});
		}
	};

	Camera.PreviewCallback mPreviewCallback = new PreviewCallback() {

		@Override
		public void onPreviewFrame(byte[] data, Camera camera) {
			TextView mTextView = (TextView) mActivity
					.findViewById(R.id.result_textview);
			int previewWidth = camera.getParameters().getPreviewSize().width;
			int previewHeight = camera.getParameters().getPreviewSize().height;

			PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(
					data, previewWidth, previewHeight, 0, 0, previewWidth,
					previewHeight, false);
			BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));

			Reader reader = new MultiFormatReader();
			Result result = null;
			try {
				result = reader.decode(bitmap);
				String text = result.getText();
				mTextView.setText(text);
			} catch (Exception e) {
				mTextView.setText("reading...");
			}
		}
	};