[안드로이드] QR코드 스캐너 (feet. ml kit & java)

[안드로이드] QR코드 스캐너 (feet. ml kit & java)

안녕하세요.

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

1. 준비

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

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

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

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

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

하지만 !!!!!!

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에 예쁘게 넣어봅시다.

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

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> result = scanner.process(inputImage) .addOnSuccessListener(new OnSuccessListener>() { @Override public void onSuccess(List 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>() { @Override public void onComplete(@NonNull Task> task) { image.close(); } }); }

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

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

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

//get information barcode private void readerBarcodeData(List 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

from http://joominl.tistory.com/3 by ccl(A) rewrite - 2021-12-04 02:28:05