-
2019-05-01 18:06:32
一、概述
蓝牙是一种无线技术标准,可实现固定设备、移动设备和楼宇个人域网之间的短距离数据交换。最多可以同时和7个其它蓝牙设备建立连接,进行通信。蓝牙可分为两大类:传统蓝牙(蓝牙3.0规范之前),低功耗蓝牙(蓝牙4.0规范之后)。
Android 从4.3版本(API Level 18)开始支持低功耗蓝牙Bluetooth Low Energy(BLE)通信。Android提供了相应的 API, 应用程序通过这些 API 可以实现 蓝牙设备扫描、配对、连接、传输数据等功能。
二、Android BLE API几个重要类
1、BluetoothAdapter
本地的蓝牙适配器。是所有蓝牙交互操作的入口点。通过这个类可以发现其他蓝牙设备,查询已配对的设备列表,使用一个已知的MAC地址来实例化一个BluetoothDevice,以及创建一个BluetoothServerSocket来为监听与其他设备的通信。
2、BluetoothDevice
远程蓝牙设备。使用这个类来请求一个与远程设备的BluetoothSocket连接,或者查询关于设备名称、地址、类和连接状态等设备信息。
3、BluetoothSocket
代表一个蓝牙socket的接口(和TCP Socket类似)。这是一个连接点,它允许一个应用与其他蓝牙设备通过InputStream和OutputStream交换数据。
4、BluetoothServerSocket
代表一个开放的服务器socket,它监听接受的请求(与TCP ServerSocket类似)。为了连接两台Android设备,一个设备必须使用这个类开启一个服务器socket。当一个远程蓝牙设备开始一个和该设备的连接请求,BluetoothServerSocket将会返回一个已连接的BluetoothSocket,接受该连接。
三、蓝牙开发
1、流程
2、开启权限
<uses-permission android:name="android.permission.BLUETOOTH"/> //开启蓝牙时,mBluetoothAdapter.enable()需要以下权限 <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
3、开启蓝牙
public void isBluetoothEnable() { //获取蓝牙适配器 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (mBluetoothAdapter != null){ // 蓝牙已打开 if (mBluetoothAdapter.isEnabled()){ }else{//未打开则开启,此处可以通过弹框提示来提示用户开启 mBluetoothAdapter.enable() } } }
4、搜索附近蓝牙设备
/** * 注册搜索蓝牙设备的广播 */ private void startDiscovery() { IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); registerReceiver(receiver, filter); IntentFilter filter1 = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); registerReceiver(receiver, filter1); startScanBluetooth(); } private void startScanBluetooth() { // 判断是否在搜索,如果在搜索,就取消搜索 if (bluetoothAdapter.isDiscovering()) { bluetoothAdapter.cancelDiscovery(); } // 开始搜索 bluetoothAdapter.startDiscovery(); } /** * 蓝牙广播接收 */ private final BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (BluetoothDevice.ACTION_FOUND.equals(action)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); //蓝牙rssi参数,代表蓝牙强度 short rssi = intent.getExtras().getShort(BluetoothDevice.EXTRA_RSSI); //蓝牙设备名称 String name = device.getName(); //蓝牙设备连接状态 int status = device.getBondState(); ... } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) { ... } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { Toast.makeText(context, "蓝牙设备搜索完成", Toast.LENGTH_SHORT).show(); } } };
关于蓝牙连接状态:
BluetoothDevice.BOND_BONDED:已配对
BluetoothDevice.BOND_BONDING:配对中
BluetoothDevice.BOND_NONE:未配对或取消配对
关于蓝牙强度rssi:
单位是dbm,蓝牙信号的强度RSSI = 10*log P,P代表接收到的信号功率。蓝牙会发送广播,距离大小会影响信号功率强弱。假设发射功率取最大值为1mw,那么RSSI的值为0,也就是说你的距离离蓝牙最近时在理想状态下所获取的RSSI的值为0,但在实际中基本不会存在这个理想状态,因此RSSI的值基本都为负数。
一般说来,在BLE中,假设信号强度按强、中、弱、差4个等级划分,rssi范围依次是:-60 ~ 0 、-70 ~ -60、-80 ~ -70、<-80。
5、配对
//获取已配对设备信息 public List<BluetoothDevice> getPairedBluetoothDevices() { List deviceList = new ArrayList<>(); Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices(); if (pairedDevices.size() > 0) { for (BluetoothDevice device : pairedDevices) { deviceList.add(device); } } return deviceList; }
//若已配对设备数为0,跳转到手机系统蓝牙设置界面 Intent enableBtIntent = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS); mContext.startActivity(enableBtIntent);
//手动配对,完成配对后重新扫描即可 Method method = BluetoothDevice.class.getMethod("createBond"); method.invoke(itemlist.get(position).getDevice());
6、连接
蓝牙连接需要在子线程中完成
public class BluetoothConnectThread extends Thread { private static final UUID BluetoothUUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); BluetoothSocket bluetoothSocket; BluetoothDevice bluetoothDevice; private boolean connected = false; private Object lock = new Object(); //蓝牙连接回调接口 private BluetoothConnectCallback connectCallback; public BluetoothConnectThread(BluetoothDevice device, BluetoothConnectCallback callback) { try { bluetoothDevice = device; bluetoothSocket = bluetoothDevice.createInsecureRfcommSocketToServiceRecord(BluetoothUUID); connectCallback = callback; } catch (Exception e) { e.printStackTrace(); } } @Override public void run() { if (bluetoothSocket != null) { if (connected) { cancel2(); connected = false; } } new Thread() { @Override public void run() { connect(); if (connected) { if (connectCallback != null){ connectCallback.connectSuccess(bluetoothSocket); } } } }.start(); } public void connect() { try { synchronized (lock) { bluetoothSocket.connect(); connected = true; } } catch (Exception connectException) { connectException.printStackTrace(); cancel(); try { Method m; m = bluetoothDevice.getClass().getMethod("createRfcommSocket", new Class[]{int.class}); bluetoothSocket = (BluetoothSocket) m.invoke(bluetoothDevice, Integer.valueOf(1)); bluetoothSocket.connect(); connected = true; } catch (Exception ex) { ex.printStackTrace(); if (connectCallback != null){ connectCallback.connectFailed(ex.getMessage()); } } } } public void cancel() { try { synchronized (lock) { if (connected) { bluetoothSocket.close(); connected = false; } } } catch (IOException e) { } } public void cancel2() { try { synchronized (lock) { bluetoothSocket.close(); connected = false; } } catch (IOException e) { } } }
public interface BluetoothConnectCallback { void connectSuccess(BluetoothSocket socket); void connectFailed(String errorMsg); void connectCancel(); }
注意以上,BluetoothUUID一般为固定的,connect()放在子线程中可以提高连接成功率(不明所以),注意connect失败需要通过反射createRfcommSocket该方法完成,自验目前连接成功率较高。
7、传输数据
//获取BluetoothSocket输出流 OutputStream outputStream = bluesocket.getOutputStream(); //之后将数据写入输出流完成传输 outputStream.write(data); outputStream.flush();
更多相关内容 -
Android通用蓝牙连接协议
2018-06-15 15:53:07Android扫描蓝牙设备,连接蓝牙设备,读取设备数据,向设备写入数据的方法类。 -
不一样的蓝牙连接方式——C#程序实现蓝牙通信
2021-01-09 23:21:00和我们平时正常连接蓝牙设备一样,需要先搜索附近的蓝牙设备,然后根据设备名来选择要连接的蓝牙模块,连接时就根据该蓝牙模块的地址(惟一标识号)来进行连接。 发送数据给蓝牙模块 发送的过程就和平时读写文件很...之前做项目的时候,需要使用电脑程序通过蓝牙向硬件设备发送指令,于是便研究了一下怎么用C#程序和蓝牙进行通信。
1、思路
- 电脑蓝牙和蓝牙模块配对连接
和我们平时正常连接蓝牙设备一样,需要先搜索附近的蓝牙设备,然后根据设备名来选择要连接的蓝牙模块,连接时就根据该蓝牙模块的地址(惟一标识号)来进行连接。 - 发送数据给蓝牙模块
发送的过程就和平时读写文件很类似,只是IO流不一样的区别
2、实现
2.1 使用的库
C#进行蓝牙操作需要用到的库是 InTheHand.Net。在VS中可以直接在Nuget中安装,这是我觉得VS最好用的一个找各种库的最好的方法。
工具菜单->NuGet包管理器->管理解决方案的Nuget程序包
然后直接搜索 InTheHand.Net,选择对应的库文件之后点击安装即可使用
2.2 搜索附近的蓝牙设备
BluetoothClient client = new BluetoothClient(); //处理蓝牙的对象 BluetoothRadio radio = BluetoothRadio.PrimaryRadio; //获取电脑蓝牙 radio.Mode = RadioMode.Connectable; //设置电脑蓝牙可被搜索到 BluetoothAddress blueAddress ; //需要连接的蓝牙模块的唯一标识符 BluetoothDeviceInfo[] devices = client.DiscoverDevices(); //搜索蓝牙设备,10秒 //从搜索到的所有蓝牙设备中选择需要的那个 foreach (var item in devices) { if(item.DeviceName.Equals("需要连接的蓝牙模块名字")) //根据蓝牙名字找 { Console.WriteLine(item.DeviceAddress); Console.WriteLine(item.DeviceName); blueAddress = item.DeviceAddress; //获得蓝牙模块的唯一标识符 break; } Console.WriteLine(item.DeviceAddress); Console.WriteLine(item.DeviceName); }
搜索蓝牙设备的目的是为了得到想要连接的那个蓝牙模块的唯一标识符,如果之前这个蓝牙设备已经和电脑连接过,那么电脑上就会有设备记录,可以直接找到唯一标识符从而就可以省去上面的这一步直接进行连接。
控制面板->设备和打印机-》右键想要连接的蓝牙设备->属性->蓝牙->唯一标识符
在代码中只需要创建一个蓝牙唯一标识符的对象即可,但是填写唯一标识符的时候需要倒着填并且用16进制表示BluetoothAddress blueAddress = new BluetoothAddress(new byte[] { 0x21, 0x18, 0x28, 0x03, 0x19, 0x20 }) ;
3、样例代码
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using InTheHand.Net; using InTheHand.Net.Bluetooth; using InTheHand.Net.Sockets; namespace BluetoothStudy { class Program { static void Main(string[] args) { BluetoothClient client = new BluetoothClient(); //处理蓝牙的对象 BluetoothRadio radio = BluetoothRadio.PrimaryRadio; //获取电脑蓝牙 radio.Mode = RadioMode.Connectable; //设置电脑蓝牙可被搜索到 BluetoothAddress blueAddress ; //需要连接的蓝牙模块的唯一标识符 BluetoothDeviceInfo[] devices = client.DiscoverDevices(); //搜索蓝牙设备,10秒 //从搜索到的所有蓝牙设备中选择需要的那个 foreach (var item in devices) { if(item.DeviceName.Equals("需要连接的蓝牙模块名字")) //根据蓝牙名字找 { Console.WriteLine(item.DeviceAddress); Console.WriteLine(item.DeviceName); blueAddress = item.DeviceAddress; //获得蓝牙模块的唯一标识符 break; } Console.WriteLine(item.DeviceAddress); Console.WriteLine(item.DeviceName); } BluetoothEndPoint ep = new BluetoothEndPoint(blueAddress, BluetoothService.SerialPort); Console.WriteLine("正在连接!"); client.Connect(ep); //开始配对 蓝牙4.0不需要setpin if(client.Connected) { Console.WriteLine("连接成功!"); Stream peerStream = client.GetStream(); //创建IO流对象 string str = "发送的内容"; peerStream.Write(str,0,str.Length); // 发送开门指令 Console.WriteLine("发送成功!"); } } } }
4、总结
- 处理类似蓝牙等和硬件相关的程序,一定是存在某个库专门来处理的,就拿InTheHand.Net这个库来举例子,我看了他们的官网,除了可以对蓝牙进行处理之外,还可以对NFC进行处理。所以在遇到一些不知道不同东西怎么结合的情况时,就多上网查有没有什么类似的库可以用
- 一般拿到一个库时,大多数情况只需要一些最基本的功能,最好的学习方法就是找一个经典的代码例子,看懂了基本就知道怎么用了。但如果需要深度使用的话,最好就是上官网,官网都会有开发文档介绍具体的用法
- 电脑蓝牙和蓝牙模块配对连接
-
蓝牙配对过程和蓝牙连接的建立过程
2021-01-19 15:07:57任何无线通信技术都存在被监听和破解的可能,蓝牙SIG为了保证蓝牙通信的安全性,采用的方式进行数据交互。同时为了保证使用的方便性,以配对的形式完成两个蓝牙设备之间的首次通讯,经配对之后,随后的通讯连接就... -
Android经典蓝牙连接
2022-02-15 11:25:52记录下连接经典蓝牙遇到的坑。 一些基本概念资料很多,这里直接上代码,里面都有注释和一些关键点。 整个类如下: import android.app.Activity; import android.bluetooth.BluetoothA2dp; import android....记录下连接经典蓝牙遇到的坑。
一些基本概念资料很多,这里直接上代码,里面都有注释和一些关键点。
整个类如下:
import android.app.Activity; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Handler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * * time:2022/2/15 */ public class BleControlTool { private static BleControlTool mInstance; private BluetoothAdapter mBluetoothAdapter; private BluetoothReceiver mReceiver; private BluetoothDevice mDeviceResult; //连接的设备 private BluetoothA2dp mBluetoothA2dp; //高级音频传输协议 private Activity mActivity; private BleControlTool() { } public static BleControlTool getInstance() { if (mInstance == null) { synchronized (BleControlTool.class) { if (mInstance == null) { mInstance = new BleControlTool(); } } } return mInstance; } public void startBlueToothConnect(Activity activity) { mActivity = activity; mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (!mBluetoothAdapter.isEnabled()) { mBluetoothAdapter.enable(); } IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(BluetoothDevice.ACTION_FOUND);//发现设备 intentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);//配对 intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//搜索结束 //注意,有些设备连接状态是这个广播,我就是栽在这里,一台设备是这里的回调,有些设备又是下面的广播回调,所以要做兼容... intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); //注意,有些设备连接状态又是这个广播, intentFilter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); mReceiver = new BluetoothReceiver(); mActivity.registerReceiver(mReceiver, intentFilter); mBluetoothAdapter.getProfileProxy(mActivity, new BluetoothProfile.ServiceListener() { @Override public void onServiceConnected(int profile, BluetoothProfile proxy) { MagicLog.e("-----onServiceConnected1"); if (profile == BluetoothProfile.A2DP) { //Service连接成功,获得BluetoothA2DP MagicLog.e("-----onServiceConnected2"); mBluetoothA2dp = (BluetoothA2dp) proxy; //获取到 mBluetoothA2dp 后才开始扫描,如果蓝牙没有预先打开,这里会先执行,mBluetoothAdapter.enable()调用之后,这做一个延时 new Handler().postDelayed(new Runnable() { @Override public void run() { //开始扫描 startDiscovery(); } }, 5000); //这里可以做已经配对过的设备可以直接进行连接,本人没有做具体处理测试,可自行做。 /*Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices(); for (BluetoothDevice device : bondedDevices) { MagicLog.d("配对过的 name:" + device.getName() + " mac:" + device.getAddress()); if (device.getName().contains("Self")) { mDeviceResult = device; connectClassic(device); } }*/ } /*List<BluetoothDevice> mDevices = proxy.getConnectedDevices(); if (mDevices != null) { for (int i = 0; i < mDevices.size(); i++) { System.out.println("---连接上的设备:" + mDevices.get(i).getName() + "---" + mDevices.get(i).getAddress()); } }*/ } @Override public void onServiceDisconnected(int profile) { MagicLog.e("-----onServiceDisconnected3"); } }, BluetoothProfile.A2DP); } //蓝牙广播接收数据 private class BluetoothReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (mActivity.isFinishing()) { MagicLog.d("-----------return"); return; } if (BluetoothDevice.ACTION_FOUND.equals(intent.getAction())) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (device == null) { return; } MagicLog.d("扫描到可连接的蓝牙设备 name:" + device.getName() + " mac:" + device.getAddress()); if (!TigerUtil.isEmpty(device.getName())) { if (device.getName().contains("Selfie")) { mDeviceResult = device; createBond(); } } } else if (BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) { MagicLog.d("-=-==============BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED"); int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, -1); MagicLog.d("=======state:" + state); switch (state) { case BluetoothAdapter.STATE_CONNECTING: MagicLog.d("=======STATE_CONNECTING"); break; case BluetoothAdapter.STATE_CONNECTED: MagicLog.d("=======STATE_CONNECTED,连接成功就销毁,这里销毁也是关键"); destroyBT(); break; case BluetoothAdapter.STATE_DISCONNECTED: MagicLog.d("=======STATE_DISCONNECTED"); break; } } else if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) { MagicLog.d("=-------BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED"); BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); switch (intent.getIntExtra(BluetoothA2dp.EXTRA_STATE, -1)) { case BluetoothA2dp.STATE_CONNECTING: BluetoothDevice deviceC = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); MagicLog.d("=====连接中: " + deviceC.getName() + " connecting"); break; case BluetoothA2dp.STATE_CONNECTED: MagicLog.d("=====连接成功 device: " + device.getAddress() + " connected"); destroyBT(); break; case BluetoothA2dp.STATE_DISCONNECTING: MagicLog.d("=====连接断开 device: "); break; case BluetoothA2dp.STATE_DISCONNECTED: MagicLog.d("=====连接断开2 device: "); //进行重连 break; default: break; } } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE); BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (device == null) { return; } switch (bondState) { case BluetoothDevice.BOND_BONDED: //配对成功 MagicLog.d("=====配对ok Device:" + device.getAddress() + " bonded."); connectClassic(); //连接蓝牙设备 break; case BluetoothDevice.BOND_BONDING: MagicLog.d("=====配对中 Device:" + device.getAddress() + " bonding."); break; case BluetoothDevice.BOND_NONE: MagicLog.d("=====配对失败 Device:" + device.getAddress() + " not bonded."); //不知道是蓝牙耳机的关系还是什么原因,经常配对不成功 //配对不成功的话,重新尝试配对 createBond(); break; default: break; } } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(intent.getAction())) { System.out.println("------扫描结束"); } } } private void createBond() { if (mDeviceResult != null) { MagicLog.d("-----createBond"); mDeviceResult.createBond(); } } private void startDiscovery() { if (mBluetoothAdapter != null) { mBluetoothAdapter.startDiscovery(); } } private void cancelDiscovery() { if (mBluetoothAdapter != null) { mBluetoothAdapter.cancelDiscovery(); } } private void connectClassic() { try { if (mBluetoothA2dp == null) { return; } //这里用反射连接 Method method = mBluetoothA2dp.getClass().getMethod("connect", BluetoothDevice.class); //method.setAccessible(true); method.invoke(mBluetoothA2dp, mDeviceResult); MagicLog.d("直接开始连接经典blue----------connectClassic-"); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } } public void destroyBT() { if (mBluetoothAdapter != null && mBluetoothA2dp != null) { mBluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP, mBluetoothA2dp); mBluetoothA2dp = null; mBluetoothAdapter = null; } if (mActivity != null) { mActivity.unregisterReceiver(mReceiver); mActivity = null; } } }
正常一般上面的步骤就差不多了,但是有些蓝牙设备是奇奇怪怪的,比如我的:
看到红圈没有,他是个键盘图标,连接成功后,发现Activity重新创建了,那就要在清单文件加上,这也是个坑:
android:configChanges="keyboard|keyboardHidden|navigation" android:screenOrientation="portrait"
-
微信公众号蓝牙连接php实现(原生)
2018-12-20 10:46:32微信公众号蓝牙连接php实现(原生) . /*此代码仅做参考 , 具体还需多加尝试,当时历时一周才做出来,有些内容需要和硬件开发者沟通 , 比如连接规则.*/ -
RFID技术中的蓝牙配对过程和蓝牙连接的建立过程
2020-10-16 00:58:18任何无线通信技术都存在被监听和破解的可能,蓝牙SIG为了保证蓝牙通信的安全性,采用认证的方式进行数据交互。同时为了保证使用的方便性,以配对的形式完成两个蓝牙设备之间的首次通讯认证,经配对之后,随后的... -
熊晨沣蓝牙实战---小程序蓝牙连接的开发1.0
2021-03-29 21:24:441、本版本区分了ANDROID和IOS系统下蓝牙连接的不同方式。 2、兼容了更多情况下的链接包括: (1)未开启设备蓝牙,当监听到开启了蓝牙后自动开始连接。 (2)初始化蓝牙失败后每3000ms自动重新初始化蓝牙适配器... -
熊晨沣蓝牙实战--小程序蓝牙连接2.0
2021-03-29 21:18:121、本版本区分了ANDROID和IOS系统下蓝牙连接的不同方式。 2、兼容了更多情况下的链接包括: (1)未开启设备蓝牙,当监听到开启了蓝牙后自动开始连接。 (2)初始化蓝牙失败后每3000ms自动重新初始化蓝牙适配器... -
Android 蓝牙/wifi云打印 ESC/POS热敏打印机打印(连接篇)
2020-09-22 10:17:30打印机的蓝牙连接方式是基于传统的蓝牙连接方式,手机作为客户端,打印机作为服务端。 我们先上效果图: 1.蓝牙权限 <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-...本篇我们将讲解蓝牙打印机和wifi云打印机的连接与数据发送,下一篇讲解ESC/POS命令集
一、蓝牙打印机连接
打印机的蓝牙连接方式是基于传统的蓝牙连接方式,手机作为客户端,打印机作为服务端。
我们先上效果图:
1.蓝牙权限
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <!-- If your app targets Android 9 or lower, you can declare ACCESS_COARSE_LOCATION instead. --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_ADMIN) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_PERMISSION); }
BLUETOOTH权限允许用户请求连接,接受连接和传输数据等,BLUETOOTH_ADMIN权限允许应用启动设备发现或操纵蓝牙设置。如果应用的目标版本是Android 9或者更低的版本,ACCESS_COARSE_LOCATION权限允许蓝牙扫描收集用户的位置信息,返回的是一个模糊的位置信息,此信息可能来自用户自己的设备,以及在商店和交通设施等位置使用蓝牙信标。Android 10开始,要使用蓝牙扫描位置信息需要申请ACCESS_FINE_LOCATION权限,返回的是精确的位置信息,除此之外,还需要开启GPS功能才行,不然不能搜索和连接其他蓝牙设备。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { LocationManager lm = (LocationManager) getSystemService(LOCATION_SERVICE); if (!lm.isProviderEnabled(LocationManager.GPS_PROVIDER)) { showToast("请您先开启gps,否则蓝牙不可用"); return; } }
2.初始化配置
初始化设备本身的蓝牙适配器BluetoothAdapter,有两种方式:
//方式一: BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); //方式二: BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE); BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
整个系统只有一个蓝牙适配器,全局只有一个实例,如果返回null,则代表设备不支持蓝牙,如果设备支持蓝牙,再接着检查蓝牙是否打开:
if (bluetoothAdapter == null || !getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { showToast("当前设备不支持蓝牙"); finish(); return; } else { if (!bluetoothAdapter.isEnabled()) { //请求开启蓝牙 Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableIntent, REQUEST_ENABLE_BLE); } else { setPairingDevice(); handler.postDelayed(new Runnable() { @Override public void run() { scanDevice(); } }, 1000); } }
成功打开蓝牙后就会回调到onActivityResult()中。除了这种主动的打开蓝牙,还可以监听BluetoothAdapter.ACTION_STATE_CHANGED广播,每当蓝牙状态发生变化时,此广播包含的值BluetoothAdapter.EXTRA_STATE,它包含新的蓝牙状态,可能的值:BluetoothAdapter.STATE_OFF和BluetoothAdapter.STATE_ON。
3.发现设备
设备发现是一个扫描过程,它会搜索局部区域内已开启蓝牙功能的设备,并请求与每台设备相关的某些信息。如果设备已开启可检测行,它会通过共享一些信息(例如设备名称、类及其唯一的MAC地址)来响应发现请求。扫描是一个耗时的过程,我们需要在异步执行,并且监听发现设备的广播。
Set<BluetoothDevice> devices = bluetoothAdapter.getBondedDevices();//获取已配对的设备 handler.postDelayed(new Runnable() { @Override public void run() { if (bluetoothAdapter.isDiscovering()) { bluetoothAdapter.cancelDiscovery(); } bluetoothAdapter.startDiscovery(); } }, 1000);
private BroadcastReceiver discoveryReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (TextUtils.isEmpty(action) || bluetoothDevice == null) { return; } switch (action) { case BluetoothAdapter.ACTION_DISCOVERY_STARTED: Log.e("TAG", "正在搜索附近的蓝牙设备"); break; case BluetoothAdapter.ACTION_DISCOVERY_FINISHED: Log.e("TAG", "搜索结束"); break; case BluetoothDevice.ACTION_ACL_CONNECTED: Log.e("TAG", "与" + bluetoothDevice.getName() + "蓝牙已连接"); break; case BluetoothDevice.ACTION_ACL_DISCONNECTED: Log.e("TAG", "与" + bluetoothDevice.getName() + "蓝牙连接已结束"); break; case BluetoothDevice.ACTION_FOUND: Log.e("TAG", "发现了新设备"); if (bluetoothDevice.getBondState() != BluetoothDevice.BOND_BONDED) { //Add } break; case BluetoothAdapter.ACTION_STATE_CHANGED: int blueState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); switch (blueState) { case BluetoothAdapter.STATE_OFF: showToast("蓝牙已关闭"); finish(); break; case BluetoothAdapter.STATE_ON: showToast("蓝牙已开启"); Set<BluetoothDevice> devices = bluetoothAdapter.getBondedDevices();//获取已配对的设备 handler.postDelayed(new Runnable() { @Override public void run() { if (bluetoothAdapter.isDiscovering()) { bluetoothAdapter.cancelDiscovery(); } bluetoothAdapter.startDiscovery(); } }, 1000); break; } break; } } };
注意:startDiscovery()只能扫描到那些状态被设为可发现的设备。安卓设备默认不可发现,要改变设备为可发现的状态,需要如下请求:
//无功能状态,查询扫描和页面扫描都无效,该状态下蓝牙模块既不能扫描其他设备,也不可见 //请求开启可见 Intent discoveryIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); discoveryIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300); startActivityForResult(discoveryIntent, REQUEST_DISCOVERABLE_BLE);
注意:startDiscovery()是一个特别耗费资源的操作,所以为了避免资源浪费,需要及时的调用cancelDiscovery()来释放资源。比如在进行设备连接之前,一定要先调用cancelDiscovery()。
4.连接设备
蓝牙设备的连接和网络连接的模型十分相似,都是Client-Server模式,都通过一个socket来进行数据传输。作为一个Android设备,存在以下三种情况:
1.只作为Client端发起连接 2.只作为Server端等待别人发起建立连接的请求 3.同时作为Client和Server
因为我们这篇文章主要为介绍连接热敏打印机的做铺垫,所以这里我们只讲Android设备作为Client建立连接的情况。因为打印机也不可能主动跟Android设备建立连接,所以打印机必然是作为Server端被连接。
4.1 作为Client连接
- 首先需要获取一个BluetoothDevice对象。获取方式如前文介绍的,通过调用startDiscovery()并监听广播获得,也可以通过查询已配对的设备获得。
- 通过BluetoothDevice.createInsecureRfcommSocketToServiceRecord(UUID)得到BluetoothSocket对象。
- 通过BluetoothSocket.connect()建立连接
- 异常处理以及连接关闭
private class ConnectThread extends Thread { private final BluetoothSocket mmSocket; private final BluetoothDevice mmDevice; public ConnectThread(BluetoothDevice device) { BluetoothSocket tmp = null; mmDevice = device; try { // 通过 BluetoothDevice 获得 BluetoothSocket 对象 tmp = device.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")); } catch (IOException e) { } mmSocket = tmp; } @Override public void run() { // 建立连接前记得取消设备发现 mBluetoothAdapter.cancelDiscovery(); try { // 耗时操作,所以必须在主线程之外进行 mmSocket.connect(); } catch (IOException connectException) { //处理连接建立失败的异常 try { mmSocket.close(); } catch (IOException closeException) { } return; } doSomething(mmSocket); } //关闭一个正在进行的连接 public void cancel() { try { mmSocket.close(); } catch (IOException e) { } } }
Client发起连接时传入的UUID必须要和Server端设置的一样,都在就会报错。
如是像我们连接热敏打印机的这种情况,因为一些常见的蓝牙服务协议已有约定的UUID,比如我们连接热敏打印机是基于SPP串口通信协议,其对应的UUID是"00001101-0000-1000-8000-00805F9B34FB"。
5.数据传输
经过前面4步的操作,两个蓝牙设备已连接,准备就绪,现在就是利用Socket获得InputStream输入流和OutputStream输出流来进行数据得收发。
由于我们是与热敏打印机连接,所以我们只需要给打印机发送我们需要打印得内容。
private class ConnectedThread extends Thread { private final BluetoothSocket mmSocket; private final InputStream mmInStream; private final OutputStream mmOutStream; public ConnectedThread(BluetoothSocket socket) { mmSocket = socket; InputStream tmpIn = null; OutputStream tmpOut = null; //通过 socket 得到 InputStream 和 OutputStream try { tmpIn = socket.getInputStream(); tmpOut = socket.getOutputStream(); } catch (IOException e) { } mmInStream = tmpIn; mmOutStream = tmpOut; } public void run() { byte[] buffer = new byte[1024]; // buffer store for the stream int bytes; // bytes returned from read() //不断的从 InputStream 取数据 while (true) { try { bytes = mmInStream.read(buffer); mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer).sendToTarget(); } catch (IOException e) { break; } } } //向 Server 写入数据 public void write(byte[] bytes) { try { mmOutStream.write(bytes); } catch (IOException e) { } } public void cancel() { try { mmSocket.close(); } catch (IOException e) { } } }
二、wifi云打印机连接
wifi打印机的连接方式跟蓝牙连接方式一样,都是Client-Server模式,通过一个socket来进行数据传输。由于手里还没有传统的wifi打印机,所以无法验证。但是公司有一台wifi云打印机,通过云服务器连接,调用WebAPI来连接发送数据实现打印。
我们先看一下效果图:
2.1 添加设备
先关注佳博科技的微信公众号,登录佳博云平台http://cloud.poscom.cn/,注册云平台账号,获取API集成所需的商户编号和API密钥。下面是网页端的云平台界面:
我们也可以直接在云平台的终端管理里面去添加终端设备,这里我们就调用API的方式在我们自己的APP中向云服务添加一台我们的打印终端。详细接口文档可以参考:http://cloud.poscom.cn/index.php?catid=18
@POST @FormUrlEncoded Observable<CommonResponse> addDevice(@Url String url, @Field("reqTime") String reqTime, @Field("securityCode") String securityCode, @Field("memberCode") String memberCode, @Field("deviceID") String deviceID, @Field("devName") String devName);
public Observable<CommonResponse> addDevice(String deviceID, String devName) { Retrofit retrofit = RetrofitUtils.getGsonRetrofit(); String url = "http://api.poscom.cn/apisc/adddev"; String memberCode = "商户编号"; String reqTime = String.valueOf(System.currentTimeMillis()); String apiKey = "API key"; String securityCode = Md5Utils.md5(memberCode + reqTime + apiKey + deviceID); return retrofit.create(IWifi.class).addDevice(url, reqTime, securityCode, memberCode, deviceID, devName); }
注意:securityCode安全校验码,是用API密钥和规定的参数进行MD5运算的结果,注意顺序不能乱。另外,如果云服务器检测到该设备ID已存在,将提示设备已存在,需要先删除再添加。
2.2 删除打印机
@POST @FormUrlEncoded Observable<CommonResponse> deleteDevice(@Url String url, @Field("reqTime") String reqTime, @Field("securityCode") String securityCode, @Field("memberCode") String memberCode, @Field("deviceID") String deviceID);
public Observable<CommonResponse> deleteDevice(String deviceID) { Retrofit retrofit = RetrofitUtils.getGsonRetrofit(); String url = "http://api.poscom.cn/apisc/deldev"; String memberCode = "商户编号"; String reqTime = String.valueOf(System.currentTimeMillis()); String apiKey = "API key"; String securityCode = Md5Utils.md5(memberCode + reqTime + apiKey + deviceID); return retrofit.create(IWifi.class).deleteDevice(url, reqTime, securityCode, memberCode, deviceID); }
注意:securityCode安全校验码的MD5运算顺序。
2.3 查询打印机列表
@POST @FormUrlEncoded Observable<ListDeviceResponse> getListDevices(@Url String url, @Field("reqTime") String reqTime, @Field("memberCode") String memberCode, @Field("securityCode") String securityCode);
public Observable<ListDeviceResponse> getListDevices() { Retrofit retrofit = RetrofitUtils.getGsonRetrofit(); String url = "http://api.poscom.cn/apisc/listDevice"; String memberCode = "商户编号"; String reqTime = String.valueOf(System.currentTimeMillis()); String apiKey = "API key"; String securityCode = Md5Utils.md5(memberCode + reqTime + apiKey); return retrofit.create(IWifi.class).getListDevices(url, reqTime, memberCode, securityCode); }
注意:securityCode安全校验码的MD5运算顺序。
2.4 发送数据到打印机
@POST @FormUrlEncoded Observable<CommonResponse> sendMsg(@Url String url, @Field("reqTime") String reqTime, @Field("securityCode") String securityCode, @Field("memberCode") String memberCode, @Field("deviceID") String deviceID, @Field("mode") String mode,//model 2-3,2自由格式打印,推荐,3十六进制命字符串打印 @Field("msgDetail") String msgDetail);
public Observable<CommonResponse> sendMsg(String deviceID) { Retrofit retrofit = RetrofitUtils.getGsonRetrofit(); String url = "http://api.poscom.cn/apisc/sendMsg"; String memberCode = "商户编号"; String reqTime = String.valueOf(System.currentTimeMillis()); String apiKey = "API key"; String securityCode = Md5Utils.md5(memberCode + deviceID + reqTime + apiKey); String mode = "2"; String msgDetail = "<gpLogo/><gpWord Align=1 Bold=1 Wsize=2 Hsize=2 Reverse=0 Underline=0>发货单</gpWord>\n" + "<gpBarCode Align=1 Type=7 Width=2 Height=80 Position=0>201811080001</gpBarCode>\n" + "<gpWord Align=0 Bold=0 Wsize=0 Hsize=0 Reverse=0 Underline=0>订单编号:201811080001</gpWord>\n" + "<gpWord Align=0 Bold=0 Wsize=0 Hsize=0 Reverse=0 Underline=0>买家姓名:张三</gpWord>\n" + "<gpWord Align=0 Bold=0 Wsize=0 Hsize=0 Reverse=0 Underline=0>买家手机:18666666666</gpWord>\n" + "<gpWord Align=0 Bold=1 Wsize=0 Hsize=1 Reverse=0 Underline=0>买家留言:发顺丰,尽快发货,谢谢</gpWord>\n" + "<gpWord Align=0 Bold=1 Wsize=0 Hsize=1 Reverse=0 Underline=0>卖家备注:发顺丰,优先处理</gpWord>\n" + "<gpWord Align=0 Bold=1 Wsize=0 Hsize=1 Reverse=0 Underline=0>买就送信息:送U盘</gpWord>\n" + "<gpWord Align=0 Bold=0 Wsize=0 Hsize=0 Reverse=0 Underline=0>--------------------------------</gpWord>\n" + "<gpTR4 Type=0><td>宝贝名称</td><td>单价</td><td>数量</td><td>价格</td></gpTR4>\n" + "<gpTR4 Type=0><td>佳博GP-CH421D云打印机</td><td>1180</td><td>1</td><td>1180</td></gpTR4>\n" + "<gpTR4 Type=0><td>佳博GP-5890XIII云打印机</td><td>480</td><td>1</td><td>480</td></gpTR4>\n" + "<gpTR4 Type=0><td>佳博G3-350V云打印机</td><td>980</td><td>1</td><td>980</td></gpTR4>\n" + "<gpTR4 Type=0><td>100x150热敏标签纸300</td><td>36</td><td>10</td><td>360</td></gpTR4>\n" + "<gpTR4 Type=0><td>58毫米热敏卷纸100米</td><td>48</td><td>5</td><td>240</td></gpTR4>\n" + "<gpTR4 Type=0><td>80毫米热敏卷纸100米</td><td>42</td><td>10</td><td>420</td></gpTR4>\n" + "<gpWord Align=0 Bold=0 Wsize=0 Hsize=0 Reverse=0 Underline=0>-------------------------------- </gpWord>\n" + "<gpWord Align=2 Bold=0 Wsize=0 Hsize=0 Reverse=0 Underline=0>合计:3660元</gpWord>\n" + "<gpWord Align=2 Bold=0 Wsize=0 Hsize=0 Reverse=0 Underline=0>优惠:-198元</gpWord>\n" + "<gpWord Align=2 Bold=0 Wsize=0 Hsize=0 Reverse=0 Underline=0>邮费: 30元</gpWord>\n" + "<gpWord Align=2 Bold=1 Wsize=1 Hsize=1 Reverse=0 Underline=0>实收:3492元 </gpWord>\n" + "<gpCut/>\n" + "<gpWord Align=1 Bold=1 Wsize=1 Hsize=1 Reverse=0 Underline=0>扫码关注佳博</gpWord>\n" + "<gpQRCode Align=1 Size=9 Error=M>http://weixin.qq.com/r/kHV3b67EXPMjreoM9yCC</gpQRCode>\n" + "<gpCut/>"; return retrofit.create(IWifi.class).sendMsg(url, reqTime, securityCode, memberCode, deviceID, mode, msgDetail); }
佳博票据云打印格式详情可以参考http://cloud.poscom.cn/index.php?id=152。
注意:securityCode安全校验码的MD5运算顺序不能乱。mode打印信息的类型,mode为2是自由格式打印,如上文格式,mode为3是十六进制命令集或十六进制字符串打印。
最后附上打印出来的效果图:
-
Android 蓝牙连接,蓝牙配对,自动连接蓝牙
2022-03-23 17:38:01免费下载!Android蓝牙配对,开启关闭蓝牙,搜索附近蓝牙设备,代码如何使用介绍 -
通过近场通信解决智能传感器与蓝牙连接问题
2021-01-19 16:03:06蓝牙低功耗(BLE)设计用于支持数据速率高达1 Mbit/s的通信,距离远约50米,比典型的蓝牙范围大约10 - 30米,蓝牙低功耗(BLE)具有强大的连接设备的凭据物联网(IoT)。 设备可能只实现蓝牙规范的BLE部分,因此... -
Android Phone蓝牙通信方式总结(Socket与Gatt)
2021-01-03 16:50:18Android手机间通过蓝牙方式进行通信,有两种常见的方式,一种是socket方式,另一种是通过Gatt Server(Android 5.0以后)通信,socket方式最为简单,但是很多低功耗的蓝牙设备,如单片机上的蓝牙模块可能不支持。... -
android 手机,连接蓝牙耳机连接不成功;两种方式都连接失败
2019-04-10 01:19:49NULL 博文链接:https://zht19880706.iteye.com/blog/1671002 -
Android蓝牙4.0BLE连接收发数据Demo
2018-04-13 10:08:010、此demo经多次测试,安全稳定,如有bug可联系QQ316585183,会及时更改。 1、此为Android Studio项目中的module,需要在...2、此工程所用的是蓝牙模块为汇承HC-08型号,如果使用的是别的蓝牙模块,需要更改UUID。 -
蓝牙自动配对连接通信
2020-09-24 17:44:11Android蓝牙自动配对demo,如果需要手动配对修改一下socket获取方式,代码中是不用配对就可连接通信 -
Bluetooth基础知识--蓝牙的几种通讯方式
2020-04-23 17:13:13在建立连接之前,BLE设备之间可以通过广播和扫描的方式进行通信,一旦建立连接,双方可以通过client/server模式通信(建立了链路层连接),因此对于BLE网络,有两种通信的方式: 1. 基于广播方式通信: 是一种无... -
实现蓝牙HC-05、06与单片机的连接及与手机通信
2020-07-14 05:20:57即51向HC-05写数据,HC-05从51读取数据,那么串口连接处51的写端P3.1引脚(TXD)就与HC-05读端(RXD)相连,反之蓝牙向51传递数据时,HC-05写端(TXD)T与51的读端P3.0引脚(RXD)相连,所以通常为以下连接方式即可实现数据... -
Android设备连接Honeywell蓝牙扫描枪实现广播方式输出.rar
2020-04-29 08:54:40Honeywell蓝牙扫描枪与Android设备连接实现焦点或广播方式自试应中文二维码输出。普通安卓设备+Honeywell蓝牙扫描枪就可以当做工业级PDA来使用。 -
Android连接TSC蓝牙打印机进行打印源码和文档
2020-04-15 11:22:35Android连接TSC蓝牙打印机进行打印,多种连接方式,问题整理,参考文档,实现代码 Instructions for Android TSC Bluetooth/Ethernet library functions 有关 Android TSC 蓝牙/以太网库功能的说明 1. open... -
蓝牙HC-05模块与电脑或手机的连接方式
2022-04-29 16:31:09蓝牙HC-05模块与电脑或手机的连接方式 -
如何以编程方式判断蓝牙设备是否已连接?
2021-06-09 17:22:55在你的仙女座宣言中加入蓝牙许可,然后使用意图筛选器侦听ACTION_ACL_CONNECTED,ACTION_ACL_DISCONNECT_REQUESTED,和ACTION_ACL_DISCONNECTED广播:publicvoidonCreate(){...IntentFilterfilter=newIntentFilter();... -
Android蓝牙连接实现
2021-06-04 11:04:461、蓝牙权限 2、打开蓝牙,有三种方式 -
android设备连接蓝牙打印机,并实现打印功能
2016-04-20 09:59:20android连接蓝牙打印机,实现搜索连接,自定义输入打印内容 -
蓝牙连接参数说明
2020-10-12 16:47:42本文主要讲解蓝牙连接参数说明。 二、实验平台 协议栈版本:BLE-CC254x-1.4.0 编译软件:IAR 8.20.2 硬件平台:Smart RF(主芯片CC2541) 三、版权声明 博主:si_zhou_qun_84342712 声明:喝水不忘挖... -
连接蓝牙设备方式总结
2018-07-11 11:57:43一、在浏览器端(包含手机浏览器、电脑浏览器)1、使用Weex蓝牙开发Github项目地址:https://github.com/lixing123/weex-bluetooth参考文:...不支持多设备同时连接 2、使用Web Bluetoot... -
【Android】蓝牙开发—— 经典蓝牙连接方法
2019-12-03 17:34:17Android官方API给出的经典蓝牙连接方法有2个 createRfcommSocketToServiceRecord 该方法建立的是一种安全的连接。意思就是,与蓝牙设备建立连接时,如果与蓝牙设备没有建立过配对关系,那么连接时会先去建立配对... -
Android与蓝牙耳机建立连接的分析
2012-12-21 13:40:45Android与蓝牙耳机建立连接的一些代码,希望能起到抛砖引玉的效果,大家一起学习 -
Android系统下蓝牙自动配对连接方法
2021-05-26 07:33:10Android系统下蓝牙自动配对连接方法【专利摘要】本发明涉及一种Android系统下蓝牙自动配对连接方法,其包括如下步骤:步骤1、在Android设备端内存储上次进行蓝牙连接蓝牙外设的蓝牙地址,并存储已配对蓝牙外设的蓝牙... -
蓝牙连接之无需配对的低速传输数据方式
2021-05-25 22:16:29基于UUID去建立一条低速的无需配对的蓝牙通道 一、原理 蓝牙是一种使用无线电通信的技术完成设备与设备间通讯与数据交换。 UUID是什么:不同的服务用不同的UUID区分。 服务是什么:能提供的功能。 UUID的详细解释:...