如何为 Android 和 iOS 构建 Expo 条形码扫描器
如何为 Android 和 iOS 构建 Expo 条形码扫描器
Dynamsoft 在 Expo 应用中扫描条形码通常需要安装一个 React Native 插件,该插件封装了某个原生解码器。而本示例采用了不同的方法:原生桥接器是在应用内部手动编写的,作为本地 Expo 模块,因此应用可以直接与Android 和 iOS 上的Dynamsoft Capture Vision原生 SDK 通信——无需任何第三方扫描插件。
你将构建:一个基于 Expo SDK 57 的应用程序(expo-barcode-scanner),其中包含一个全屏原生相机扫描器,可使用 Dynamsoft Capture Vision 模板解码二维码和其他一维/二维条形码ReadBarcodes_Default,并在每个检测到的条形码周围绘制实时叠加层,最后返回一个包含解码文本、条形码格式和四个角点的结果列表。系统自带的图片选择器路径可以从现有图像中解码条形码,此外还有一个scanFile方法可以从本地文件路径解码条形码。
演示视频:Expo条码扫描器实际应用
要点总结
- 无需第三方插件,即可从 Expo 调用 Dynamsoft 的原生移动 SDK:在应用程序
modules/文件夹下实现一个本地 Expo 模块(Android 使用 Kotlin,iOS 使用 Swift),并将其暴露给 TypeScriptrequireNativeModule。 - 该
ReadBarcodes_Default模板可从实时摄像机帧中读取所有支持的 1D 和 2D 格式,无需针对每种格式进行配置,并且每个解码项都带有文本、formatString和用于绘制叠加层的四点location。 - 由于无法从 Android 上的 Expo 模块中观察到活动结果,因此该模块启动了一个不可见的桥接活动来托管
startActivityForResult和解决承诺——这种模式使桥接活动基于承诺并能够取消。 - 相机、照片选择器和文件扫描共享一个原生引擎和一个结果类型,因此无论图像来源如何,React Native UI 都保持不变。
开发者常见问题
如何在 Expo 应用中使用 Dynamsoft 原生 SDK 解码条形码?
添加一个本地 Expo 模块,该模块封装了 SDK,并从 JavaScript 调用三个方法:initLicense()` get_images()`、 startScan()`get_events()`(用于实时摄像头扫描)或`get_staticimages scanFromGallery()()` scanFile()(用于静态图像扫描)。示例模块名为 `example_module` ,并在 `example_module.js` 和`example_module.js`ExpoDynamsoftBarcodeScanner中实现。ExpoDynamsoftBarcodeScannerModule.kt.swift
扫描仪支持哪些格式?
Dynamsoft Capture VisionReadBarcodes_Default模板涵盖 1D 格式(Code 39、Code 128、Code 93、Codabar、EAN-13、EAN-8、ITF、UPC-A、UPC-E 等)和 2D 格式(QR 码、Data Matrix、PDF417、Aztec 和 Micro QR),所有这些都可以在一次扫描会话中完成,无需模板调整。
每个扫描结果包含哪些内容?
解析后的results数组中的每个元素都包含text解码后的有效载荷、条形码的四个角点位置formatString(例如 `<x> QR_CODE` 或 `<y> CODE_128`),以及条形码在扫描帧中的四个角点位置。React Native UI 将它们渲染成一个列表,而原生扫描器会在实时预览中绘制相应的叠加层。points{x, y}
同一段原生代码能否处理图片选择器图像?
是的。scanFromGallery()它会打开系统照片选择器,并使用相同的引擎和模板在原生端解码所选图像;scanFile({ uri })对于应用已保存的路径或内容 URI,也会执行相同的操作。两者解析后的结果ScanResult格式完全相同。
先决条件
开始之前,请确保您已准备好以下物品:
- Expo 工具链使用Node.js (LTS) 和 npm。
- Android Studio(最新版),配备 JDK 17 和已连接的 Android 设备或模拟器
- 在Xcode(最新版本,仅限 macOS)中,连接 iPhone,并在 Xcode → 设置 → 帐户 中配置签名团队。
- Dynamsoft许可证密钥——该示例嵌入了一个有时限的试用密钥,首次使用时需要网络连接。
访问dynamsoft.com/customer/license/trialLicense获取 30 天免费试用许可证
示例项目的组织结构
完整的应用程序位于examples/expo-barcode-scanner示例存储库的文件夹中:
<span style="color:#212529"><span style="color:#333333"><span style="background-color:#eaeaea"><code>expo-barcode-scanner/
├── app.json # App config: permissions, bundle IDs
├── App.tsx # Home + results list (React Native UI)
├── assets/ # App icons
└── modules/
└── expo-dynamsoft-barcode-scanner/ # Local Expo module (the native bridge)
├── expo-module.config.json # Registers the module for apple + android
├── src/ExpoDynamsoftBarcodeScannerModule.ts # TypeScript surface
├── android/ # Kotlin module + ScannerActivity
│ ├── build.gradle # Dynamsoft capturevisionbundle dependency
│ └── src/main/AndroidManifest.xml
└── ios/ # Swift module + BarcodeCameraScanViewController
├── ExpoDynamsoftBarcodeScanner.podspec
├── ExpoDynamsoftBarcodeScannerModule.swift
└── SharedImagePicker.swift
</code></span></span></span>
步骤 1:搭建 Expo 应用和本地模块
使用空白的 TypeScript 模板创建项目并搭建模块框架:
<span style="color:#212529"><span style="color:#333333"><span style="background-color:#eaeaea"><code>npx create-expo-app@latest expo-barcode-scanner <span style="color:#330099"><strong>--template</strong></span> blank-typescript
<span style="color:#336666">cd </span>expo-barcode-scanner
npx create-expo-module@latest <span style="color:#330099"><strong>--local</strong></span> modules/expo-dynamsoft-barcode-scanner
</code></span></span></span>
如果脚手架在 `<module>` 下创建了模块modules/modules/,请将其上移一级并删除空包装器。限制expo-module.config.json为原生平台,并统一模块类名:
<span style="color:#212529"><span style="color:#333333"><span style="background-color:#eaeaea"><code>{
<span style="color:#9999ff">"platforms"</span>: [<span style="color:#cc3300">"apple"</span>, <span style="color:#cc3300">"android"</span>],
<span style="color:#9999ff">"apple"</span>: {
<span style="color:#9999ff">"modules"</span>: [<span style="color:#cc3300">"ExpoDynamsoftBarcodeScannerModule"</span>]
},
<span style="color:#9999ff">"android"</span>: {
<span style="color:#9999ff">"modules"</span>: [<span style="color:#cc3300">"expo.modules.dynamsoftbarcodescanner.ExpoDynamsoftBarcodeScannerModule"</span>]
}
}
</code></span></span></span>
步骤二:添加相机和照片图库权限
app.json在Android 模块清单中声明使用说明;该清单添加了CAMERA照片库权限,这些权限会合并到应用程序清单中:
<span style="color:#212529"><span style="color:#333333"><span style="background-color:#eaeaea"><code><span style="color:#9999ff">"ios"</span>: {
<span style="color:#9999ff">"bundleIdentifier"</span>: <span style="color:#cc3300">"com.dynamsoft.expo.barcodescanner"</span>,
<span style="color:#9999ff">"infoPlist"</span>: {
<span style="color:#9999ff">"NSCameraUsageDescription"</span>: <span style="color:#cc3300">"Camera is used to scan barcodes."</span>,
<span style="color:#9999ff">"NSPhotoLibraryUsageDescription"</span>: <span style="color:#cc3300">"The app lets you pick a barcode image from your photo library to scan it."</span>
}
}
</code></span></span></span>
步骤 3:定义 TypeScript API 和结果列表 UI
模块表面声明了桥接方法和结果类型。每个解码后的条形码的位置都以四个角点的形式给出:
<span style="color:#212529"><span style="color:#333333"><span style="background-color:#eaeaea"><code><span style="color:#006699"><strong>export</strong></span> <span style="color:#006699"><strong>type</strong></span> BarcodeResult <span style="color:#555555">=</span> {
<span style="color:#330099">text</span>: <span style="color:#006699"><strong>string</strong></span>; <span style="color:#0099ff"><em>// decoded payload</em></span>
<span style="color:#9999ff">formatString</span>: <span style="color:#006699"><strong>string</strong></span>; <span style="color:#0099ff"><em>// e.g. QR_CODE, CODE_128</em></span>
<span style="color:#9999ff">points</span>: <span style="color:#336666">Array</span><span style="color:#555555"><</span>{ <span style="color:#330099">x</span>: <span style="color:#006699"><strong>number</strong></span>; <span style="color:#9999ff">y</span>: <span style="color:#006699"><strong>number</strong></span> }<span style="color:#555555">></span>; <span style="color:#0099ff"><em>// 4 corners in frame coordinates</em></span>
};
<span style="color:#006699"><strong>export</strong></span> <span style="color:#006699"><strong>type</strong></span> ScanResult <span style="color:#555555">=</span> { <span style="color:#330099">results</span>: BarcodeResult[] };
<span style="color:#006699"><strong>declare</strong></span> <span style="color:#006699"><strong>class</strong></span> <span style="color:#00aa88"><strong>ExpoDynamsoftBarcodeScannerModule</strong></span> <span style="color:#006699"><strong>extends</strong></span> <span style="color:#00aa88"><strong>NativeModule</strong></span><span style="color:#555555"><</span>{}<span style="color:#555555">></span> {
<span style="color:#cc00ff">initLicense</span>(license?: <span style="color:#006699"><strong>string</strong></span>): <span style="color:#336666">Promise</span><span style="color:#555555"><</span>InitLicenseResult<span style="color:#555555">></span>;
<span style="color:#cc00ff">startScan</span>(): <span style="color:#336666">Promise</span><span style="color:#555555"><</span>ScanResult<span style="color:#555555">></span>; <span style="color:#0099ff"><em>// full-screen camera scanner</em></span>
<span style="color:#cc00ff">scanFromGallery</span>(): <span style="color:#336666">Promise</span><span style="color:#555555"><</span>ScanResult<span style="color:#555555">></span>; <span style="color:#0099ff"><em>// system photo picker</em></span>
<span style="color:#cc00ff">scanFile</span>(<span style="color:#330099">options</span>: ScanFileOptions): <span style="color:#336666">Promise</span><span style="color:#555555"><</span>ScanResult<span style="color:#555555">></span>;
}
</code></span></span></span>
主屏幕App.tsx提供相机/图像文件切换开关,对两种来源运行相同的许可和扫描流程,并将每个解码后的条形码渲染成一张卡片,其中包含其格式、文本和边角信息:
<span style="color:#212529"><span style="color:#333333"><span style="background-color:#eaeaea"><code><span style="color:#006699"><strong>const</strong></span> scan <span style="color:#555555">=</span> <span style="color:#006699"><strong>async </strong></span>() <span style="color:#555555">=></span> {
<span style="color:#cc00ff">setBusy</span>(<span style="color:#006699"><strong>true</strong></span>);
<span style="color:#cc00ff">setError</span>(<span style="color:#006699"><strong>null</strong></span>);
<span style="color:#cc00ff">setResults</span>([]);
<span style="color:#006699"><strong>try</strong></span> {
<span style="color:#006699"><strong>const</strong></span> license <span style="color:#555555">=</span> <span style="color:#006699"><strong>await</strong></span> ExpoDynamsoftBarcodeScanner.<span style="color:#cc00ff">initLicense</span>();
<span style="color:#006699"><strong>if </strong></span>(<span style="color:#555555">!</span>license.success) {
<span style="color:#cc00ff">setError</span>(<span style="color:#cc3300">`License failed: </span>${license.message}<span style="color:#cc3300">`</span>);
<span style="color:#006699"><strong>return</strong></span>;
}
<span style="color:#006699"><strong>const</strong></span> outcome <span style="color:#555555">=</span> mode <span style="color:#555555">===</span> '<span style="color:#cc3300">camera</span>'
? <span style="color:#006699"><strong>await</strong></span> ExpoDynamsoftBarcodeScanner.<span style="color:#cc00ff">startScan</span>()
: <span style="color:#006699"><strong>await</strong></span> ExpoDynamsoftBarcodeScanner.<span style="color:#cc00ff">scanFromGallery</span>();
<span style="color:#cc00ff">setResults</span>(outcome.results);
} <span style="color:#006699"><strong>catch </strong></span>(<span style="color:#330099">e</span>: <span style="color:#006699"><strong>any</strong></span>) {
<span style="color:#006699"><strong>const</strong></span> <span style="color:#330099">message</span>: <span style="color:#006699"><strong>string</strong></span> <span style="color:#555555">=</span> e?.message <span style="color:#555555">??</span> <span style="color:#00aa88"><strong>String</strong></span>(e);
<span style="color:#006699"><strong>if </strong></span>(<span style="color:#555555">!</span><span style="color:#33aaaa">/cancel/i</span>.<span style="color:#cc00ff">test</span>(message)) {
<span style="color:#cc00ff">setError</span>(message); <span style="color:#0099ff"><em>// user cancel: keep the home screen clean</em></span>
}
} <span style="color:#006699"><strong>finally</strong></span> {
<span style="color:#cc00ff">setBusy</span>(<span style="color:#006699"><strong>false</strong></span>);
}
};
{results.<span style="color:#cc00ff">map</span>((r, i) <span style="color:#555555">=></span> (
<<span style="color:#00aa88"><strong>View</strong></span> <span style="color:#330099">key</span>=<span style="color:#aa0000">{</span><span style="color:#cc3300">`</span>${r.text}<span style="color:#cc3300">-</span>${i}<span style="color:#cc3300">`</span><span style="color:#aa0000">}</span> <span style="color:#330099">style</span>=<span style="color:#aa0000">{</span>styles.resultCard<span style="color:#aa0000">}</span>>
<<span style="color:#00aa88"><strong>Text</strong></span> <span style="color:#330099">style</span>=<span style="color:#aa0000">{</span>styles.resultFormat<span style="color:#aa0000">}</span>><span style="color:#aa0000">{</span>r.formatString<span style="color:#aa0000">}</span></<span style="color:#00aa88"><strong>Text</strong></span>>
<<span style="color:#00aa88"><strong>Text</strong></span> <span style="color:#330099">style</span>=<span style="color:#aa0000">{</span>styles.resultText<span style="color:#aa0000">}</span>><span style="color:#aa0000">{</span>r.text<span style="color:#aa0000">}</span></<span style="color:#00aa88"><strong>Text</strong></span>>
<span style="color:#aa0000">{</span>r.points.length <span style="color:#555555">===</span> <span style="color:#ff6600">4</span> ? (
<<span style="color:#00aa88"><strong>Text</strong></span> <span style="color:#330099">style</span>=<span style="color:#aa0000">{</span>styles.resultPoints<span style="color:#aa0000">}</span>>
corners: <span style="color:#aa0000">{</span>r.points.<span style="color:#cc00ff">map</span>((p) <span style="color:#555555">=></span> <span style="color:#cc3300">`(</span>${<span style="color:#336666">Math</span>.<span style="color:#cc00ff">round</span>(p.x)}<span style="color:#cc3300">, </span>${<span style="color:#336666">Math</span>.<span style="color:#cc00ff">round</span>(p.y)}<span style="color:#cc3300">)`</span>).<span style="color:#cc00ff">join</span>(' ')<span style="color:#aa0000">}</span>
</<span style="color:#00aa88"><strong>Text</strong></span>>
) : <span style="color:#006699"><strong>null</strong></span><span style="color:#aa0000">}</span>
</<span style="color:#00aa88"><strong>View</strong></span>>
))}
</code></span></span></span>
步骤 4:实现 Android 原生模块
Android 模块添加了独立的 Dynamsoft Capture Vision 软件包和 appcompat(扫描仪活动需要LifecycleOwner摄像头):
<span style="color:#212529"><span style="color:#333333"><span style="background-color:#eaeaea"><code><span style="color:#006699"><strong>dependencies</strong></span> <span style="color:#555555">{</span>
implementation <span style="color:#cc3300">'androidx.appcompat:appcompat:1.7.0'</span>
implementation <span style="color:#cc3300">'com.dynamsoft:capturevisionbundle:3.6.2000'</span>
<span style="color:#555555">}</span>
</code></span></span></span>
ScannerEngine.kt它由相机和文件源共享一个CaptureVisionRouter条形码预设模板。该模板同时用于以下两个方面:
<span style="color:#212529"><span style="color:#333333"><span style="background-color:#eaeaea"><code><span style="color:#006699"><strong>fun</strong></span> <span style="color:#cc00ff">template</span>(): <span style="color:#00aa88"><strong>String</strong></span> = <span style="color:#00aa88"><strong>EnumPresetTemplate</strong></span>.<span style="color:#00aa88"><strong>PT_READ_BARCODES</strong></span>
<span style="color:#006699"><strong>fun</strong></span> <span style="color:#cc00ff">decodeBitmap</span>(bitmap: <span style="color:#00aa88"><strong>Bitmap</strong></span>?): <span style="color:#00aa88"><strong>DecodedBarcodesResult</strong></span> {
<span style="color:#006699"><strong>if</strong></span> (bitmap == <span style="color:#006699"><strong>null</strong></span>) <span style="color:#006699"><strong>throw</strong></span> <span style="color:#00aa88"><strong>ScannerException</strong></span>(<span style="color:#cc3300">"Source bitmap is null"</span>)
<span style="color:#006699"><strong>return</strong></span> <span style="color:#cc00ff">unwrap</span>(router.<span style="color:#cc00ff">capture</span>(bitmap, <span style="color:#cc00ff">template</span>()))
}
</code></span></span></span>
ScannerActivity.kt是全屏扫描器。它CapturedResultReceiver接收每个解码帧,一旦找到至少一个条形码,就启用“确认”按钮,并将结果四边形绘制在相机视图的 DBR 绘图层上:
<span style="color:#212529"><span style="color:#333333"><span style="background-color:#eaeaea"><code><span style="color:#0099ff"><em>// inside onDecodedBarcodesReceived -> refreshUi()</em></span>
<span style="color:#006699"><strong>val</strong></span> count = latestItems.size
<span style="color:#006699"><strong>if</strong></span> (count == <span style="color:#ff6600">0</span>) {
statusView.text = <span style="color:#cc00ff">getString</span>(<span style="color:#00aa88"><strong>R</strong></span>.string.status_scanning)
captureButton.isEnabled = <span style="color:#006699"><strong>false</strong></span>
<span style="color:#cc00ff">clearOverlay</span>()
<span style="color:#006699"><strong>return</strong></span>
}
statusView.text = resources.<span style="color:#cc00ff">getQuantityString</span>(<span style="color:#00aa88"><strong>R</strong></span>.plurals.barcodes_found, count, count)
captureButton.isEnabled = <span style="color:#006699"><strong>true</strong></span>
<span style="color:#cc00ff">drawOverlay</span>()
</code></span></span></span>
<span style="color:#212529"><span style="color:#333333"><span style="background-color:#eaeaea"><code><span style="color:#006699"><strong>private</strong></span> <span style="color:#006699"><strong>fun</strong></span> <span style="color:#cc00ff">drawOverlay</span>() {
<span style="color:#006699"><strong>val</strong></span> layer = cameraView.<span style="color:#cc00ff">getDrawingLayer</span>(<span style="color:#00aa88"><strong>DrawingLayer</strong></span>.<span style="color:#00aa88"><strong>DBR_LAYER_ID</strong></span>) <span style="color:#555555">?:</span> <span style="color:#006699"><strong>return</strong></span>
<span style="color:#006699"><strong>val</strong></span> items = <span style="color:#00aa88"><strong>ArrayList</strong></span><<span style="color:#00aa88"><strong>DrawingItem</strong></span><*>>()
<span style="color:#006699"><strong>for</strong></span> (item <span style="color:#006699"><strong>in</strong></span> latestItems) {
item.location<span style="color:#555555">?.</span><span style="color:#cc00ff">let</span> { items.<span style="color:#cc00ff">add</span>(<span style="color:#00aa88"><strong>QuadDrawingItem</strong></span>(it)) }
}
layer.<span style="color:#cc00ff">setDrawingItems</span>(items)
}
</code></span></span></span>
确认操作会将项目(text、、formatString)序列points化为 JSON 数组,并将其作为活动结果返回。与 MRZ 示例类似,BridgeResultActivity它代表startActivityForResult模块发起调用,并使用解析后的 JSON 解析已停放的 Promise;取消操作会产生canceled拒绝。
步骤 5:实现 iOS 原生模块
podspec 依赖于独立的 Dynamsoft 框架(切勿将其与单独的 core 或 license pod 组合在一起——重复的 Objective-C 类会破坏相机预览):
<span style="color:#212529"><span style="color:#333333"><span style="background-color:#eaeaea"><code>s.<span style="color:#cc00ff">dependency</span> <span style="color:#cc3300">'ExpoModulesCore'</span>
s.<span style="color:#cc00ff">dependency</span> <span style="color:#cc3300">'DynamsoftCaptureVisionBundle'</span>, <span style="color:#cc3300">'3.6.2000'</span>
</code></span></span></span>
ExpoDynamsoftBarcodeScannerModule.swift呈现BarcodeCameraScanViewController在主队列中。控制器将一个“ CameraView+”绑定CameraEnhancer到一个CaptureVisionRouter,加载框架内提供的条形码模板,并将解码后的项目应用到其自定义的绿色绘图层中CapturedResultReceiver:
<span style="color:#212529"><span style="color:#333333"><span style="background-color:#eaeaea"><code><span style="color:#006699"><strong>let</strong></span> <span style="color:#003333">bundleCandidates</span> <span style="color:#555555">=</span> <span style="color:#007788"><strong>Bundle</strong></span><span style="color:#555555">.</span>allFrameworks <span style="color:#555555">+</span> <span style="color:#007788"><strong>Bundle</strong></span><span style="color:#555555">.</span>allBundles
<span style="color:#006699"><strong>if</strong></span> <span style="color:#006699"><strong>let</strong></span> <span style="color:#003333">templatePath</span> <span style="color:#555555">=</span> bundleCandidates<span style="color:#555555">.</span><span style="color:#006699"><strong>lazy</strong></span>
<span style="color:#555555">.</span><span style="color:#cc00ff">compactMap</span>({ <span style="color:#003333">$0</span><span style="color:#555555">.</span><span style="color:#cc00ff">path</span>(<span style="color:#003333">forResource</span>: <span style="color:#cc3300">"dbr-bundle-mobile-templates"</span>, <span style="color:#003333">ofType</span>: <span style="color:#cc3300">"json"</span>) })
<span style="color:#555555">.</span>first {
<span style="color:#006699"><strong>try</strong></span>? cvr<span style="color:#555555">.</span><span style="color:#cc00ff">initSettingsFromFile</span>(templatePath)
}
</code></span></span></span>
对于文件数据源,该模块使用相同的模板运行后台解码,并将每个项目的location点(包装为NSValue)转换为普通{x, y}字典:
<span style="color:#212529"><span style="color:#333333"><span style="background-color:#eaeaea"><code><span style="color:#006699"><strong>let</strong></span> <span style="color:#003333">result</span> <span style="color:#555555">=</span> router<span style="color:#555555">.</span><span style="color:#cc00ff">captureFromFile</span>(url<span style="color:#555555">.</span>path, <span style="color:#003333">templateName</span>: <span style="color:#007788"><strong>Constants</strong></span><span style="color:#555555">.</span>templateName)
<span style="color:#006699"><strong>guard</strong></span> <span style="color:#006699"><strong>let</strong></span> <span style="color:#003333">items</span> <span style="color:#555555">=</span> result<span style="color:#555555">.</span>decodedBarcodesResult?<span style="color:#555555">.</span>items <span style="color:#006699"><strong>else</strong></span> { <span style="color:#0099ff"><em>/* reject "No barcodes found" */</em></span> }
<span style="color:#006699"><strong>for</strong></span> item <span style="color:#006699"><strong>in</strong></span> items {
<span style="color:#006699"><strong>var</strong></span> <span style="color:#003333">points</span>: [[<span style="color:#007788"><strong>String</strong></span>: <span style="color:#007788"><strong>Any</strong></span>]] <span style="color:#555555">=</span> []
<span style="color:#006699"><strong>for</strong></span> value <span style="color:#006699"><strong>in</strong></span> item<span style="color:#555555">.</span>location<span style="color:#555555">.</span>points {
<span style="color:#006699"><strong>let</strong></span> <span style="color:#003333">p</span> <span style="color:#555555">=</span> value<span style="color:#555555">.</span>cgPointValue
points<span style="color:#555555">.</span><span style="color:#cc00ff">append</span>([<span style="color:#cc3300">"x"</span>: p<span style="color:#555555">.</span>x, <span style="color:#cc3300">"y"</span>: p<span style="color:#555555">.</span>y])
}
array<span style="color:#555555">.</span><span style="color:#cc00ff">append</span>([<span style="color:#cc3300">"text"</span>: item<span style="color:#555555">.</span>text ?? <span style="color:#cc3300">""</span>, <span style="color:#cc3300">"formatString"</span>: item<span style="color:#555555">.</span>formatString ?? <span style="color:#cc3300">""</span>, <span style="color:#cc3300">"points"</span>: points])
}
promise<span style="color:#555555">.</span><span style="color:#cc00ff">resolve</span>([<span style="color:#cc3300">"results"</span>: array])
</code></span></span></span>
步骤 6:构建并运行应用程序
<span style="color:#212529"><span style="color:#333333"><span style="background-color:#eaeaea"><code>npm <span style="color:#336666">install</span>
<span style="color:#0099ff"><em># Android — builds and installs the debug app on the connected device/emulator</em></span>
npx expo run:android
<span style="color:#0099ff"><em># iOS — builds and installs on a connected iPhone (Xcode signing required)</em></span>
npx expo run:ios <span style="color:#330099"><strong>--device</strong></span>
</code></span></span></span>
使用以下命令生成自包含发布版本:
<span style="color:#212529"><span style="color:#333333"><span style="background-color:#eaeaea"><code><span style="color:#336666">cd </span>android <span style="color:#555555">&&</span> ./gradlew :app:assembleRelease <span style="color:#0099ff"><em># APK in app/build/outputs/apk/release/</em></span>
<span style="color:#0099ff"><em># or: npx expo run:ios --configuration Release --device</em></span>
</code></span></span></span>
将摄像头对准任意条形码——预览会高亮显示每个已解码的条形码,状态栏会显示当前计数。点击“确认”即可打开结果列表,其中包含格式、文本和角点坐标:

常见问题和特殊情况
- 在 iOS 上安装额外的 Dynamsoft pods 后,相机预览显示为黑色:
DynamsoftCaptureVisionBundle已嵌入核心和许可模块。在启动时添加DynamsoftCore或DynamsoftLicense单独注册重复的 Objective-C 类——请将其删除并重新运行pod install。 - 该应用无法在 Expo Go 中运行:该模块注册了自定义活动和视图控制器,因此请使用
npx expo run:android/npx expo run:ios或发布版本。 - 文件扫描显示“图像中未找到条形码”:图像需要包含完整且清晰的条形码。标签损坏或严重倾斜都可能导致扫描失败;请尝试拍摄更清晰的图像。在实时摄像头上,“确认”按钮仅在首次解码后启用。
- 取消操作:在 Android 系统上,按下返回键或取消键会拒绝 Promise 并返回错误信息
canceled(RESULT_CANCELED)。示例程序会过滤掉该错误信息,/cancel/i而不是直接显示错误。 - 试用许可证过期或设备离线:嵌入式密钥有时效性,需要通过 Dynamsoft 的许可证服务器进行验证。发货前,请在
License.ktAndroid 和Constants.licenseKeySwift 模块中替换该密钥。 - 模块名称不匹配:
requireNativeModule('ExpoDynamsoftBarcodeScanner')必须与类名和expo-module.config.json条目(ExpoDynamsoftBarcodeScannerModule在两个平台上)匹配;否则自动链接将不会注册任何内容,并且在启动时导入会抛出异常。
源代码
原生桥接源代码位于modules/expo-dynamsoft-barcode-scanner该项目的文件夹中。有关底层 SDK 的更多信息,请参阅Dynamsoft Capture Vision 文档。
更多推荐



所有评论(0)