hyeon.s
개발로그
hyeon.s
전체 방문자
오늘
어제
  • 분류 전체보기 (150)
    • Web 및 인프라 (1)
      • Web (1)
      • Terraform (2)
      • Docker (1)
    • Android (1)
      • 공부 (28)
      • 트러블슈팅 (12)
      • 프로젝트 개발 (10)
      • Compose (2)
      • 우테코 프리코스 (0)
    • Server (5)
      • 공부 (1)
      • Spring (4)
    • 알고리즘 (68)
      • 문제풀이 (C++,Kotlin) (54)
      • 공부 (13)
    • 디자인 (3)
      • UI (3)
    • Language (5)
      • Kotlin (5)
      • JAVA (0)
    • IT 동아리 (8)
      • UMC 3기 (Android) (7)
      • Sopt 32기 (Android) (1)

Github

글쓰기 / 관리자
hELLO · Designed By 정상우.
hyeon.s

개발로그

[Android/JAVA] Zxing 활용 바코드/QR 스캐너 구현하기
Android/프로젝트 개발

[Android/JAVA] Zxing 활용 바코드/QR 스캐너 구현하기

2024. 8. 7. 15:30
728x90

 

기능 요구사항

바코드, QR코드 스캔을 통해 데이터를 가져올 수 있도록 한다.

Zxing을 활용한 바코드 구현

Zxing이란

Zxing (Zebra Crossing) : zxing은 오픈소스로 다중형식의 1D/2D 바코드 이미지를 처리하는 자바 라이브러리이다. zxing을 활용해서 바코드를 생성하고 읽을 수 있으며 지원하는 바코드 형식 또한 다양하다. 아래 repository를 통해서 안드로이드에서 zxing을 활용할 수 있다.

https://github.com/journeyapps/zxing-android-embedded

카메라 권한 설정

바코드, QR코드 스캐너는 사용자의 카메라를 통해 스캔한다. 따라서 앱에서 카메라를 사용할 때 사용자의 카메라 권한이 필요하다. 카메라 권한을 설정하는 방법은 다음과 같다.

1. AndroidManifest.xml

-manifest에 아래 권한을 추가해준다.

<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera"/>

*uses-permission vs uses-feature

  • <uses-permission> 태그는 애플리케이션이 특정 권한을 요청하는 데 사용됩니다. 이 태그를 통해 애플리케이션이 필요한 시스템 리소스나 기능에 접근할 수 있도록 사용자에게 권한을 요청합니다. 예를 들어, 카메라를 사용하려면 카메라 권한을 요청해야 합니다.
  • <uses-feature> 태그는 애플리케이션이 특정 하드웨어나 소프트웨어 기능을 사용한다는 것을 선언합니다.
  • <uses-feature> 에서 카메라의 required = "true" 로 설정하면 카메라가 없는 하드웨어는 해당 어플을 설치하지 못한다.
  • <uses-permission> says "hey, Android (and associated distribution channels), please ask the user to allow me to do X". <uses-feature> says "hey, Android (and associated distribution channels), I am interested in running on hardware with feature Y".

2. 런타임 권한 설정

앱 실행 도중 카메라가 필요한 경우 런타임에 카메라 권한을 받는 방법은 아래 방법으로 이루어진다.

1.checkSelfPermission을 통해서 사용자가 이전에 권한을 허용했는지 확인한다.
2.분기에 따른 처리를 한다.
     허용했다면 : 권한 호출 없이 그대로 이어간다.
     허용한 적이 없다면 : 카메라 권한을 설정할 수 있는 창을 띄우고 권한을 확인한다.
            1. 이 때 사용자가 권한을 확인했다면 requestCode == 1로 권한이 허용된다.
            2. 권한확인을 하지 않았다면 권한이 필요하다는 메시지를 띄운다.
            3. 사용자의 권한 설정 결과는 onRequestPermissionResult 함수에 온다.

권한 설정 코드


    public void requestCameraPermission() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.CAMERA}, 1);
        } else {
            initializeScanner();
        }
    } //권한 확인 함수 (onCreate()에 해당 함수를 호출한다.) 

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == 1) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) initializeScanner(); //권한 허용
        }
        else {
            Toast.makeText(this, "카메라 권한이 필요합니다.", Toast.LENGTH_SHORT).show();
            // 권한 거부
        }
    } //사용자의 권한 result 확인 함수

스캐너 구현 (기본)

구현 내용

-main Activity에서 바코드 스캔 버튼 클릭 시 바코드 스캐너 activity로 이동하여 스캔이 가능하도록 한다.

의존성 추가

zxing을 활용해서 구현하므로 build.gradle에 의존성을 추가해준다.

implementation 'com.journeyapps:zxing-android-embedded:4.3.0'

MainActivity

스캐너 실행 후 launcher 결과 반환 함수

    private final ActivityResultLauncher<ScanOptions> barcodeLauncher = registerForActivityResult(new ScanContract(),
            result -> {
                if (result.getContents() == null) {
                    Toast.makeText(this, "Cancelled", Toast.LENGTH_LONG).show();
                } else {
                    Toast.makeText(this, "Scanned: " + result.getContents(), Toast.LENGTH_LONG).show();
                }
            });

스캐너 실행 및 옵션 설정 함수

ScanOptions을 통해 스캐너의 여러 옵션을 지정할 수 있습니다.

- options.setBeepEnabled(boolean) : 스캔 성공 시 삡- 소리의 여부
- options.setPrompt(string) : 스캐너 하단의 안내 문구 설정
- options.setDesireBarcodeFormats(ScanOptions.type) : 스캐너가 스캔 가능한 바코드 타입 지정 -> QR코드, 바코드 등 여러 코드 타입 지정 가능.
- barcodeLauncher.launch(options) : 지정한 options의 스캐너 실행 함수  barcodeLauncher는 앞서 선언한 결과를 return 받는 변수이다.

    private void initScanOption() {
        ScanOptions options = new ScanOptions();
        options.setBeepEnabled(true);
        options.setOrientationLocked(false);
        options.setPrompt("바코드를 스캐너에 스캔하세요.");
        options.setDesiredBarcodeFormats(ScanOptions.ONE_D_CODE_TYPES);
        barcodeLauncher.launch(options);
    }

바코드 스캔 버튼 클릭 리스너

  binding.btnBarcodeScanner.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                initScanOption();
            }
        });

스캐너 구현 (커스텀)

구현 내용

뷰 내에 스캐너가 존재하도록 커스텀 스캐너를 구현한다.

-MainActivity 버튼 클릭 시 스캐너 Activity로 이동

activity_barcode_scanner.xml

바코드가 보여질 activity에 DecoratedBarcodeView를 넣어준다.

<?xml version="1.0" encoding="utf-8"?>
<layout>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:background="@color/light_sky_blue">

   **<com.journeyapps.barcodescanner.DecoratedBarcodeView
        android:id="@+id/view_barcode_scan"
        android:layout_width="match_parent"
        app:zxing_scanner_layout="@layout/activity_custom_barcode_scanner"
        android:layout_height="300dp"
        app:layout_constraintTop_toBottomOf="@id/cl_top_bar">
    </com.journeyapps.barcodescanner.DecoratedBarcodeView>**

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

activity_custom_barcode_scanner.xml

내부 스캐너와 뷰 파인더를 커스텀하는 layout이다.

<layout>
    <merge xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.journeyapps.barcodescanner.BarcodeView
            android:id="@+id/zxing_barcode_surface"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:zxing_framing_rect_height="100dp"
            app:zxing_framing_rect_width="250dp" />

        <com.journeyapps.barcodescanner.ViewfinderView
            android:id="@+id/zxing_viewfinder_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:zxing_possible_result_points="@color/zxing_custom_possible_result_points"
            app:zxing_result_view="@color/zxing_custom_result_view"
            app:zxing_viewfinder_laser="@color/zxing_custom_viewfinder_laser"
            app:zxing_viewfinder_mask="@color/zxing_custom_viewfinder_mask" />
    </merge>
</layout>

MainActivity

BarcodeScannerActivity

-바코드 스캐닝을 관리하는 클래스인 CaptureManager를 추가해준다.
-xml에 decoratedBarcodeview에 DecoratedBarcodeView 를 추가해준다.
-capturemanager를 새로 생성한 후 activity의 생명주기와 같이 관리되도록 onPause, onResume, onDestroy 관리해준다.
-onSaveInstanceState는 Activity가 비정상 종료되었을 때 상태를 저장하는 메서드로 captureManager도 저장되도록 한다.

public class BarcodeScannerActivity extends Activity {

    private DecoratedBarcodeView barcodeScannerView;
    private CaptureManager captureManager;
    private ActivityBarcodeScannerBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_barcode_scanner);
        barcodeScannerView = binding.viewBarcodeScan;
        requestCameraPermission();
    }

    public void requestCameraPermission() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.CAMERA}, 1);
        } else {
            initializeScanner();
        }
    }

    private void initializeScanner() {
        if (captureManager == null) {
            captureManager = new CaptureManager(BarcodeScannerActivity.this,barcodeScannerView);
            captureManager.initializeFromIntent(getIntent(),null);
            captureManager.decode();
        }
    }
    @Override
    protected void onSaveInstanceState(@NonNull Bundle outState) {
        super.onSaveInstanceState(outState);
        if (captureManager != null) {
            captureManager.onSaveInstanceState(outState);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (captureManager != null) {
            captureManager.onPause();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (captureManager != null) {
            captureManager.onDestroy();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        captureManager.onResume();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == 1) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) initializeScanner();
        }
        else {
            Toast.makeText(this, "카메라 권한이 필요합니다.", Toast.LENGTH_SHORT).show();
        }
    }
}

MainActivity

커스텀 스캐너를 사용하기 위해서 options의 값을 설정해준다.

-options.setCaptureActivity(activity) : 스캐너를 실행 할 activity를 설정해준다.
-그 외는 기본 스캐너 실행 option과 동일하다.

   private void initScanOption(boolean isBarcode, Class <?> scannerActivity) {
        ScanOptions options = new ScanOptions();
        options.setBeepEnabled(true);
        options.setOrientationLocked(false);
        options.setCaptureActivity(scannerActivity);
        if (isBarcode) options.setDesiredBarcodeFormats(ScanOptions.ONE_D_CODE_TYPES);
        else options.setDesiredBarcodeFormats(ScanOptions.QR_CODE);
        barcodeLauncher.launch(options);
    }
  binding.btnBarcodeScanner.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                initScanOption(true, BarcodeScannerActivity.class);
            }
        });

스캐너 방향 설정

기본적으로 스캐너는 가로모드로만 되어있다. 따라서 세로모드로 고정 또는 화면 회전 전환에 따라 올바르게 스캐너 방향을 바꾸기 위해서는 설정이 필요하다.

AndroidManifest.xml

<activity
        android:name="com.journeyapps.barcodescanner.CaptureActivity"
        android:screenOrientation="portrait" //fullSensor로 설정 시 4방향으로 다 회전 가능 
        tools:replace="screenOrientation"
        tools:ignore="DiscouragedApi" />

options 설정

options.setOrientationLocked(false); // 방향의 locked를 false로 지정하면 4방향 회전 가능.

구현 트러블슈팅 및 배운점

- onSaveInstanceState(outState) 의 역할과 중요성을 깨달았다.
- 커스텀 스캐너 (뷰 내에 스캐너) 를 만들기 위해서는 captureManager를 생성해야한다.
    - captureManager의 초기화를 카메라 권한 허용 이후에 넣어 진행해야한다.
-captureManager는 생명주기와 같이 함께 관리해줘야 한다.
    -하지만 captureManager가 null 일 때 onPause(), onDestroy(), onResume()을 실행하면 nullpointException이 발생하므로 null값 예외처리를 해줘야한다.

728x90
저작자표시 (새창열림)

'Android > 프로젝트 개발' 카테고리의 다른 글

[Android] Github Actions로 업무 생산성 향상 시키기 feat. APK 자동 추출  (0) 2024.12.12
[Android] PDF 뷰어 띄우기 / Activity 내에 PDF 뷰어 넣기 feat. 파일관리  (0) 2024.09.20
[Android] Retrofit에서 XML 데이터 통신 및 파싱 방법 / RecyclerView XML 데이터 받아오기  (0) 2024.02.26
[Android] 인앱 업데이트 구현하기 / 인앱 업데이트 테스트  (1) 2023.11.01
[Android] registerForActivityResult로 팝업 Activity를 만들어보자  (0) 2023.08.26
'Android/프로젝트 개발' 카테고리의 다른 글
  • [Android] Github Actions로 업무 생산성 향상 시키기 feat. APK 자동 추출
  • [Android] PDF 뷰어 띄우기 / Activity 내에 PDF 뷰어 넣기 feat. 파일관리
  • [Android] Retrofit에서 XML 데이터 통신 및 파싱 방법 / RecyclerView XML 데이터 받아오기
  • [Android] 인앱 업데이트 구현하기 / 인앱 업데이트 테스트
hyeon.s
hyeon.s
이유있는 코드를 짜자

티스토리툴바