on
[안드로이드] 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