안녕하세요.

안드로이드 스튜디오 java언어로 QR코드 스캐너를 만들 일이 있었는데 제대로 정리를 하기 위해 글을 써봅니다.

 

1. 준비

 

저는 ml kit의 바코드 스캐닝을 받아와서 써야하기 때문에 앱 모듈의 그레이들에 필요한 요소들을 추가해줍니다.

 

디버그용과 릴리즈용, 출시된 앱에 따라 추가해야하는 코드가 다르니 꼭 확인 후 추가해주세요.

 

https://developers.google.com/ml-kit/vision/barcode-scanning/android

 

Scan Barcodes with ML Kit on Android  |  Google Developers

Scan Barcodes with ML Kit on Android You can use ML Kit to recognize and decode barcodes. There are two ways to integrate barcode scanning: by bundling the model as part of your app, or by using an unbundled model that depends on Google Play Services. If y

developers.google.com

 

그리고 jetpack의 카메라X를 쓸 예정이라 그것도 들고와서 추가해줍시다.

 

https://developer.android.com/jetpack/androidx/releases/camera

 

CameraX  |  Android 개발자  |  Android Developers

CameraX CameraX가 Jetpack에 추가되어 앱에 카메라 기능을 더 쉽게 추가할 수 있습니다. 라이브러리는 다양한 호환성 수정사항과 해결 방법을 제공하여 많은 기기에서 개발자 환경을 일관되게 유지하

developer.android.com

 

하지만 !!!!!!

21.12.04 날짜를 기준으로 공식 문서에 있는 최신 카메라X를 적용시 sdk 31버전과 충돌이 있는 것 같아요.

그래서 다음과 같은 에러가 발생합니다.

 

java.lang.NoSuchMethodError: No static method getOrCreateInstance

 

이럴 경우 카메라X의 이전 버전을 들고와서 적용해줍니다.

 

def camerax_version = "1.0.1"
// CameraX core library using camera2 implementation
implementation "androidx.camera:camera-camera2:$camerax_version"
// CameraX Lifecycle Library
implementation "androidx.camera:camera-lifecycle:$camerax_version"
// CameraX View class
implementation "androidx.camera:camera-view:1.0.0-alpha27"

 

추가가 끝났다면 카메라 사용을 위한 퍼미션 체크를 해주세요.

카메라 퍼미션은 다루지 않겠습니다.

 

2. xml

카메라를 보여줄 화면을 만들어줍니다.

카메라X의 프리뷰를 xml에 예쁘게 넣어봅시다.

 

<androidx.camera.view.PreviewView
    android:id="@+id/camerax_preview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:importantForAccessibility="no"
    />

 

 

3. 카메라 뷰 바인딩

 

카메라X 세팅을 해주고 이미지를 분석할 수 있는 준비를 해줍시다.

이미지를 분석하기 위해서는 executor와 analyzer가 필요합니다. 

 

imageAnalysis.setAnalyzer(cameraExecutor, myImageAnalyzer);

(분석시키기 위해 cameraExecutor와 analyzer를 넣어줘야하는 모습)

 

 

 

executor는 ExecutorService 외에도 터프하게

 

Executor executor = Executors.newSingleThreadExecutor(); 

 

초기화 해서 사용 가능합니다. 저는 ExecutorService를 사용했습니다.

 

public class MainActivity extends AppCompatActivity {

    private PreviewView mPreviewView;
    private ListenableFuture cameraProviderFuture;
    private ExecutorService cameraExecutor;

    private MyImageAnalyzer myImageAnalyzer;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        init();
    }

    private void init(){
        mPreviewView = findViewById(R.id.camerax_preview);

        cameraExecutor = Executors.newSingleThreadExecutor();
        cameraProviderFuture = ProcessCameraProvider.getInstance(this);
        myImageAnalyzer = new MyImageAnalyzer(this.getSupportFragmentManager());

        cameraProviderFuture.addListener(new Runnable() {
            @Override
            public void run() {
                try {
                    ProcessCameraProvider processCameraProvider = (ProcessCameraProvider) cameraProviderFuture.get();
                    bindPreview(processCameraProvider);
                } catch (ExecutionException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, ContextCompat.getMainExecutor(this));


    }

    private void bindPreview(ProcessCameraProvider processCameraProvider) {
        Preview preview = new Preview.Builder().build();
        CameraSelector cameraSelector = new CameraSelector.Builder()
                .requireLensFacing(CameraSelector.LENS_FACING_BACK)
                .build();

        ImageCapture imageCapture =new ImageCapture.Builder().build();
        ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
                .setTargetRotation(Surface.ROTATION_270)
                .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                .build();
        preview.setSurfaceProvider(mPreviewView.getSurfaceProvider());

        imageAnalysis.setAnalyzer(cameraExecutor, myImageAnalyzer);

        processCameraProvider.unbindAll();
        processCameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture, imageAnalysis);

    }

 

 

이제 카메라 준비가 끝났습니다.

카메라로 가져온 이미지에서 정보를 가져오는 스캐닝 작업을 해주기 전에!!

 

 

내부 클래스로 analyzer를 만들어줍니다.

공식에서 적어준걸 가져옵시다.

 

https://developers.google.com/ml-kit/vision/barcode-scanning/android#java

 

Scan Barcodes with ML Kit on Android  |  Google Developers

Scan Barcodes with ML Kit on Android You can use ML Kit to recognize and decode barcodes. There are two ways to integrate barcode scanning: by bundling the model as part of your app, or by using an unbundled model that depends on Google Play Services. If y

developers.google.com

 

public class MyImageAnalyzer implements ImageAnalysis.Analyzer{
    private FragmentManager fragmentManager;

    public MyImageAnalyzer(FragmentManager fragmentManager) {
        this.fragmentManager = fragmentManager;
    }

    @Override
    public void analyze(@NonNull ImageProxy image) {
        scanBarcode(image);
    }
}

 

 

4. 바코드 스캐닝

 

analyzer 에서 던져주는 ImageProxy를 분석하는 코드입니다.

 

private void scanBarcode(ImageProxy image) {

    //input image
    @SuppressLint("UnsafeOptInUsageError") Image image1 = image.getImage();
    InputImage inputImage = InputImage.fromMediaImage(image1, image.getImageInfo().getRotationDegrees());

    BarcodeScannerOptions options =
            new BarcodeScannerOptions.Builder()
                    .setBarcodeFormats(
                            Barcode.FORMAT_QR_CODE,
                            Barcode.FORMAT_AZTEC)
                    .build();

    //Get an instance of BarcodeScanner
    BarcodeScanner scanner = BarcodeScanning.getClient(options);

    //process the image
    Task<List<Barcode>> result = scanner.process(inputImage)
            .addOnSuccessListener(new OnSuccessListener<List<Barcode>>() {
                @Override
                public void onSuccess(List<Barcode> barcodes) {
                    readerBarcodeData(barcodes);
                    Log.d("cameraSuccess::", barcodes.toString());
                }
            })
            .addOnFailureListener(new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception e) {
                    // Task failed with an exception
                    // ...
                    Log.d("cameraFail::", e.toString());
                }
            })
            .addOnCompleteListener(new OnCompleteListener<List<Barcode>>() {
                @Override
                public void onComplete(@NonNull Task<List<Barcode>> task) {
                    image.close();
                }
            });
}

 

BarcodeScannerOptions 에서 옵션을 다르게 선택하면 QR코드가 아니라 바코드를 읽어낼 수 있습니다.

지금은 QR코드만 스캔할 수 있도록 옵션을 설정해두었습니다.

 

이렇게 성공적으로 바코드 스캔까지 마쳤으면 바코드가 담고있는 정보를 가져와 정제를 해줍니다.

 

//get information barcode
private void readerBarcodeData(List<Barcode> barcodes) {
    for (Barcode barcode: barcodes) {
        Rect bounds = barcode.getBoundingBox();
        Point[] corners = barcode.getCornerPoints();

        String rawValue = barcode.getRawValue();

        int valueType = barcode.getValueType();

        // See API reference for complete list of supported types
        switch (valueType) {
            case Barcode.TYPE_WIFI:
                Toast.makeText(this, "wifi", Toast.LENGTH_SHORT).show();

                String ssid = barcode.getWifi().getSsid();
                String password = barcode.getWifi().getPassword();
                int type = barcode.getWifi().getEncryptionType();

                cameraExecutor.shutdownNow();

                break;
            case Barcode.TYPE_URL:
                Toast.makeText(this, "url", Toast.LENGTH_SHORT).show();
                String title = barcode.getUrl().getTitle();
                String url = barcode.getUrl().getUrl();

                cameraExecutor.shutdownNow();

                break;
        }
    }
}

 

바코드가 담고있는 정보의 타입에 따라 어떻게 다룰것인지 나눠줍니다.

url을 받아왔으면 intent로 넘겨줄 수 있겠네요!

 

 

이렇게 끝!

일 줄 알았겠지만 현재 사용중인 액티비티가 꺼질때 백그라운드에서 카메라가 돌지 않도록 꺼줍시다.

꺼주지 않으면... 혼나요.

 

private void closeCamera(){
    if (cameraProviderFuture != null && cameraExecutor != null){
        cameraProviderFuture.cancel(true);
        cameraProviderFuture = null;
        cameraExecutor.shutdown();
        cameraExecutor = null;
    }
}

 

저는 그냥 null을 박아버렸습니다.

사실 더 좋은 방법이 있지 않을까 싶은데 없애주는 게 마음이 편해요.

카메라 꺼주는 메소드를 만들어서

 

@Override
protected void onPause() {
    super.onPause();
    closeCamera();
}

@Override
protected void onDestroy() {
    super.onDestroy();
    closeCamera();
}

 

이렇게 붙여줍니다.

onPause에서 카메라를 없앴으니 onResume에서는 다시 켜줘야겠죠!!

 

코드 전문은 깃헙을 참조해주세요.

 

https://github.com/jmnl225/QRcodeScanner

 

GitHub - jmnl225/QRcodeScanner

Contribute to jmnl225/QRcodeScanner development by creating an account on GitHub.

github.com

 

+ Recent posts