기능 요구사항
바코드, 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값 예외처리를 해줘야한다.
'Android > 프로젝트 개발' 카테고리의 다른 글
[Android] Menu 데이터는 어디에 저장하는 게 성능이 좋을까? (RoomDB vs Object) (0) | 2024.12.05 |
---|---|
[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 |