近日在工作中接到一个需求,要求使用RN来实现pda扫码功能。这里记录一下我的工作思路和流程,仅供参考。
pda应用的一个主要场景是使用设备自身的红外线扫描仪进行扫描,解码后把结果传输到应用中,在应用中接收到扫描结果后进行业务逻辑处理。那pda设备是如何把结果传输给我们的应用的呢?有两种模式:模拟输入模式和广播模式
-
模拟输入模式: 模拟用户的键盘输入,当设备扫描到结果时,会把结果自动填充到当前页面中聚焦的输入框中。这种模式的优点是通用性强,不需要额外的开发。但缺点也很明显,就是每次扫描前都必须要让input框聚焦,而且软键盘弹出也会遮挡布局。
-
广播模式: 是更高级的扫描结果传递方式。扫描器通过 Android 的广播机制(Intent)将扫描结果发送给应用程序,应用程序可以通过接收广播来获取数据。这种模式的优点是灵活性高,可以接收多种数据,且不依赖输入框的焦点状态。但缺点是需要进行适配,不同的设备型号都有各自的广播名称和传输字段名称
以下是广播模式在RN上的实现步骤
1、Android原生端添加广播接收器
1.1、新增ScannerBroadcastReceiver.java文件, 创建BroadcastReceiver 来接收扫描结果
java
package com.pda;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.Arguments;
public class ScannerBroadcastReceiver extends BroadcastReceiver {
/**
* 接收扫描器发送的广播
*/
@Override
public void onReceive(Context context, Intent intent) {
if (intent != null && intent.getAction() != null) {
String action = intent.getAction();
/**
* 检查广播的 Action 是否是扫描器发送
* android.intent.ACTION_DECODE_DATA为pda设备自身的广播名称,通常在pda设备的设置中可以找到
* 以UROVO这个牌子的设备为例,在设置-扫描设置-输出方式-intent输出-广播动作中可以找到名为"android.intent.ACTION_DECODE_DATA"的广播动作
*/
if ("android.intent.ACTION_DECODE_DATA".equals(action)) {
String scanData = intent.getStringExtra(Constants.PDA_DATA_KEY);
if (scanData != null) {
// 向RN发送结果
} else {
Log.d("onNewIntent", "Scan Data is null");
}
}
}
}
}
1.2、在MainActivity.java中注册广播接收器
java
package com.pda;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactActivityDelegate;
// 新增以下导入
import android.content.Intent;
import android.os.Bundle;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.ReactContext;
import android.content.IntentFilter;
import com.pda.ScannerBroadcastReceiver;
import android.util.Log;
public class MainActivity extends ReactActivity {
private ScannerBroadcastReceiver scannerReceiver; // 扫描器广播接收器
private boolean isReceiverRegistered = false; // 跟踪接收器的状态
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 其他初始化代码
}
@Override
protected void onResume() {
super.onResume();
// 创建并注册广播接收器
scannerReceiver = new ScannerBroadcastReceiver();
IntentFilter filter = new IntentFilter("android.intent.ACTION_DECODE_DATA");
registerReceiver(scannerReceiver, filter);
isReceiverRegistered = true;
}
@Override
protected void onPause() {
super.onPause();
// 注销广播接收器
if (isReceiverRegistered) {
unregisterReceiver(scannerReceiver);
isReceiverRegistered = false;
}
}
// 其他初始代码
}
2、Android与React通信,传输扫描内容
2.1、获取React上下文
与RN通讯需要在Android中获取React的上下文,通过React上下文向React发送消息。
java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ReactInstanceManager reactInstanceManager = getReactNativeHost().getReactInstanceManager();
reactInstanceManager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() {
@Override
public void onReactContextInitialized(ReactContext context) {
Log.d("IntentLog", "初始化完成");
if (context instanceof ReactApplicationContext) {
// 获取reactNative上下文,用于通知rn
reactContext = (ReactApplicationContext) context;
}
}
});
}
我在这一步卡了很久,原因是以前获取React上下文的方式是直接在MainActivity中通过ReactActivity的getReactApplicationContext()方法就可以拿到,但在0.72版本中该方法已经被移除了。所以只能在onCreate事件中监听React初始化完成后才能拿到
2.2、改造ScannerBroadcastReceiver.java,在ScannerBroadcastReceiver类中处理接收到广播后与React的通信
java
package com.pda;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.Arguments;
public class ScannerBroadcastReceiver extends BroadcastReceiver {
private ReactApplicationContext reactContext;
// 通过构造函数传入 ReactApplicationContext
public ScannerBroadcastReceiver(ReactApplicationContext reactContext) {
this.reactContext = reactContext;
}
/**
* 接收扫描器发送的广播
*/
@Override
public void onReceive(Context context, Intent intent) {
if (intent != null && intent.getAction() != null) {
String action = intent.getAction();
/**
* 检查广播的 Action 是否是扫描器发送
* Constants.PDA_ACTION_NAME为pda设备自身的广播名称,通常在pda设备的设置中可以找到
* 以UROVO这个牌子的设备为例,在设置-扫描设置-输出方式-intent输出-广播动作中可以找到名为"android.intent.ACTION_DECODE_DATA"的广播动作
*/
if (Constants.PDA_ACTION_NAME.equals(action)) {
String scanData = intent.getStringExtra(Constants.PDA_DATA_KEY);
if (scanData != null) {
sendScanResultToReactNative(scanData);
} else {
Log.d("IntentLog", "Scan Data is null");
}
}
}
}
/**
* 发送扫描结果到 React Native
*/
private void sendScanResultToReactNative(String scanData) {
if (this.reactContext != null) {
WritableMap params = Arguments.createMap();
params.putString("scanData", scanData);
sendEvent("onBarcodeScanned", params);
} else {
Log.d("IntentLog", "reactContext为空");
}
}
/**
* 发送事件
*/
private void sendEvent(String eventName, WritableMap params) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
}
2.3、改造MainActivity.java,传递React上下文给ScannerBroadcastReceiver
java
package com.pda;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
import com.facebook.react.defaults.DefaultReactActivityDelegate;
// 新增以下导入
import android.content.Intent;
import android.os.Bundle;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.ReactContext;
import android.content.IntentFilter;
import com.pda.ScannerBroadcastReceiver;
import android.util.Log;
public class MainActivity extends ReactActivity {
private ReactApplicationContext reactContext; // reactNative上下文
private ScannerBroadcastReceiver scannerReceiver; // 扫描器广播接收器
private boolean isReceiverRegistered = false; // 跟踪接收器的状态
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ReactInstanceManager reactInstanceManager = getReactNativeHost().getReactInstanceManager();
reactInstanceManager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() {
@Override
public void onReactContextInitialized(ReactContext context) {
Log.d("IntentLog", "初始化完成");
if (context instanceof ReactApplicationContext) {
// 获取reactNative上下文,用于通知rn
reactContext = (ReactApplicationContext) context;
// 创建广播接收器
scannerReceiver = new ScannerBroadcastReceiver(reactContext);
// 注册广播接收器
registerScannerReceiver();
}
}
});
}
/**
* 应用切换到前台
* 注册广播接收器,监听扫码事件
*/
@Override
protected void onResume() {
super.onResume();
Log.d("IntentLog", "onResume");
// 注册广播接收器
registerScannerReceiver();
}
/**
* 应用切换到后台
* 注销广播接收器,防止在应用后台时扫码也触发业务逻辑
*/
@Override
protected void onPause() {
super.onPause();
Log.d("IntentLog", "onPause");
// 注销广播接收器
if (scannerReceiver != null && isReceiverRegistered) {
unregisterReceiver(scannerReceiver);
isReceiverRegistered = false; // 更新状态为未注册
Log.d("IntentLog", scannerReceiver.toString());
}
}
/**
* 注册广播接收器
*/
private void registerScannerReceiver() {
if (scannerReceiver != null && !isReceiverRegistered) {
IntentFilter filter = new IntentFilter(Constants.PDA_ACTION_NAME);
registerReceiver(scannerReceiver, filter);
isReceiverRegistered = true; // 更新状态为已注册
}
}
// 其他原始代码
}
3、RN接收Android发送来的消息
tsx
import React, { useState } from 'react';
import {
Text,
DeviceEventEmitter,
} from 'react-native';
function App(): JSX.Element {
const [scanData, setScanData] = useState('');
useEffect(() => {
// 监听来自 Android 端的扫描结果事件
const subscription = DeviceEventEmitter.addListener(
'onBarcodeScanned', // 与Android端发送的事件名一致
event => {
console.log('接受到广播', event);
setScanData(event.scanData);
},
);
// 清理监听器
return () => {
subscription.remove();
};
}, []);
return (
<Text>扫描到的条形码: {scanData}</Text>
);
}
export default App;
Android端的一些调试技巧:
通过Log.d输出日志,确保连接adb后,真机运行启动,在终端输入
perl
adb logcat | grep "IntentLog"
就可以查看实时日志,grep "IntentLog"是筛选关键词为IntentLog的日志