You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native外接USB设备读取指定文件的Java模块扩展需求

React Native USB设备文件查找与分享功能扩展方案

针对你的需求,我们在已有的USB设备监听模块基础上,添加USB权限请求、存储设备识别、文件查找和系统分享功能,以下是完整实现方案:

一、扩展后的Java原生模块代码

package com.awesomeproject.usb;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.net.Uri;
import android.os.Environment;
import android.util.Log;
import android.app.PendingIntent;

import androidx.annotation.NonNull;
import androidx.core.content.FileProvider;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;

import java.io.File;
import java.util.HashMap;
import java.util.Locale;

public class UsbDeviceListenerModule extends ReactContextBaseJavaModule implements LifecycleEventListener {

    private static final String TAG = "UsbDeviceListener";
    private static final String ACTION_USB_PERMISSION = "com.awesomeproject.USB_PERMISSION";
    private static final String TARGET_FILE_NAME = "DataXYZ.txt";

    private final ReactApplicationContext reactContext;
    private final BroadcastReceiver usbReceiver;
    private final BroadcastReceiver permissionReceiver;
    private boolean isInitialized = false;

    public UsbDeviceListenerModule(ReactApplicationContext reactContext) {
        super(reactContext);
        this.reactContext = reactContext;
        reactContext.addLifecycleEventListener(this);

        // USB设备连接/断开监听
        usbReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
                    UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                    if (device != null) {
                        // 仅处理存储类设备
                        if (device.getDeviceClass() == UsbDevice.USB_CLASS_MASS_STORAGE) {
                            requestUsbPermission(device);
                        } else {
                            sendDeviceConnectedEvent(device, false);
                        }
                    }
                } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
                    UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                    if (device != null) {
                        sendDeviceDisconnectedEvent(device);
                    }
                }
            }
        };

        // USB权限请求结果监听
        permissionReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                if (ACTION_USB_PERMISSION.equals(action)) {
                    synchronized (this) {
                        UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                        if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                            if (device != null) {
                                sendDeviceConnectedEvent(device, true);
                                // 权限获取成功,开始查找文件
                                findAndShareTargetFile();
                            }
                        } else {
                            Log.d(TAG, "USB权限被拒绝");
                            sendDeviceConnectedEvent(device, false);
                        }
                    }
                }
            }
        };

        // 注册广播接收器
        IntentFilter filter = new IntentFilter();
        filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
        filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
        reactContext.registerReceiver(usbReceiver, filter);

        IntentFilter permissionFilter = new IntentFilter(ACTION_USB_PERMISSION);
        reactContext.registerReceiver(permissionReceiver, permissionFilter);
    }

    @NonNull
    @Override
    public String getName() {
        return "UsbDeviceListener";
    }

    @Override
    public void invalidate() {
        super.invalidate();
        reactContext.unregisterReceiver(usbReceiver);
        reactContext.unregisterReceiver(permissionReceiver);
        reactContext.removeLifecycleEventListener(this);
    }

    @Override
    public void onHostResume() {
        if (!isInitialized) {
            checkForConnectedUsbDevices();
            isInitialized = true;
        }
    }

    @Override
    public void onHostPause() {}

    @Override
    public void onHostDestroy() {}

    // 请求USB设备访问权限
    private void requestUsbPermission(UsbDevice device) {
        UsbManager usbManager = (UsbManager) reactContext.getSystemService(Context.USB_SERVICE);
        usbManager.requestPermission(device, PendingIntent.getBroadcast(reactContext, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE));
    }

    // 检查已连接的USB设备
    private void checkForConnectedUsbDevices() {
        UsbManager usbManager = (UsbManager) reactContext.getSystemService(Context.USB_SERVICE);
        HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
        if (!deviceList.isEmpty()) {
            for (UsbDevice device : deviceList.values()) {
                if (device.getDeviceClass() == UsbDevice.USB_CLASS_MASS_STORAGE) {
                    if (usbManager.hasPermission(device)) {
                        sendDeviceConnectedEvent(device, true);
                        findAndShareTargetFile();
                    } else {
                        requestUsbPermission(device);
                    }
                } else {
                    sendDeviceConnectedEvent(device, false);
                }
            }
        }
    }

    // 查找目标文件并触发分享
    private void findAndShareTargetFile() {
        // 获取USB存储挂载路径(Android 10+需适配Scoped Storage,这里以传统路径为例)
        File[] externalStorages = Environment.getExternalStorageDirectory().listFiles();
        if (externalStorages != null) {
            for (File storage : externalStorages) {
                if (storage.isDirectory() && !storage.getAbsolutePath().equals(Environment.getExternalStorageDirectory().getAbsolutePath())) {
                    File targetFile = searchFile(storage, TARGET_FILE_NAME);
                    if (targetFile != null) {
                        shareFile(targetFile);
                        sendFileFoundEvent(targetFile.getAbsolutePath());
                        return;
                    }
                }
            }
        }
        sendFileNotFoundEvent();
    }

    // 递归查找文件
    private File searchFile(File directory, String fileName) {
        File[] files = directory.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    File result = searchFile(file, fileName);
                    if (result != null) {
                        return result;
                    }
                } else {
                    if (file.getName().toLowerCase(Locale.ROOT).equals(fileName.toLowerCase(Locale.ROOT))) {
                        return file;
                    }
                }
            }
        }
        return null;
    }

    // 调用系统分享功能
    private void shareFile(File file) {
        if (!file.exists()) return;

        Uri fileUri = FileProvider.getUriForFile(reactContext, reactContext.getPackageName() + ".fileprovider", file);
        Intent shareIntent = new Intent(Intent.ACTION_SEND);
        shareIntent.setType("text/plain");
        shareIntent.putExtra(Intent.EXTRA_STREAM, fileUri);
        shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

        Intent chooser = Intent.createChooser(shareIntent, "分享DataXYZ.txt文件");
        chooser.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        reactContext.startActivity(chooser);
    }

    // 发送设备连接事件到RN端
    private void sendDeviceConnectedEvent(UsbDevice device, boolean hasPermission) {
        WritableMap deviceData = Arguments.createMap();
        deviceData.putString("deviceName", device.getDeviceName());
        deviceData.putInt("vendorId", device.getVendorId());
        deviceData.putInt("productId", device.getProductId());
        deviceData.putBoolean("hasPermission", hasPermission);
        deviceData.putBoolean("isStorageDevice", device.getDeviceClass() == UsbDevice.USB_CLASS_MASS_STORAGE);

        if (reactContext.hasActiveCatalystInstance()) {
            reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                    .emit("UsbDeviceConnected", deviceData);
        }
    }

    // 发送设备断开事件到RN端
    private void sendDeviceDisconnectedEvent(UsbDevice device) {
        WritableMap deviceData = Arguments.createMap();
        deviceData.putString("deviceName", device.getDeviceName());

        if (reactContext.hasActiveCatalystInstance()) {
            reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                    .emit("UsbDeviceDisconnected", deviceData);
        }
    }

    // 发送文件找到事件到RN端
    private void sendFileFoundEvent(String filePath) {
        WritableMap data = Arguments.createMap();
        data.putString("filePath", filePath);

        if (reactContext.hasActiveCatalystInstance()) {
            reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                    .emit("TargetFileFound", data);
        }
    }

    // 发送文件未找到事件到RN端
    private void sendFileNotFoundEvent() {
        if (reactContext.hasActiveCatalystInstance()) {
            reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                    .emit("TargetFileNotFound", null);
        }
    }

    // RN端可调用的手动查找方法
    @ReactMethod
    public void triggerFileSearch() {
        findAndShareTargetFile();
    }
}

二、必要的配置

1. AndroidManifest.xml配置

添加USB权限和FileProvider配置:

<!-- USB权限 -->
<uses-permission android:name="android.permission.USB_PERMISSION" />
<uses-feature android:name="android.hardware.usb.host" android:required="false" />

<!-- FileProvider配置(用于Android 7+文件分享) -->
<application>
    <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="${applicationId}.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
    </provider>
</application>

2. 创建file_paths.xml文件

在res/xml目录下创建该文件,定义文件共享路径:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="." />
</paths>

三、React Native端调用示例

import { NativeEventEmitter, NativeModules } from 'react-native';

const { UsbDeviceListener } = NativeModules;
const usbDeviceEmitter = new NativeEventEmitter(UsbDeviceListener);

// 监听设备连接事件
usbDeviceEmitter.addListener('UsbDeviceConnected', (device) => {
    console.log('USB设备连接:', device);
    if (device.isStorageDevice && device.hasPermission) {
        console.log('开始查找目标文件');
        // 可手动触发查找,也可等待原生自动触发
        // UsbDeviceListener.triggerFileSearch();
    }
});

// 监听设备断开事件
usbDeviceEmitter.addListener('UsbDeviceDisconnected', (device) => {
    console.log('USB设备断开:', device);
});

// 监听文件找到事件
usbDeviceEmitter.addListener('TargetFileFound', (data) => {
    console.log('找到目标文件:', data.filePath);
});

// 监听文件未找到事件
usbDeviceEmitter.addListener('TargetFileNotFound', () => {
    console.log('未找到目标文件DataXYZ.txt');
});

四、关键说明

  1. 权限处理:通过UsbManager.requestPermission请求USB设备访问权限,确保能读取USB存储内容
  2. 存储设备识别:通过device.getDeviceClass() == UsbDevice.USB_CLASS_MASS_STORAGE判断是否为存储类设备
  3. 文件查找:递归遍历USB存储目录,忽略大小写查找DataXYZ.txt
  4. 文件分享:使用FileProvider生成安全的文件URI,调用系统分享Intent完成分享
  5. Android版本适配:Android 10+需注意Scoped Storage限制,若遇到路径访问问题,需适配MediaStore或使用MANAGE_EXTERNAL_STORAGE权限

内容的提问来源于stack exchange,提问作者Yuriy Proniagin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 15:04:57