Scandit SDK 핵심 개념 — DataCaptureContext와 5요소 아키텍처 완전 가이드
최근 업데이트: 2026-06-10
한줄 요약
Scandit SDK는 DataCaptureContext(오케스트레이터)·DataCaptureView(렌더링)·FrameSource(카메라)·DataCaptureMode(스캔 능력)·Overlays(AR 증강) 5요소로 구성된다. 각 요소는 명확한 책임 경계를 가지며, 라이프사이클 연동과 메모리 관리가 안정적인 프로덕션 운영의 핵심이다. 하나의 Context에는 한 번에 하나의 Mode만 연결되며, 라이선스 키는 빌드별로 분리하고 일시적인 오프라인 상태에서도 일정 기간 스캔이 계속 동작한다.
TL;DR
- Scandit SDK는 5요소로 구성됩니다:
DataCaptureContext(오케스트레이터),FrameSource(카메라),DataCaptureMode(인식 엔진),DataCaptureView(렌더링),Overlays(AR 증강). - 초기화 순서와 플랫폼별 라이프사이클 연동이 잘못되면 콜백이 호출되지 않는 침묵 장애가 발생합니다.
- 하나의
DataCaptureContext에는 한 번에 하나의 Mode만 연결할 수 있습니다. 기능 전환은 Mode 교체로 구현합니다. - 라이선스 키는 개발·스테이징·프로덕션을 반드시 분리하고, 소스 코드에 하드코딩하지 않습니다.
- 일시적인 오프라인 상태에서도 일정 기간 스캔이 계속 동작하지만, 상시 오프라인 환경은 별도 협의가 필요합니다.
Scandit SDK 5요소 — Architecture Map
Scandit SDK를 처음 접하는 개발자가 가장 자주 혼란을 겪는 지점은 "어떤 객체가 무엇을 책임지는가"입니다. SDK는 관심사 분리(Separation of Concerns) 원칙에 따라 다섯 가지 핵심 빌딩 블록을 명확하게 분리해 놓았습니다.
DataCaptureContext — 모든 것의 시작점
DataCaptureContext는 SDK 전체의 진입점이자 라이프사이클 관리자입니다. 라이선스 키 검증, FrameSource 연결, DataCaptureMode 등록, DataCaptureView 바인딩을 모두 이 객체가 중재합니다. iOS에서는 DataCaptureContext(licenseKey:)로, Android에서는 DataCaptureContext.forLicenseKey()로, React Native에서는 DataCaptureContext.initialize()로 생성하며, Web에서는 DataCaptureContext.forLicenseKey(licenseKey, { libraryLocation, moduleLoaders })를 await한 뒤 DataCaptureContext.sharedInstance로 접근합니다.
주의할 점은 네이티브 플랫폼에서 Context 생성 시점에 라이선스 검증이 동기적으로 끝나지 않는다는 것입니다. 키가 무효하거나 번들 ID가 일치하지 않아도 Context 객체 자체는 만들어지며, 검증 결과는 DataCaptureContextListener의 상태(ContextStatus) 콜백으로 비동기 전달됩니다. 따라서 라이선스 문제를 감지하려면 이 리스너를 반드시 구현해야 합니다. Web SDK의 forLicenseKey()는 비동기 호출이므로 초기화 실패 시 Promise가 reject될 수 있어, try/catch로 감싸는 것이 안전합니다. 특히 Trial 키에서 Production 키로 전환할 때 번들 ID 바인딩 오류가 자주 발생하므로, 전환 전 스테이징 환경에서 충분히 검증하는 것이 중요합니다.
또 하나 중요한 제약은 Context 하나에 연결할 수 있는 Mode가 한 번에 하나뿐이라는 점입니다. 바코드 스캔과 신분증 인식처럼 여러 기능이 필요한 화면은 Mode를 교체하는 방식으로 설계해야 하며, 이는 "Mode 전환 — 단일 Mode 모델" 섹션에서 상세히 다룹니다.
FrameSource — 카메라와 영상 스트림
FrameSource는 SDK에 영상 프레임을 공급하는 계층입니다. 실제 디바이스 카메라를 추상화한 Camera 객체가 가장 일반적인 구현체입니다. 기본 후면 카메라는 플랫폼별로 iOS Camera.default, Android Camera.getDefaultCamera(), Web Camera.pickBestGuess()로 얻습니다. Web에서는 camera.applySettings(BarcodeCapture.recommendedCameraSettings)로 권장 카메라 설정을 함께 적용하는 것이 좋습니다.
FrameSource를 DataCaptureContext.setFrameSource()로 연결하면 Context가 카메라를 제어하기 시작합니다. 카메라 권한 요청은 SDK가 아니라 앱의 책임입니다. 카메라를 켜기 전에 플랫폼 OS API(Android ActivityCompat.requestPermissions(), iOS AVCaptureDevice.requestAccess(for:))로 권한을 먼저 획득해야 하며, 권한 없이 카메라를 시작하면 프레임이 공급되지 않아 화면이 검게 보입니다. FrameSourceListener의 onStateChanged 콜백으로 카메라 상태(On/Off/Starting/Stopping 등)를 감시하는 것이 실무 패턴입니다.
화면 렌더링 없이 스캔만 구동하는 시나리오는 "Capture View 없이 off-screen 처리" 섹션에서 별도로 다룹니다.
DataCaptureMode — 실제 인식 능력
DataCaptureMode는 프레임에서 무엇을 인식할 것인지를 정의합니다. Scandit SDK가 지원하는 Mode는 다음과 같습니다.
| Mode | 용도 | 주요 설정 클래스 |
|---|---|---|
| BarcodeCapture | 단일 바코드 스캔 | BarcodeCaptureSettings |
| SparkScan | 고속 연속 스캔 (피킹·물류) | SparkScanSettings |
| IdCapture | 신분증·여권 인식 | IdCaptureSettings |
| LabelCapture | 복합 라벨 인식 | LabelCaptureSettings |
| MatrixScan | 다중 바코드 동시 인식 | BarcodeBatchSettings |
각 Mode는 생성 시점에 context를 파라미터로 받는 팩토리(예: Android BarcodeCapture.forDataCaptureContext(context, settings))로 Context에 연결하거나, 생성 후 setMode()로 연결합니다. 하나의 Context에는 한 번에 하나의 Mode만 연결되므로, 다른 Mode로 바꿀 때는 context.removeCurrentMode()로 기존 Mode를 먼저 제거합니다. 스캔 일시정지는 Mode 교체가 아니라 mode.isEnabled 토글로 처리합니다.
iOS Swift — BarcodeCapture 리스너 콜백 구현
import ScanditBarcodeCapture
class ScanViewController: UIViewController, BarcodeCaptureListener {
func barcodeCapture(_ barcodeCapture: BarcodeCapture,
didScanIn session: BarcodeCaptureSession,
frameData: FrameData) {
// 인식된 바코드 처리 (newlyRecognizedBarcode: 단수)
guard let barcode = session.newlyRecognizedBarcode else { return }
let symbology = barcode.symbology.description
let data = barcode.data ?? "(데이터 없음)"
print("인식: [\(symbology)] \(data)")
// 중복 스캔 방지: 리스너 내부에서 즉시 비활성화
DispatchQueue.main.async {
barcodeCapture.isEnabled = false
self.handleScanResult(data: data)
}
}
}
// BarcodeCapture 생성 후 리스너 등록
barcodeCapture.addListener(self)
DataCaptureView — UI 렌더링 레이어
DataCaptureView는 카메라 피드를 화면에 그리고, 인식 영역 UI(뷰파인더)와 Overlay를 표시하는 역할을 합니다. 이 객체는 필수가 아닙니다. 화면이 없는 백엔드 처리나 헤드리스 환경에서는 생략 가능합니다.
iOS에서는 DataCaptureView(context: context, frame: view.bounds)로 생성하고 UIView 계층에 추가합니다. Android에서는 DataCaptureView.newInstance(context, dataCaptureContext)로 생성 후 레이아웃에 배치합니다. Web SDK에서는 DataCaptureView.forContext(context)로 생성한 뒤 DOM 엘리먼트에 연결합니다.
실무에서 중요한 점은 DataCaptureView의 크기와 레이아웃이 카메라 미리보기 해상도와 일치해야 한다는 것입니다. View 크기가 카메라 출력 비율과 맞지 않으면 뷰파인더가 왜곡되거나, 스캔 영역(Viewfinder)이 실제 인식 가능 영역과 시각적으로 어긋나는 현상이 발생합니다. 국내 Android 단말의 경우 제조사별 화면 비율 차이가 크기 때문에, DataCaptureView를 전체 화면에 맞게 배치하고 ScanAreaMargins로 인식 범위를 명시적으로 지정하는 방어 설정을 권장합니다.
Overlays — AR 증강과 스캔 박스 UI
Overlay는 DataCaptureView 위에 인식 결과를 시각적으로 표시하는 AR 레이어입니다. BarcodeCaptureOverlay는 인식된 바코드 위에 브러시(색상·테두리)를 그려줍니다. 반면 SparkScan은 Overlay를 직접 구성하지 않습니다. SparkScan은 스캔 버튼·피드백·카메라 제어가 모두 포함된 프리빌트 UI인 SparkScanView를 제공하므로, DataCaptureView에 Overlay를 추가하는 방식이 아니라 SparkScanView를 그대로 사용합니다.
Overlay는 DataCaptureView.addOverlay()로 View에 연결합니다. 커스텀 Overlay를 구현하면 인식된 바코드 위치에 재고 수량, 가격 정보 같은 커스텀 데이터를 AR로 오버레이할 수 있습니다. 이 패턴은 MatrixScan을 활용한 실시간 재고 피킹 UI에서 실무 적용 빈도가 높습니다.
라이선스 모델 — 발급·검증·만료 처리
라이선스 키 종류
Scandit 라이선스 키는 세 가지 유형으로 발급됩니다.
Trial 키: Scandit 포털에서 자가 발급이 가능합니다. 기능 제한 없이 일정 기간 또는 스캔 횟수 제한으로 동작합니다. PoC나 개발 초기 단계에 적합하지만, 프로덕션 배포에는 사용할 수 없습니다. 번들 ID나 패키지 이름이 특정 값으로 고정되지 않아 개발 환경 유연성이 높습니다.
Development 키: Scandit과 계약 체결 후 발급되는 개발용 키입니다. 특정 번들 ID(iOS) 또는 Application ID(Android)에 바인딩되며, 디버그 빌드에만 동작하도록 제한할 수 있습니다. 팀 내 여러 개발자가 공유하는 키로 운용하되, 프로덕션 서버에 배포해서는 안 됩니다.
Production 키: 앱스토어·플레이스토어 배포용 키입니다. 번들 ID, 서명 인증서, 라이선스된 기능 범위(활성화된 Mode, 심볼로지 종류)가 모두 고정됩니다. 키 유출 시 Scandit 측에 즉시 폐기 요청이 가능합니다.
국내 구축 사례에서 DC가 권장하는 패턴은 Development 키와 Production 키를 환경 변수(.env.local, Bitrise/Jenkins CI 시크릿)로 분리 관리하는 것입니다. 소스 코드에 키를 하드코딩하면 GitHub 등 공개 저장소에 노출될 위험이 있으며, 특히 한국 공공기관 발주 프로젝트에서 보안 감사 시 지적 사항이 되는 경우가 있습니다.
검증 메커니즘과 오프라인 동작
앱이 처음 실행되어 DataCaptureContext를 생성하면, SDK는 라이선스 키의 유효성·만료일·바인딩된 번들 ID·활성화된 기능을 확인합니다. 이 검증은 Context 생성과 동기적으로 일어나지 않으며, 결과는 DataCaptureContextListener의 상태 콜백으로 전달됩니다.
검증에 성공한 뒤에는 일시적인 오프라인 상태에서도 일정 기간(짧은 유예 기간) 스캔이 계속 동작합니다. 다만 이 유예 기간이 지나면 온라인 재검증이 필요하고, 재검증에 실패하면 스캔 기능이 비활성화됩니다. 병원 지하층, 냉동 창고, 지하철 물류 터미널처럼 Wi-Fi가 불안정한 환경에서는 주기적으로 네트워크 연결을 확보할 수 있는지를 설계 단계에서 점검하고, 상시 오프라인 운영이 필요하다면 계약 시점에 Scandit 측과 별도 오프라인 라이선스 옵션을 협의해야 합니다.
만료 시 동작
라이선스가 만료되면 스캔 기능이 중단됩니다. 이때 네이티브 플랫폼에서는 별도의 예외가 동기적으로 던져지는 것이 아니라, DataCaptureContextListener의 상태(ContextStatus) 콜백으로 라이선스 문제가 전달됩니다. 이 리스너를 구현하지 않으면 "스캔이 조용히 멈추는" 형태로 장애가 드러나므로, 콜백에서 애플리케이션 레벨의 경고 알림을 표시하는 것이 권장 패턴입니다.
iOS — 라이선스 만료 모니터링
DataCaptureContextListener를 구현하면 context(_:didChange:) 콜백으로 Context 상태 변화를 감지할 수 있습니다. 라이선스 만료 처리를 위해서는 DataCaptureContext에 리스너를 등록하고, 해당 콜백에서 애플리케이션 레벨의 경고 알림 로직을 구현하는 것이 권장 패턴입니다. 구체적인 ContextStatus API는 Scandit Docs — DataCaptureContextListener (iOS)를 참조하세요.
라이선스 만료 예방의 실무 패턴으로는 만료 60일 전 내부 모니터링 알림을 설정하고, 30일 전에 갱신을 시작하며, 만료 7일 전에 프로덕션 키 교체를 완료하는 것이 권장됩니다.
iOS / Android / Web / React Native — 라이프사이클 매핑
플랫폼마다 컴포넌트 라이프사이클이 다르기 때문에, SDK 객체를 언제 초기화하고 언제 해제할지가 통합의 핵심입니다. 이 타이밍이 어긋나면 카메라가 해제되지 않아 다른 앱이 카메라를 사용하지 못하거나, 백그라운드에서 복귀할 때 카메라가 재시작에 실패하는 문제가 발생합니다.
iOS — UIViewController 라이프사이클
iOS에서 DataCaptureContext는 일반적으로 UIViewController의 viewDidLoad에서 초기화합니다. 카메라(FrameSource)는 viewWillAppear에서 시작하고, viewWillDisappear에서 정지합니다. context.removeCurrentMode()는 ViewController가 pop되거나 dismiss될 때 호출해 Mode가 메모리에 남지 않도록 합니다.
SwiftUI를 사용하는 경우 onAppear / onDisappear에서 동일한 패턴을 적용하되, @StateObject로 Context를 보관해 View 재렌더링 시 불필요한 재초기화가 일어나지 않도록 합니다.
국내 iOS 앱 프로젝트에서 자주 발생하는 실수는 viewDidDisappear에서 카메라를 정지하는 것입니다. viewWillDisappear와 viewDidDisappear의 차이는 애니메이션 타이밍인데, viewDidDisappear에서 정지하면 화면 전환 애니메이션 중에도 카메라가 동작하여 불필요한 전력 소모와 간혹 ARKit 충돌이 발생합니다.
Android — Activity / Fragment 라이프사이클
Android에서는 Activity.onCreate()에서 DataCaptureContext를 생성하고, onResume()에서 카메라를 시작하며, onPause()에서 Mode를 비활성화하고 카메라를 정지합니다. onDestroy()에서는 리스너를 해제하고 context.removeCurrentMode()를 호출해 Mode를 정리합니다.
Fragment를 사용하는 경우 onViewCreated / onDestroyView의 쌍으로 View 관련 리소스를 관리하되, Context 자체의 생명주기는 Fragment보다 긴 경우가 있으므로 ViewModel에 Context를 보관하는 패턴이 적합합니다.
국내 Android 환경에서 특히 주의할 점은 삼성 OneUI의 멀티윈도우·DeX 모드입니다. 앱이 분할 화면으로 전환될 때 onPause→onResume 사이클이 즉시 발생하는데, 이 시점에 카메라 재초기화 지연이 발생하면 사용자에게 카메라 피드가 잠시 검게 보이는 증상이 나타납니다. FrameSource 상태를 onResume에서 즉시 on으로 전환하기보다 FrameSourceListener를 등록해 onStateChanged 콜백에서 상태가 실제로 On이 된 것을 확인한 후에 스캔을 활성화하는 방어 코드가 필요합니다.
Web — Page Lifecycle API와 SPA 라우팅
Web SDK는 비동기 초기화가 필요합니다. DataCaptureContext.forLicenseKey(key, { libraryLocation, moduleLoaders }) 한 번의 호출이 WASM 모듈과 Worker 로드, 라이선스 설정을 모두 처리하므로, 이 호출을 await한 뒤 DataCaptureContext.sharedInstance로 Context에 접근합니다.
Web TypeScript — SDK 초기화 및 libraryLocation 설정
import {
Camera,
DataCaptureContext,
FrameSourceState,
} from "@scandit/web-datacapture-core";
import { BarcodeCapture, barcodeCaptureLoader } from "@scandit/web-datacapture-barcode";
// WASM·Worker 파일은 /sdc-lib/ 경로에서 서빙해야 합니다
// 404가 발생하면 libraryLocation 경로를 먼저 확인하세요
await DataCaptureContext.forLicenseKey(
process.env.NEXT_PUBLIC_SCANDIT_KEY!,
{
libraryLocation: new URL("sdc-lib/", document.baseURI).toString(),
moduleLoaders: [barcodeCaptureLoader()],
}
);
// 반환값을 변수에 담지 말고 sharedInstance로 접근합니다
const camera = Camera.pickBestGuess();
await camera.applySettings(BarcodeCapture.recommendedCameraSettings);
await DataCaptureContext.sharedInstance.setFrameSource(camera);
// 탭 닫힘 또는 컴포넌트 언마운트 시 정리: 카메라 정지 + 뷰 분리
async function cleanup() {
await DataCaptureContext.sharedInstance.frameSource
?.switchToDesiredState(FrameSourceState.Off);
view.detachFromElement();
}
window.addEventListener("beforeunload", () => { void cleanup(); });
SPA(React, Vue, Angular)에서의 핵심 패턴은 컴포넌트 언마운트 시 카메라를 FrameSourceState.Off로 전환하고 view.detachFromElement()로 View를 DOM에서 분리하는 것입니다. Context 자체는 그대로 두어도 되며, 같은 정리를 beforeunload 이벤트에서도 수행해 탭을 닫을 때 카메라 스트림이 해제되도록 합니다.
Next.js나 Nuxt처럼 SSR 환경에서는 DataCaptureContext를 클라이언트 사이드에서만 초기화해야 합니다. typeof window === 'undefined' 가드나 Next.js의 dynamic(() => import(...), { ssr: false }) 패턴으로 서버 사이드 렌더링 시 SDK 초기화를 방지합니다.
React Native — 싱글턴 Context와 useEffect cleanup
React Native에서 DataCaptureContext는 프로세스 전역 싱글턴입니다. 모듈 레벨에서 DataCaptureContext.initialize(licenseKey)를 한 번만 호출하고, 이후에는 어디서든 DataCaptureContext.sharedInstance로 접근합니다. 컴포넌트마다 Context를 새로 만들거나 useMemo로 생성하는 패턴은 사용하지 않습니다.
// CaptureContext.ts — 모듈 레벨에서 한 번만 실행됩니다
import { DataCaptureContext } from "scandit-react-native-datacapture-core";
DataCaptureContext.initialize("-- ENTER YOUR SCANDIT LICENSE KEY HERE --");
export default DataCaptureContext.sharedInstance;
// 스캔 화면 컴포넌트 — 언마운트 시 정리는 카메라·Mode·리스너 단위로
useEffect(() => {
barcodeCapture.isEnabled = true;
Camera.default?.switchToDesiredState(FrameSourceState.On);
return () => {
barcodeCapture.isEnabled = false;
Camera.default?.switchToDesiredState(FrameSourceState.Off);
dataCaptureView?.removeOverlay(overlay);
barcodeCapture.removeListener(listener);
context.removeMode(barcodeCapture);
};
}, []);
주의: cleanup에서 context.dispose()를 호출하면 안 됩니다. Context는 앱 전체가 공유하는 싱글턴이므로, dispose하면 해당 화면뿐 아니라 앱 내 모든 Scandit 화면이 무력화되고 JS 번들을 다시 로드하기 전까지 스캔이 불가능해집니다. 올바른 언마운트 정리는 카메라 정지, Mode 비활성화, Overlay 제거, 리스너 해제, removeMode()까지입니다. React Navigation을 사용한다면 useFocusEffect로 화면 포커스에 맞춰 카메라와 isEnabled를 토글하는 패턴이 가장 깔끔합니다.
Capture View 없이 off-screen 처리
모든 스캔 작업에 화면 UI가 필요한 것은 아닙니다. 창고 자동화나 고정 스캐너 스테이션처럼 운영자가 카메라 미리보기를 볼 필요가 없는 환경에서는 DataCaptureView 없이 DataCaptureContext + FrameSource + DataCaptureMode 리스너만으로 스캔 파이프라인을 구성할 수 있습니다.
이 패턴의 핵심은 DataCaptureView와 Overlay를 아예 생성하지 않는 것입니다. 모바일 단말이 아닌 환경이라면 Scandit이 공식 지원하는 임베디드 플랫폼(Linux 등)에서 SDK를 구동하는 구성을 검토할 수 있습니다.
구체적인 시나리오는 다음과 같습니다.
고정 스캐너 스테이션: 컨베이어 라인 옆 고정 위치에 설치된 스캔 디바이스에서 화면 출력 없이 인식 결과만 백엔드로 전송합니다. DataCaptureView는 불필요하며, DataCaptureMode의 리스너만 구현해 인식 결과를 수신합니다.
Robot + embedded: 로봇이나 임베디드 디바이스에 장착된 카메라 영상을 처리하는 경우, Linux 등 공식 지원 임베디드 플랫폼에서 디스플레이 없이 DataCaptureContext + FrameSource + BarcodeCapture만으로 스캔 파이프라인을 구성합니다.
off-screen 패턴은 특히 GS1 DataMatrix 직렬화 검증이 필요한 의약품 유통 창고에서 자주 사용됩니다. 화면 렌더링에 드는 리소스를 절약하고 인식 파이프라인에 처리 능력을 집중할 수 있다는 점, 그리고 운영자 UI를 인식 결과 기반의 별도 화면으로 분리할 수 있다는 점이 실무에서의 장점입니다. 구체적인 플랫폼 지원 범위와 구성 방식은 도입 전 Scandit 공식 문서 또는 DC 기술팀과 함께 확인하는 것을 권장합니다.
Mode 전환 — 단일 Mode 모델
SDK 8.x에서 하나의 DataCaptureContext에는 한 번에 하나의 DataCaptureMode만 연결할 수 있습니다. 두 번째 Mode를 추가로 연결하려고 하면 에러가 발생합니다. 따라서 바코드 스캔과 신분증 인식을 한 화면 흐름에서 처리해야 한다면, 두 Mode를 동시에 돌리는 것이 아니라 시점에 따라 Mode를 교체하는 순차 설계가 필요합니다.
상황별 전환 패턴
| 상황 | 패턴 |
|---|---|
| 같은 Mode에서 잠시 스캔 일시정지 | mode.isEnabled = false 토글 |
| 다른 Mode로 전환 (예: 바코드 → 신분증) | context.removeCurrentMode() 후 새 Mode 연결 |
| 같은 Mode의 설정 변경 | mode.applySettings(newSettings) |
가장 가벼운 동작은 isEnabled 토글입니다. 카메라와 Mode 연결을 유지한 채 인식만 멈추므로, 스캔 결과 처리 중 중복 인식을 막는 용도나 화면 내 일시정지 버튼에 적합합니다. Mode 자체를 바꿔야 할 때만 removeCurrentMode()로 기존 Mode를 제거하고 새 Mode를 연결합니다.
순차 전환 설계 예시 — 바코드 스캔 후 신분증 인식
예를 들어 약국 카운터에서 의약품 바코드를 스캔한 뒤 환자 신분증을 인식해야 하는 플로라면, 다음과 같은 순차 전환으로 구성합니다.
BarcodeCapture를 Context에 연결하고 스캔을 진행합니다.- 바코드 인식이 완료되면
barcodeCapture.isEnabled = false로 비활성화하고,BarcodeCaptureOverlay를 View에서 제거합니다. context.removeCurrentMode()로BarcodeCapture를 제거합니다.IdCapture를 생성해 Context에 연결하고, 해당 Overlay를 View에 추가한 뒤 인식을 시작합니다.- 신분증 인식이 끝나면 역순으로 되돌립니다.
Mode를 교체할 때는 Overlay와 리스너도 함께 정리해야 합니다. 이전 Mode의 Overlay가 View에 남아 있으면 화면에 불필요한 UI가 겹치고, 리스너가 해제되지 않으면 참조가 남아 메모리 누수의 원인이 됩니다. "Mode 교체 = Mode + Overlay + 리스너를 한 세트로 교체"라고 기억하는 것이 안전합니다.
심볼로지를 넓게 켜는 것보다 필요한 것만 활성화하는 것이 인식 속도와 정확도에 유리하다는 원칙은 Mode 전환 설계에도 동일하게 적용됩니다. 전환 시점마다 해당 단계에서 실제로 필요한 설정만 가진 Mode를 연결하면 불필요한 처리 부하를 줄일 수 있습니다.
Android Kotlin — onDestroy 메모리 정리 패턴
// Activity.onDestroy()에서 SDK 리소스를 명시적으로 해제합니다
override fun onDestroy() {
// 1. 스캔 Mode 비활성화 후 리스너 해제
barcodeCapture.isEnabled = false
barcodeCapture.removeListener(this)
// 2. 카메라(FrameSource) 정지
camera?.switchToDesiredState(FrameSourceState.OFF)
// 3. Context에서 현재 Mode 제거
dataCaptureContext.removeCurrentMode()
super.onDestroy()
}
트러블슈팅 — 자주 마주치는 5가지 패턴
현장 통합에서 DC가 반복적으로 마주치는 문제와 원인·해결 방법을 정리합니다.
패턴 1: 카메라 권한 거부
증상: 앱을 처음 실행하면 카메라가 표시되지 않고, 카메라를 On으로 전환해도 프레임이 공급되지 않아 화면이 검게 보인다.
원인: OS 레벨 카메라 권한이 없거나, 사용자가 권한 요청을 거부한 상태다. SDK는 권한을 대신 요청해 주지 않는다.
해결: 카메라를 시작하기 전에 OS API로 권한을 먼저 요청합니다. Android에서는 ActivityCompat.requestPermissions()로 명시적 권한 요청 후 onRequestPermissionsResult에서 결과를 처리하고, 허용된 경우에만 camera.switchToDesiredState(FrameSourceState.ON)을 호출합니다. iOS는 AVCaptureDevice.authorizationStatus(for: .video) 확인 후 필요 시 requestAccess(for:)를 호출하며, Info.plist의 NSCameraUsageDescription이 반드시 설정되어 있어야 합니다. 권한이 거부된 상태라면 OS 설정 화면으로 안내하는 Alert를 표시합니다.
패턴 2: Web SDK library file 404
증상: Web 앱 로드 시 브라우저 콘솔에 WASM 파일 또는 Worker 스크립트 404 에러가 표시되고 스캔이 동작하지 않는다.
원인: DataCaptureContext.forLicenseKey()에 전달한 libraryLocation 경로가 실제 파일 위치와 일치하지 않는다.
해결: 빌드 스크립트에서 node_modules/@scandit/web-datacapture-core/build/의 .wasm, .js, Worker 파일들을 /public/scandit/으로 복사하고 libraryLocation: '/scandit/'로 설정합니다. Vite나 webpack을 사용하는 경우 CopyPlugin으로 자동화할 수 있습니다. CDN 경유 시에는 CORS 헤더 설정이 필요합니다.
패턴 3: Mode가 View에 attach되지 않은 채 활성화
증상: 스캔을 시작해도 DataCaptureListener의 콜백이 호출되지 않는다.
원인: Mode가 DataCaptureContext에 등록되었지만, DataCaptureView에 Overlay가 추가되지 않았거나, Mode 자체가 isEnabled = false 상태인 경우가 많다.
해결: 초기화 순서를 점검합니다. 올바른 순서는 다음과 같습니다.
DataCaptureContext생성BarcodeCaptureSettings설정 및BarcodeCapture생성 (Context 연결)DataCaptureView생성 (Context 연결)BarcodeCaptureOverlay생성 및 View에 추가barcodeCapture.isEnabled = truecamera.switchToDesiredState(.on)
Overlay가 View에 추가되지 않아도 콜백은 동작하지만, 뷰파인더 UI가 없으면 사용자는 스캔 영역을 인지하지 못합니다. Step 4를 빠뜨리면 기능은 동작해도 UX가 불완전합니다.
패턴 4: 라이선스 키 만료
증상: 배포 후 특정 날짜 이후로 모든 기기에서 스캔이 중단됩니다. DataCaptureContextListener의 상태 콜백으로 라이선스 관련 에러가 전달되거나, 리스너를 구현하지 않은 경우 콜백이 조용히 멈춥니다.
원인: 라이선스 계약 갱신이 누락되거나, Trial 키를 프로덕션에 사용한 경우다.
해결: Scandit 포털에서 라이선스 만료일을 주기적으로 확인합니다. Production 키의 만료일이 계약 기간과 일치하는지 점검하고, 갱신 후 새 키를 앱 업데이트로 배포합니다. 만료 30일 전 알림을 CI/CD 파이프라인에 연동해 놓으면 배포 지연 위험을 줄일 수 있습니다. DC는 국내 고객사의 라이선스 갱신 일정을 대신 관리하는 서비스를 제공하므로, 갱신 누락이 우려된다면 담당 엔지니어에게 문의하시면 됩니다.
패턴 5: 백그라운드 복귀 시 카메라 freeze
증상: 앱을 백그라운드로 보냈다가 돌아오면 카메라 피드가 검거나 멈춰 있습니다.
원인: onPause에서 카메라를 정지했지만 onResume에서 재시작 로직이 누락되거나, 재시작 타이밍이 너무 이르다.
해결: Android에서는 onResume에서 camera.switchToDesiredState(FrameSourceState.ON)을 호출하고, FrameSourceListener의 onStateChanged() 콜백에서 상태가 실제로 On이 된 것을 확인한 후에 barcodeCapture.isEnabled = true를 설정합니다. iOS에서는 viewWillAppear에서 카메라를 재시작하고 상태가 On이 됨을 리스너로 확인합니다. 상태 확인 없이 즉시 활성화하면 일부 단말에서 첫 몇 프레임이 누락되거나 검은 화면이 잠시 표시됩니다.
DC 한국 도입 패턴
혼합 플랫폼 환경에서의 라이선스 키 관리
한 글로벌 물류사가 국내 DC 지원으로 SDK를 도입할 때 직면한 과제는 iOS 단말과 Android 단말이 혼재하는 환경에서 라이선스 키를 어떻게 배포·관리하는가였습니다. 플랫폼별로 키를 분리 발급하되, CI/CD 시스템의 시크릿 스토어에 SCANDIT_KEY_IOS_PROD와 SCANDIT_KEY_ANDROID_PROD를 별도로 저장하고 빌드 시 각 플랫폼 바이너리에 주입하는 방식으로 해결했습니다.
이 방식의 장점은 iOS 키가 유출되더라도 Android 스캔에 영향이 없고, 키 폐기·재발급 시 해당 플랫폼만 업데이트하면 된다는 점입니다. 국내 공공기관 발주 프로젝트의 경우 보안 감사에서 시크릿 관리 방식이 확인 항목으로 포함되는 경우가 있어, 이런 분리 관리 패턴이 실질적인 컴플라이언스 이점도 제공합니다.
의약품 유통 — 한 국내 대형 의약품 유통사 사례
한 국내 대형 의약품 유통사는 식약처 의약품 표준코드 컴플라이언스를 위해 GS1 DataMatrix 스캔 시스템을 구축했습니다. 핵심 요구사항은 창고 내 고정 스캐너 스테이션과 이동형 Android 단말이 동일한 DataCaptureContext 설정을 공유해야 한다는 것이었습니다.
DataCaptureContext 초기화 코드를 공통 라이브러리로 추출하고, 단말 유형(고정/이동)에 따라 구성만 달리하는 아키텍처를 채택했습니다. 고정 스테이션은 DataCaptureView 없이 인식 결과만 백엔드로 전송하는 off-screen 구성을 사용했고, 이동형 단말은 표준 Camera 객체와 DataCaptureView를 사용했습니다. Mode 설정과 리스너 코드는 두 환경 모두 동일하게 재사용되어 유지보수 부담이 크게 줄었습니다.
구형 Android 단말 호환성
국내 물류·리테일 현장처럼 단말 교체 주기가 긴 환경에서는 최신 SDK가 보유 단말에서 동작하는지 사전 검증이 필수입니다. Scandit SDK 8.x의 공식 최소 지원 버전은 Android 7.0(API 24), iOS 15입니다. 이보다 낮은 OS 버전의 단말이 현장에 남아 있다면 SDK 8.x를 적용할 수 없으므로, 단말 교체 계획을 먼저 수립하거나 도입 전 DC 기술팀과 지원 가능 범위를 확인해야 합니다.
지원 범위 안에 있더라도 구형 단말일수록 카메라 HAL 구현의 품질 차이가 커서 자동 초점(AF)·자동 노출(AE) 응답이 느린 경우가 있습니다. 이때는 BarcodeCaptureSettings의 codeDuplicateFilter(Android에서는 TimeInterval.millis(500) 같은 형태)를 넉넉하게 설정해 동일 바코드가 반복 인식되는 문제를 줄이는 것이 효과적입니다. 이 값은 Mode 생성 전에 settings에 지정하거나, 런타임에는 barcodeCapture.applySettings(newSettings)로 적용합니다. 또한 처리 성능이 제한된 단말에서는 Active Symbol Counts 범위를 좁혀 처리 부하를 줄이는 것이 인식 반응성에 도움이 됩니다.
자주 묻는 질문 (FAQ)
DataCaptureContext와 DataCaptureView 둘 다 만들어야 하나요?
용도에 따라 다릅니다. 카메라 피드를 화면에 보여줘야 한다면 DataCaptureView가 반드시 필요합니다. 반면 창고 자동화나 서버 사이드 이미지 처리처럼 화면 렌더링이 없는 환경이라면 DataCaptureContext만으로 스캔을 구동할 수 있습니다. DataCaptureContext는 항상 필수이며, DataCaptureView는 선택 사항입니다.
라이선스 키는 어디서 발급받고 어떻게 검증하나요?
Scandit 공식 포털(ssl.scandit.com)에서 개발·프로덕션·트라이얼 키를 각각 발급받을 수 있습니다. 네이티브 플랫폼에서는 라이선스 검증이 Context 생성과 동기적으로 일어나지 않으며, 결과가 DataCaptureContextListener의 상태(ContextStatus) 콜백으로 전달됩니다. Web SDK의 forLicenseKey()는 비동기 호출이므로 실패 시 Promise가 reject될 수 있습니다. 검증에 성공하면 일시적인 오프라인 상태에서도 일정 기간 스캔이 계속 동작합니다.
Activity/ViewController가 백그라운드로 가면 카메라 세션은 어떻게 처리해야 하나요?
iOS에서는 viewWillDisappear에서 카메라를 정지시키고, 화면을 완전히 떠날 때 removeCurrentMode()로 Mode를 정리합니다. Android에서는 onPause에서 Mode를 비활성화하고 FrameSource를 끈 뒤, onDestroy에서 리스너 해제와 removeCurrentMode()를 호출하는 패턴을 권장합니다.
Web SDK는 라이브러리 파일을 어디에 두어야 하나요?
Web SDK는 WASM 및 Worker 파일을 정적 경로에서 서빙해야 합니다. 빌드 시 node_modules의 scandit 패키지 파일을 /public/scandit/ 같은 정적 디렉터리로 복사하고 libraryLocation: '/scandit/'로 설정하는 것이 권장 방법입니다.
오프라인 환경에서 라이선스 검증이 작동하나요?
온라인 초기 검증 후에는 일시적인 오프라인 상태에서도 일정 기간(짧은 유예 기간) 스캔이 계속 동작합니다. 유예 기간이 지나면 온라인 재검증이 필요하며, 재검증 실패 시 스캔 기능이 비활성화됩니다. 상시 오프라인 환경에서는 Scandit 측과 별도 오프라인 라이선스 옵션을 협의해야 합니다.
최근 업데이트
최근 업데이트: 2026-06-10
이 페이지는 Scandit SDK 8.x 기준으로 작성되었습니다. SDK 버전이 업데이트되면 API 시그니처와 라이프사이클 동작이 변경될 수 있으므로, Scandit Docs — Core Concepts 및 DataCaptureContext API (Android)를 병행 참조하시기 바랍니다. Scandit SDK 도입 설계, PoC, 라이선스 상담은 데이터커넥트 기술팀으로 문의해 주시기 바랍니다.
코드 샘플
DataCaptureContext 초기화 및 ViewController 라이프사이클 연동
import ScanditCaptureCore
let context = DataCaptureContext(licenseKey: "-- ENTER YOUR SCANDIT LICENSE KEY HERE --")
// ViewController 라이프사이클에 맞춰 정리
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
camera?.switch(toDesiredState: .off)
context.removeCurrentMode()
}
DataCaptureContext 초기화 및 Activity 라이프사이클 연동
val context = DataCaptureContext.forLicenseKey("YOUR_LICENSE_KEY")
override fun onPause() {
barcodeCapture.isEnabled = false
camera?.switchToDesiredState(FrameSourceState.OFF)
super.onPause()
}
override fun onDestroy() {
barcodeCapture.removeListener(this)
context.removeCurrentMode()
super.onDestroy()
}
DataCaptureContext 비동기 초기화 및 페이지 언마운트 정리
import { DataCaptureContext, FrameSourceState } from "@scandit/web-datacapture-core";
import { barcodeCaptureLoader } from "@scandit/web-datacapture-barcode";
await DataCaptureContext.forLicenseKey(
"-- ENTER YOUR SCANDIT LICENSE KEY HERE --",
{ libraryLocation: "/sdc-lib/", moduleLoaders: [barcodeCaptureLoader()] }
);
// 이후에는 반환값이 아닌 sharedInstance로 접근합니다
const context = DataCaptureContext.sharedInstance;
// 페이지 unmount 시 정리: 카메라 정지 + 뷰 분리
async function cleanup() {
await context.frameSource?.switchToDesiredState(FrameSourceState.Off);
view.detachFromElement(); // DataCaptureView를 사용 중인 경우
}
DataCaptureContext 초기화 및 useEffect cleanup 패턴
import { Camera, DataCaptureContext, FrameSourceState } from "scandit-react-native-datacapture-core";
// initialize()는 컴포넌트 외부(모듈 레벨)에서 한 번만 호출합니다
DataCaptureContext.initialize("-- ENTER YOUR SCANDIT LICENSE KEY HERE --");
const context = DataCaptureContext.sharedInstance;
useEffect(() => {
return () => {
// dispose()는 호출하지 않습니다 — Context는 프로세스 전역 싱글턴입니다
barcodeCapture.isEnabled = false;
Camera.default?.switchToDesiredState(FrameSourceState.Off);
barcodeCapture.removeListener(listener);
context.removeMode(barcodeCapture);
};
}, []);

