Android硬件通信之 USBManager通信

devtools/2025/1/15 13:56:31/

一 简介

1.1 USB是一种常见的设备端接口,在PC端,移动端等上面应用非常的广泛,这就涉及到USB设备与主机端的通信。下面就讲下Android设备上的USB通信流程。

1.2 在android提供了USBManager接口来管理USB设备,USBManager类是Android系统管理USB设备与操作的核心组件,涉及设备识别、权限请求、数据传输、事件监听等。通过USBManager,开发者可以实现设备模式切换、USB权限管理、配置选择、自定义USB驱动程序开发以及支持MIDI和音频设备的管理。

二 USBManager通信流程

2.1 添加USB权限和特性声明

<uses-permission android:name="android.permission.USB_PERMISSION" />
<uses-feature android:name="android.hardware.usb.host" />

2.2 初始化USB通信

   //设备管理器private UsbManager usbManager;//连接的设备private UsbDevice currentDevice = null;//连接状态类private UsbDeviceConnection connection;//USB连接接口private UsbInterface usbInterface;//请求权限private String ACTION_USB_PERMISSION = "com.your.package.USB_PERMISSION";private BroadcastReceiver mUsbBroadcast;/*** USB请求权限*/private class UsbBroadcast extends BroadcastReceiver {public void onReceive(Context paramAnonymousContext, Intent paramAnonymousIntent) {try {String action = paramAnonymousIntent.getAction();// 判断广播类型if (ACTION_USB_PERMISSION.equals(action)) {try {// 如果用户同意,则对读写器进行操作connectDevice();} catch (Exception e) {disconnectDevice();}}} catch (Exception e) {e.printStackTrace();}}}

2.3  注册USB广播接收器

IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
registerReceiver(usbReceiver, filter);

2.4 搜索USB设备

    /*** 获取Usb列表* @return*/public List<UsbDevice> getUsbPrinters() {List<UsbDevice> usbDevices = new ArrayList<>();usbManager = (UsbManager) App.getContext().getSystemService(Context.USB_SERVICE);HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();while (deviceIterator.hasNext()) {UsbDevice device = deviceIterator.next();if (isCscidDevice(device)) {usbDevices.add(device);}}return usbDevices;}/*** 判断UsbDevice 是否为读卡器类型设备* @param device* @return true:读卡器设备*/public boolean isCscidDevice(UsbDevice device) {if (device == null) {return false;}if (device.getInterfaceCount() == 0) {return false;}for (int i = 0; i < device.getInterfaceCount(); i++) {android.hardware.usb.UsbInterface usbInterface = device.getInterface(i);if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {int productId = device.getProductId();int vendorId = device.getVendorId();if (productId == 10203 && vendorId == 5824) {return true;}}}return false;}

2.5 请求权限,并连接设备

    /*** 连接设备*/public void connectDevice() {try {//权限请求/** 在使用USB读写器设备前,应用必须获得权限。* 为了确切地获得权限,首先需要创建一个广播接收器。在调用requestPermission()这个方法时从得到的广播中监听这个意图。* 通过调用requestPermission()这个方法为用户跳出一个是否连接该设备的对话框。*/if (mUsbBroadcast == null) {mUsbBroadcast = new UsbBroadcast();IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);App.getContext().registerReceiver(mUsbBroadcast, filter);}//获取全部设备if (currentDevice == null) {List<UsbDevice> usbDeviceList = getUsbPrinters();if (usbDeviceList.size() > 0) {currentDevice = usbDeviceList.get(0);}}if (currentDevice != null) {// 检查设备是否具有所需的权限if (usbManager.hasPermission(currentDevice)) {// 打开设备并进行通信if (connection == null) {connection = usbManager.openDevice(currentDevice);}if (connection != null) {// 获取接口并配置if (usbInterface == null) {usbInterface = currentDevice.getInterface(0);}connection.claimInterface(usbInterface, true);// 你可以在这里与设备进行通信//readDataFromDevice();Log.d(TAG, "连接到设备: " + currentDevice.getDeviceName());} else {Log.e(TAG, "无法打开设备: " + currentDevice.getDeviceName());}} else {// 如果没有则请求权限PendingIntent mPermissionIntent = PendingIntent.getBroadcast(JuAiApp.getContext(), 0,new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE);usbManager.requestPermission(currentDevice, mPermissionIntent);}}} catch (Exception r) {r.printStackTrace();}}

2.6 发送数据,先获取UsbEndpoint输出流端点

/*** 发送数据** @param message*/UsbEndpoint endpointOut = null;private void sendDataToDevice(String message) {// 获取输出端点if (endpointOut == null) {for (int i = 0; i < usbInterface.getEndpointCount(); i++) {UsbEndpoint endpoint = usbInterface.getEndpoint(i);if (endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK &&endpoint.getDirection() == UsbConstants.USB_DIR_OUT) {endpointOut = endpoint; // 获取输出端点break;}}}if (endpointOut == null || connection == null) {Log.e(TAG, "输出端点或连接未初始化");return;}byte[] buffer = message.getBytes(); // 将要发送的字符串转换为字节数组int bytesSent = connection.bulkTransfer(endpointOut, buffer, buffer.length, 1000); // 1秒超时if (bytesSent < 0) {Log.e(TAG, "发送数据失败");} else {Log.d(TAG, "成功发送数据: " + message);}}

2.7 读取数据,先获取输入流端点

/*** 读取数据*/UsbEndpoint endpointIn = null;private static final int BUFFER_SIZE = 1024; // 定义读取的缓冲区大小private void readDataFromDevice() {if (endpointIn == null) {// 获取输入端点for (int i = 0; i < usbInterface.getEndpointCount(); i++) {UsbEndpoint endpoint = usbInterface.getEndpoint(i);Log.e(TAG, "readDataFromDevice: " + endpoint.getType());Log.e(TAG, "getDirection: " + endpoint.getDirection());if (endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_INT &&endpoint.getDirection() == UsbConstants.USB_DIR_IN) {endpointIn = endpoint; // 获取输入端点break;}}}if (endpointIn == null || connection == null) {Log.e(TAG, "输入端点或连接未初始化");return;}byte[] buffer = new byte[BUFFER_SIZE]; // 创建缓冲区// 循环读取数据Thread readThread = new Thread(new Runnable() {@Overridepublic void run() {while (true) {int bytesRead = connection.bulkTransfer(endpointIn, buffer, buffer.length, 1000); // 1秒超时if (bytesRead > 0) {// 处理读取到的数据String data = new String(buffer, 0, bytesRead); // 将字节数组转换为字符串Log.d(TAG, "读取数据: " + data);} else if (bytesRead == 0) {Log.d(TAG, "没有更多数据可读");break;} else {Log.e(TAG, "读取数据失败: " + bytesRead);break;}}}});readThread.start(); // 启动读取线程}

2.8 断开USB设备并关闭资源

/*** 断开设备*/public void disconnectDevice() {try {if (usbManager != null && currentDevice != null && usbInterface != null) {// 获取之前连接的接口// 释放接口connection.releaseInterface(usbInterface);// 关闭连接connection.close();// 清空连接connection = null;usbInterface = null;//读取置空endpointIn = null;//写入置空endpointOut = null;//设备置空currentDevice = null;if (mUsbBroadcast != null) {App.getContext().unregisterReceiver(mUsbBroadcast);}Log.d(TAG, "设备已断开: ");}} catch (Exception e) {e.printStackTrace();}}

三 USB协议相关概念

3.1 USB常见类型,用于判断是哪种USB设备

//获取设备类型
for (int i = 0; i < device.getInterfaceCount(); i++) {android.hardware.usb.UsbInterface usbInterface = device.getInterface(i);/*** 设备类 ID	设备类名称	描述* 0x01	Audio	音频设备,如 USB 音频接口。* 0x02	Communications	通信设备,如调制解调器和网络适配器。* 0x03	HID (Human Interface Device)	人机接口设备,通常指键盘、鼠标等。* 0x05	Physical Interface Device	物理接口设备,通常用于各种控制设备。* 0x06	Image	图像设备,如 USB 摄像头和扫描仪。* 0x07	Printer	打印机设备。* 0x08	Mass Storage	大容量存储设备,如 USB 闪存驱动器和外部硬盘。* 0x09	Hub	USB 集线器。* 0x0A	CDC (Communications Device Class)	通信设备类,常用于调制解调器和串行口。* 0x0B	Smart ICCard	智能卡读卡器。* 0x0D	Content Security Device	内容保护设备,用于安全性应用。* 0x0E	Video	视频设备,如视频采集卡。* 0xEF	Miscellaneous	其他不属于上述类别的设备。* 0xFF	Vendor Specific	厂商自定义设备,通常特定于某个硬件厂商的设备。* @param deviceClass* @param deviceSubclass* @param deviceProtocol* @return*/if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {int productId = device.getProductId();int vendorId = device.getVendorId();if (productId == 10203 && vendorId == 5824) {return true;}}}
ID设备类名称描述
0x01Audio音频设备,如 USB 音频接口。
0x02Communications通信设备,如调制解调器和网络适配器。
0x03HID(Human Interface Device)人机接口设备,通常指键盘、鼠标等。
0x05Physical Interface Device物理接口设备,通常用于各种控制设备。
0x06Image图像设备,如 USB 摄像头和扫描仪。
0x07Printer打印机设备。
0x08Mass大容量存储设备,如 USB 闪存驱动器和外部硬盘。
0x09Hub    USB 集线器。
0x0ACDC通信设备类,常用于调制解调器和串行口。
0x0BSmart ICCard智能卡读卡器。
0x0DContent Security Device内容保护设备,用于安全性应用。
0x0EVideo视频设备,如视频采集卡。
0x0FMiscellaneous其他不属于上述类别的设备。
0x0FVendor Specific

厂商自定义设备,通常特定于某个硬件厂商的设

3.2 设备信息参数

//获取设备类型
if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {//产品IDint productId = device.getProductId();//厂商IDint vendorId = device.getVendorId();if (productId == 10203 && vendorId == 5824) {return true;}
}
* DeviceName=/dev/bus/usb/005/007         USB端口名字
* ProductName=null                        产品名字
* DeviceId=5007(0x138F)                   设备ID
* VendorId=5824(0x16C0)                   厂商ID
* ProductId=10203(0x27DB)                 产品ID
* DeviceClass=0                           设备分类
* device Class 为0-------------      
* Interface.describeContents()=0          接口描述
* Interface.getEndpointCount()=2          接口端点数量
* Interface.getId()=0                     ID接口
* Interface.getInterfaceClass()=3         接口数量
* anInterface.getInterfaceProtocol()=0    接口协议数量
* anInterface.getInterfaceSubclass()=0
* device Class 为0------end-------

四 示例代码

public class USBManagerUtils {private static final String TAG = "USBManagerUtils";private static USBManagerUtils mInstance;public static USBManagerUtils getInstance() {if (mInstance == null) {synchronized (USBManagerUtils.class) {if (mInstance == null) {mInstance = new USBManagerUtils();}}}return mInstance;}//设备管理器private UsbManager usbManager;//连接的设备private UsbDevice currentDevice = null;//连接状态类private UsbDeviceConnection connection;//USB连接接口private UsbInterface usbInterface;//请求权限private String ACTION_USB_PERMISSION = "com.your.package.USB_PERMISSION";private BroadcastReceiver mUsbBroadcast;/*** USB请求权限*/private class UsbBroadcast extends BroadcastReceiver {public void onReceive(Context paramAnonymousContext, Intent paramAnonymousIntent) {try {String action = paramAnonymousIntent.getAction();// 判断广播类型if (ACTION_USB_PERMISSION.equals(action)) {try {// 如果用户同意,则对读写器进行操作connectDevice();} catch (Exception e) {disconnectDevice();}}} catch (Exception e) {e.printStackTrace();}}}/*** 获取Usb 打印机列表* get USB printer list** @return*/public List<UsbDevice> getUsbPrinters() {List<UsbDevice> usbDevices = new ArrayList<>();usbManager = (UsbManager) App.getContext().getSystemService(Context.USB_SERVICE);HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();while (deviceIterator.hasNext()) {UsbDevice device = deviceIterator.next();if (isCscidDevice(device)) {usbDevices.add(device);}}return usbDevices;}/*** 判断UsbDevice 是否为读卡器类型设备* check a UsbDevice* DeviceName=/dev/bus/usb/005/007* ProductName=null* DeviceId=5007(0x138F)* VendorId=5824(0x16C0)* ProductId=10203(0x27DB)* DeviceClass=0* device Class 为0-------------* Interface.describeContents()=0* Interface.getEndpointCount()=2* Interface.getId()=0* Interface.getInterfaceClass()=3* anInterface.getInterfaceProtocol()=0* anInterface.getInterfaceSubclass()=0* device Class 为0------end-------** @param device* @return true:读卡器设备* 对于同一型号的USB产品,其 vendorId 和 productId 通常是一致的,因为它们来自同一个制造商且属于同一产品系列。例如,如果一个制造商生产了一个特定的USB闪存驱动器型号,那么该型号的所有设备都会共享相同的 vendorId 和 productId。*/public boolean isCscidDevice(UsbDevice device) {if (device == null) {return false;}if (device.getInterfaceCount() == 0) {return false;}for (int i = 0; i < device.getInterfaceCount(); i++) {android.hardware.usb.UsbInterface usbInterface = device.getInterface(i);/*** 设备类 ID	设备类名称	描述* 0x01	Audio	音频设备,如 USB 音频接口。* 0x02	Communications	通信设备,如调制解调器和网络适配器。* 0x03	HID (Human Interface Device)	人机接口设备,通常指键盘、鼠标等。* 0x05	Physical Interface Device	物理接口设备,通常用于各种控制设备。* 0x06	Image	图像设备,如 USB 摄像头和扫描仪。* 0x07	Printer	打印机设备。* 0x08	Mass Storage	大容量存储设备,如 USB 闪存驱动器和外部硬盘。* 0x09	Hub	USB 集线器。* 0x0A	CDC (Communications Device Class)	通信设备类,常用于调制解调器和串行口。* 0x0B	Smart ICCard	智能卡读卡器。* 0x0D	Content Security Device	内容保护设备,用于安全性应用。* 0x0E	Video	视频设备,如视频采集卡。* 0xEF	Miscellaneous	其他不属于上述类别的设备。* 0xFF	Vendor Specific	厂商自定义设备,通常特定于某个硬件厂商的设备。* @param deviceClass* @param deviceSubclass* @param deviceProtocol* @return*/if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_HID) {int productId = device.getProductId();int vendorId = device.getVendorId();if (productId == 10203 && vendorId == 5824) {return true;}}}return false;}/*** 连接设备*/public void connectDevice() {try {//权限请求/** 在使用USB读写器设备前,应用必须获得权限。* 为了确切地获得权限,首先需要创建一个广播接收器。在调用requestPermission()这个方法时从得到的广播中监听这个意图。* 通过调用requestPermission()这个方法为用户跳出一个是否连接该设备的对话框。*/if (mUsbBroadcast == null) {mUsbBroadcast = new UsbBroadcast();IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);App.getContext().registerReceiver(mUsbBroadcast, filter);}//获取全部设备if (currentDevice == null) {List<UsbDevice> usbDeviceList = getUsbPrinters();if (usbDeviceList.size() > 0) {currentDevice = usbDeviceList.get(0);}}if (currentDevice != null) {// 检查设备是否具有所需的权限if (usbManager.hasPermission(currentDevice)) {// 打开设备并进行通信if (connection == null) {connection = usbManager.openDevice(currentDevice);}if (connection != null) {// 获取接口并配置if (usbInterface == null) {usbInterface = currentDevice.getInterface(0);}connection.claimInterface(usbInterface, true);// 你可以在这里与设备进行通信//readDataFromDevice();Log.d(TAG, "连接到设备: " + currentDevice.getDeviceName());} else {Log.e(TAG, "无法打开设备: " + currentDevice.getDeviceName());}} else {// 如果没有则请求权限PendingIntent mPermissionIntent = PendingIntent.getBroadcast(App.getContext(), 0,new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE);usbManager.requestPermission(currentDevice, mPermissionIntent);}}} catch (Exception r) {r.printStackTrace();}}/*** 发送数据** @param message*/UsbEndpoint endpointOut = null;private void sendDataToDevice(String message) {// 获取输出端点if (endpointOut == null) {for (int i = 0; i < usbInterface.getEndpointCount(); i++) {UsbEndpoint endpoint = usbInterface.getEndpoint(i);if (endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK &&endpoint.getDirection() == UsbConstants.USB_DIR_OUT) {endpointOut = endpoint; // 获取输出端点break;}}}if (endpointOut == null || connection == null) {Log.e(TAG, "输出端点或连接未初始化");return;}byte[] buffer = message.getBytes(); // 将要发送的字符串转换为字节数组int bytesSent = connection.bulkTransfer(endpointOut, buffer, buffer.length, 1000); // 1秒超时if (bytesSent < 0) {Log.e(TAG, "发送数据失败");} else {Log.d(TAG, "成功发送数据: " + message);}}/*** 读取数据*/UsbEndpoint endpointIn = null;private static final int BUFFER_SIZE = 1024; // 定义读取的缓冲区大小private void readDataFromDevice() {if (endpointIn == null) {// 获取输入端点for (int i = 0; i < usbInterface.getEndpointCount(); i++) {UsbEndpoint endpoint = usbInterface.getEndpoint(i);Log.e(TAG, "readDataFromDevice: " + endpoint.getType());Log.e(TAG, "getDirection: " + endpoint.getDirection());if (endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_INT &&endpoint.getDirection() == UsbConstants.USB_DIR_IN) {endpointIn = endpoint; // 获取输入端点break;}}}if (endpointIn == null || connection == null) {Log.e(TAG, "输入端点或连接未初始化");return;}byte[] buffer = new byte[BUFFER_SIZE]; // 创建缓冲区// 循环读取数据Thread readThread = new Thread(new Runnable() {@Overridepublic void run() {while (true) {int bytesRead = connection.bulkTransfer(endpointIn, buffer, buffer.length, 1000); // 1秒超时if (bytesRead > 0) {// 处理读取到的数据String data = new String(buffer, 0, bytesRead); // 将字节数组转换为字符串Log.d(TAG, "读取数据: " + data);} else if (bytesRead == 0) {Log.d(TAG, "没有更多数据可读");break;} else {Log.e(TAG, "读取数据失败: " + bytesRead);break;}}}});readThread.start(); // 启动读取线程}/*** 断开设备*/public void disconnectDevice() {try {if (usbManager != null && currentDevice != null && usbInterface != null) {// 获取之前连接的接口// 释放接口connection.releaseInterface(usbInterface);// 关闭连接connection.close();// 清空连接connection = null;usbInterface = null;//读取置空endpointIn = null;//写入置空endpointOut = null;//设备置空currentDevice = null;if (mUsbBroadcast != null) {App.getContext().unregisterReceiver(mUsbBroadcast);}Log.d(TAG, "设备已断开: ");}} catch (Exception e) {e.printStackTrace();}}
}

五 注意事项

5.1 对于同一型号的USB产品,其 vendorId 和 productId 通常是一致的,因为它们来自同一个制造商且属于同一产品系列。例如,如果一个制造商生产了一个特定的USB闪存驱动器型号,那么该型号的所有设备都会共享相同的 vendorId 和 productId。 

5.2 扫码枪usb插拔时,会造成Activity重新加载,说明对Activity生命周期有影响,需要处理一下。
可在Activity配置

android:configChanges="orientation|keyboard|keyboardHidden"

六 使用场景举例-扫码枪设备

6.1 Android端获取扫码枪数据一般有如下三种获取方式

  • USB键盘模式,自感模式(出厂默认):大多数扫码枪是模拟键盘进行输入(不支持包含有汉字的二维码),大多数时候需要使用带焦点的 EditText 接收其扫描的信息。设置一个带焦点的EditText进行接收,此方式容易漏读扩展的ASCII码数据,除非二维码是标准的ASCII码(1-128)。由于扫码枪会将扫描出来的内容转化为键盘事件,对应的就是Android中的KeyEvent事件,所以我们只需要在我们的activity中重写dispatchKeyEvent方法,即可获取相应事件。
  • 通过 USB COM连接扫码:发送扫码开关指令进行扫码,即基于Usb相关类控制扫码开关
  • USB串口自感模式:即串口连接(当扫码枪使用 USB 通讯接口,但主机应用程序是采用串口通讯方式接收数据),客户端只负责监听读取数据,无须控制扫码枪指令,需要依赖usb串口传输数据的第三方库:https://github.com/mik3y/usb-serial-for-android

6.2 USB键盘模式使用

/*** usb键盘模式扫码示例,无需EditText接收*/
public class ScannerUsbKeyboardActivity extends AppCompatActivity {UsbKeyboardAutoScan usbKeyboardAutoScan;EditText et_barcode;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.scanner_usb_keyboard_activity);et_barcode = findViewById(R.id.et_barcode);usbKeyboardAutoScan = new UsbKeyboardAutoScan();usbKeyboardAutoScan.setOnScanListener(new OnScanListener() {@Overridepublic void onScanSuccess(String barcode) {//扫码接收et_barcode.setText(barcode);}});}@Overridepublic boolean dispatchKeyEvent(KeyEvent event) {if (usbKeyboardAutoScan.isIntercept()&& event.getKeyCode() != KeyEvent.KEYCODE_BACK) {//不处理返回键usbKeyboardAutoScan.analysisKeyEvent(event);return true;//防止输入框接收事件}return super.dispatchKeyEvent(event);}@Overrideprotected void onDestroy() {super.onDestroy();usbKeyboardAutoScan.cancel();}
}

 6.3 USB串口自感模式使用

/*** 基于usb扫码*/
public class ScannerUsbActivity extends AppCompatActivity {BaseUsbScan usbScan;EditText et_barcode;UsbConfig usbConfig = new UsbConfig();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.scanner_usb_activity);et_barcode = findViewById(R.id.et_barcode);usbConfig.setProductId(1233);usbConfig.setVendorId(1234);
//        usbScan = new UsbCmdScan(this); //通过usb连接扫码, 发送命令扫码UsbConfig.SerialPortConfig serialPortConfig = new UsbConfig.SerialPortConfig();usbConfig.setSerialPortConfig(serialPortConfig);usbScan = new UsbComAutoScan(this);//通过usb转串口,自感模式,推荐usbScan.setOnScanListener(new OnScanListener() {@Overridepublic void onScanSuccess(String barcode) {}});}public void listUsbDevice(View view) {//找到设备插入的usb孔对应UsbDeviceList<UsbDevice> usbDevices = usbScan.getUsbDevices(this);for (UsbDevice device : usbDevices) {et_barcode.setText(et_barcode.getText() + "\n" + device.getDeviceName() + ",vendorID:" + device.getVendorId() + ",ProductId:" + device.getProductId());}}public void openUsbScan(View view) {int ret = usbScan.openScan(usbConfig);if (ret != 0) {showMsg("打开usb扫码失败,ret" + ret);} else {showMsg("打开usb扫码成功");}}private void showMsg(String s) {et_barcode.setText(s);}public void closeUsbScan(View view) {usbScan.closeScan();}public void loopScan(View view) {usbScan.startReading();}public void stopLoopScan(View view) {usbScan.stopReading();}@Overrideprotected void onDestroy() {super.onDestroy();usbScan.closeScan();}
}

http://www.ppmy.cn/devtools/150693.html

相关文章

「蓝桥杯题解」数字接龙

前言 这个是我的 ac 代码&#xff0c;里面的注释是用自己的话写的。因为我看蓝桥杯官方题解文字和代码分离&#xff0c;代码部分没有注释&#xff0c;看着巨难受&#xff0c;所以自己写了一版。感觉他们的视频解析也挺水的&#xff08;小声 题目链接 代码 import java.util…

openmetadata二次开发之前端汉化

目录 1.背景 1.1 设置默认的语言为中文,应该如何修改代码 1.2 将未做国际化的页面做汉化,应该如何修改代码 1.3 配置连接信息的指导教程,应该如何汉化 2. 设置默认的语言为中文 2.1 查看源码当前项目为react构建的项目,使用的国际化插件为react-i18next和i18next 2.…

数据结构------树

前言&#xff1a;前面我们学习了栈和队列。今天我们来学习一种新的数据结构---------树。 首先我们来了解一下树的概念。 1.树的概念与结构 前面我们学习过的顺序表&#xff0c;栈都是一种顺序结构。链表&#xff0c;队列是链式结构。今天学习的树也是一种链式结构。它是由n…

infinitetensor训练营-cuda1

一、基础概念剖析&#xff1a; 1、延迟&#xff1a;发送内存请求——>实际完成数据移动所需的时间。可以理解为家到公司的距离 2、带宽&#xff1a;单位时间移动数据量&#xff0c;即数据传输的速度。可以理解为家到公司中间马路的宽度 我们这里举个例子&#xff0c;计算机…

idea无法下载源码

1. 方式一 在项目下&#xff0c;项目根目录下 或 pom.xml同级目录中执行 mvn dependency:resolve -Dclassifiersources然后点击“download source”时就能看到源码了。

基于深度学习的视觉检测小项目(十二) 使用线条边框和渐变颜色美化界面

到目前为止&#xff0c;已经建立起了基本的项目架构&#xff0c;样式表体系也初步具备&#xff0c;但是与成品的界面相比&#xff0c;还是差点什么。 我的界面效果图&#xff1a; 优秀demo的界面截图&#xff1a; 是的&#xff0c;我的界面太“平” 了&#xff0c;没有立体感&…

【专题】2025年节日营销趋势洞察报告汇总PDF洞察(附原数据表)

原文链接&#xff1a; https://tecdat.cn/?p38813 在当今复杂多变且竞争激烈的消费市场环境下&#xff0c;节日营销已成为企业获取市场份额、提升品牌影响力的关键战略时机。我们深知深入洞察节日营销趋势对于企业决策的重要性。 本报告汇总基于对 2024 年多个关键消费节点及…

【论文阅读】基于空间相关性与Stacking集成学习的风电功率预测方法

文章目录 摘要0. 引言1. 空间相关性分析2. 风电功率预测模型2.1 Stacking 集成策略2.2 基学习器2.2.1 基于机器学习算法的基学习器2.2.2 基于神经网络的基学习器2.2.3 基于粒子群优化算法的超参数优化 2.3 元学习器2.4 基于空间相关性与Stacking集成学习的风电功率预测方法 3 算…