QR 码扫描¶
本文档介绍了 QR 码扫描功能的实现方式及其如何暴露给 WebView 层,内容包括:
- JavaScript 用于启动扫描的桥接 API
- 权限处理方式
- 如何启动华为 ScanKit Activity
- 扫描结果如何回传给 JavaScript
QR 扫描功能在 BaseWebViewActivity 中实现,并通过 bridgeWebView 暴露给 WebView。
概述¶
QR 扫描流程如下:
┌──────────┐
│ START │
└────┬─────┘
│
▼
┌─────────────────────────┐
│ Check Permissions │
│ - CAMERA │
│ - READ_MEDIA │
└────┬────────────────────┘
│
▼
┌─────────────────────────┐
│ Register Callback │
│ scanQrcodeResultCallback│
└────┬────────────────────┘
│
▼
┌─────────────────────────┐
│ startQrCodeScan(999) │
└────┬────────────────────┘
│
▼
┌─────────────────────────┐
│ Open Camera Scanner │
│ │
│ ┌─────────────────┐ │
│ │ [QR Frame] │ │
│ │ │ │
│ │ Point camera │ │
│ │ at QR code │ │
│ └─────────────────┘ │
└────┬────────────────────┘
│
│ (User scans or cancels)
│
├────────────┬─────────────┐
▼ ▼ ▼
Success Cancel Error
│
▼
┌─────────────────────────┐
│ Decode QR Data │
│ Extract value │
└────┬────────────────────┘
│
▼
┌─────────────────────────┐
│ scanQrcodeResultCallback│
│ { │
│ value: "QR_CONTENT", │
│ requestCode: 999 │
│ } │
└────┬────────────────────┘
│
▼
┌─────────────────────────┐
│ Process QR Data │
│ in JavaScript │
│ - URL redirect │
│ - Parse JSON │
│ - etc. │
└─────────────────────────┘
│
▼
┌──────────┐
│ END │
└──────────┘
- JavaScript 调用原生桥接方法
startQrCodeScan。 - 原生代码检查并请求必要的权限(摄像头等)。
- 权限授予后,原生配置华为 ScanKit 并启动 ScanKit Activity。
- 用户扫描 QR 码(或取消)。
BaseWebViewActivity.onActivityResult()接收结果,提取扫描值,并通过scanQrcodeResultCallBack将其回传给 JavaScript。
整个流程完全由事件驱动:WebView 触发扫描,并通过回调异步接收结果。
原生–WebView 桥接 API¶
startQrCodeScan(JS → 原生)
桥接处理器名称:startQrCodeScan
定义于:BaseWebViewActivity 内的 registerMethod()
原生侧签名:
bridgeWebView.registerHandler("startQrCodeScan", new BridgeHandler() {
@Override
public void handler(String requestCode, CallBackFunction function) {
// ...
}
});
参数¶
requestCode(String)
- 可选,由 JavaScript 发送。
- 如果为空,`REQUEST_CODE_SCAN_ONE` 默认为 0。
- 如果非空,则解析为整数并存储在 `REQUEST_CODE_SCAN_ONE` 中。
- 允许 JS 侧在需要时区分多个扫描请求。
原生逻辑示例:
REQUEST_CODE_SCAN_ONE = TextUtils.isEmpty(requestCode)
? 0
: Integer.valueOf(requestCode);
立即原生行为¶
在 handler(...) 内部,原生代码:
- 使用
XXPermissions(配合自定义PermissionInterceptor)请求运行时权限。 - 如果未授予所有必需权限,立即向 JS 回调权限错误:
function.onCallBack(gson.toJson(Result.fail(PERMISSION_ERROR, false))); return; - 如果权限已授予,则构建华为 ScanKit 配置并启动扫描 Activity。
权限处理¶
通过 XXPermissions 请求权限:
XXPermissions.with(BaseWebViewActivity.this)
// (Camera and any related permissions are configured here)
.interceptor(new PermissionInterceptor())
.request(new OnPermissionCallback() {
@Override
public void onGranted(@NonNull List<String> permissions, boolean allGranted) {
if (!allGranted) {
function.onCallBack(gson.toJson(Result.fail(PERMISSION_ERROR, false)));
return;
}
// Permissions granted → proceed to launch scanner
// ...
}
});
关键要点:¶
- 在启动任何扫描 UI 之前请求权限。
- 如果用户拒绝任何必需权限,WebView 收到包含
Result.fail(PERMISSION_ERROR, false)的响应,且不会启动扫描 Activity。 - 实际权限列表(摄像头、存储等)在原生代码中配置,可根据需要调整。
启动扫描 UI(华为 ScanKit)¶
权限授予后,代码构建 HmsScanAnalyzerOptions 并启动华为 ScanKit Activity:
HmsScanAnalyzerOptions.Creator creator = new HmsScanAnalyzerOptions.Creator();
// Accept all types of barcodes / QR codes
creator.setHmsScanTypes(HmsScan.ALL_SCAN_TYPE);
// Show the built-in scan guide overlay
creator.setShowGuide(true);
// Allow scanning from gallery (photo mode)
creator.setPhotoMode(true);
HmsScanAnalyzerOptions hmsScanAnalyzerOptions = creator.create();
// Launch ScanKit activity
Intent intent = new Intent(BaseWebViewActivity.this, ScanKitActivity.class);
if (intent != null) {
intent.putExtra("ScanFormatValue", hmsScanAnalyzerOptions.mode);
}
startActivityForResult(intent, REQUEST_CODE_SCAN_ONE);
注意事项:
HmsScan.ALL_SCAN_TYPE启用多种条形码格式的识别,而不仅仅是 QR 码。setShowGuide(true)在摄像头预览上显示引导叠加层,帮助用户对准。setPhotoMode(true)允许用户从相册中选择现有图片进行扫描。REQUEST_CODE_SCAN_ONE稍后在onActivityResult中用于识别结果。
如果在构建选项或启动 Activity 时发生异常,将被捕获并记录:
}
catch (Exception e) {
e.printStackTrace();
}
接收扫描结果(原生)¶
扫描结果在 onActivityResult 中处理:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_SCAN_ONE && resultCode == RESULT_OK) {
HmsScan obj = data.getParcelableExtra(ScanUtil.RESULT);
Toaster.show(obj.originalValue);
if (obj != null) {
Map<String, Object> map = new HashMap<>();
map.put("value", obj.originalValue);
map.put("requestCode", REQUEST_CODE_SCAN_ONE);
bridgeWebView.callHandler("scanQrcodeResultCallBack",
gson.toJson(Result.ok(map)),
new CallBackFunction() {
@Override
public void onCallBack(String data) {
}
});
} else {
bridgeWebView.callHandler("scanQrcodeResultCallBack",
gson.toJson(Result.fail()),
new CallBackFunction() {
@Override
public void onCallBack(String data) {
}
});
}
}
// ... (other onActivityResult branches: picture choose, OCR, etc.)
}
重要细节¶
- 只有当
requestCode == REQUEST_CODE_SCAN_ONE且resultCode == RESULT_OK时,才将其视为有效的扫描结果。 HmsScan obj = data.getParcelableExtra(ScanUtil.RESULT);从 Intent 中获取HmsScan结果对象。obj.originalValue包含 QR 码/条形码中编码的字符串值。- 显示一个简短的 Toast(
Toaster.show(obj.originalValue);)用于调试/快速反馈。 -
如果
obj非空:- 创建一个
map,包含:"value"→ 扫描到的字符串(obj.originalValue)"requestCode"→ 使用的请求码(使 JS 可以关联响应)
- 将 map 用
Result.ok(map)包装后通过scanQrcodeResultCallBack发送回 JS。
- 创建一个
-
如果
obj为空:- 原生调用
scanQrcodeResultCallBack并传入Result.fail()。
- 原生调用
取消处理:
- 如果用户取消 ScanKit 界面,`resultCode` 将不是 `RESULT_OK`,因此 QR 分支被跳过。
- 此时不发送成功负载;JS 侧应通过将"未收到结果"视为已取消操作来处理,或根据需要添加单独的机制。
JavaScript 侧使用方法¶
1. 注册结果回调¶
在 WebView JavaScript 中,您必须注册处理器以接收扫描结果
bridgeWebView.registerHandler("scanQrcodeResultCallBack", function (data) {
const res = JSON.parse(data);
// Depending on your Result implementation, this may look like:
// { success: true/false, code: ..., data: { value, requestCode } }
if (res.success) {
const value = res.data.value;
const reqCode = res.data.requestCode;
console.log("QR scan success:", value, "(req:", reqCode, ")");
// TODO: handle the scanned value (navigate, fill form, etc.)
} else {
console.warn("QR scan failed", res);
// TODO: show error message or retry option
}
});
2. 从 JS 启动 QR 扫描¶
当您想要开始扫描时:
function startQrScan() {
const REQUEST_CODE_QR = 1001; // any integer you want to track this call
bridgeWebView.callHandler(
"startQrCodeScan",
String(REQUEST_CODE_QR), // this becomes requestCode in native
function (response) {
// Initial response from native (permission / immediate errors)
const res = JSON.parse(response || "{}");
if (res.success === false) {
console.error("Failed to start QR scan:", res);
// Probably permission denied or some immediate error.
} else {
// In most cases, you simply wait for scanQrcodeResultCallBack.
console.log("QR scan started; waiting for result...");
}
}
);
}
从 JS 角度看的流程:
- 调用
startQrCodeScan。 - 可选地检查立即响应(用于权限错误)。
- 等待包含最终结果的
scanQrcodeResultCallBack。
结果负载结构¶
所有响应在发送给 JS 之前都用内部 Result 类包装,例如:
-
成功(概念结构):
{ "success": true, "code": 0, "data": { "value": "scanned-qr-content", "requestCode": 1001 } } -
失败时
{ "success": false, "code": "<error-code>", "message": "<human-readable message>" }
确切的键(code、message 等)取决于您的 Result 实现,但重要的是 data.value 保存扫描到的字符串,data.requestCode 让您将结果映射到特定的扫描请求。
错误处理与边界情况¶
-
权限被拒绝
startQrCodeScan立即返回Result.fail(PERMISSION_ERROR, false)。- 扫描 UI 从不打开。
- JS 应显示消息,要求用户授予摄像头权限。
-
扫描返回空结果(
obj == null)scanQrcodeResultCallBack以Result.fail()调用。- JS 应将此视为"扫描失败"并允许重试。
-
用户取消 ScanKit
resultCode不是RESULT_OK。onActivityResult中跳过 QR 分支。- 不发送成功负载;JS 应将此处理为"无结果"(如通过超时或用户流程)。
-
启动 ScanKit 时发生意外异常
- 目前仅通过
e.printStackTrace()记录。 - 您可能希望扩展此功能以向 JS 发送失败的
Result,以改善用户体验。
- 目前仅通过
总结¶
-
QR 扫描实现向 WebView 提供了简洁的高级 API:
- JS 入口点:
startQrCodeScan - 原生扫描器:通过
ScanKitActivity使用华为 ScanKit - 向 JS 的结果回调:
scanQrcodeResultCallBack,负载包含扫描值和 requestCode 的 Result 封装对象。
- JS 入口点:
这将所有摄像头和 ScanKit 细节保留在原生层,同时保持 WebView 集成简单可预测。