1. Basic introduction
The so-called Bluetooth (Bluetooth) technology is actually a short-range radio technology, originally invented by Ericsson Corporation. The technology started with Ericsson's 1994 scheme, which was the study of a method for low-power, low-cost wireless communication links between mobile phones and other accessories. The inventor hopes to create a set of unified rules (standardized protocols) for communication between devices to solve the incompatible mobile electronic devices among users.
On May 20, 1998, Sony established the "Special Interest Group SIG", the predecessor of the Bluetooth Technology Alliance, with industry leaders such as Lixin, International Business Machines, Intel, Nokia and Toshiba. , The high-efficiency Bluetooth technology standard that can be wirelessly connected in a short distance range is an international organization responsible for the formulation and promotion of Bluetooth specifications.
The development of Bluetooth has gone through multiple versions of updates, 1.1, 1.2, 2.0, 2.1, 3.0, 4.0, 4.1, 4.2, 5.0, etc. Among them, the version between 1.x and 3.0 is called classic bluetooth, and the bluetooth starting from 4.x is called low-power bluetooth, that is, bluetooth ble. Depending on the application, protocol type, etc., Bluetooth can be classified as follows:
<br/>
Second, the classic Bluetooth API introduction
The Android platform includes support for the Bluetooth network stack, which enables devices to exchange data wirelessly with other Bluetooth devices. The application framework provides access to Bluetooth functionality through the Android Bluetooth API. These APIs allow applications to connect wirelessly to other Bluetooth devices, enabling point-to-point and multipoint wireless functionality. Android apps can do the following through the Bluetooth API:
- Scan for other Bluetooth devices
- Query the paired bluetooth devices of the local bluetooth adapter
- Create RFCOMM channel
- Connect to other devices through service discovery
- Bidirectional data transfer with other devices
- Manage multiple connections
The following introduces the APIs related to classic Bluetooth development:
1. BluetoothAdapter class
The BluetoothAdapter represents the local Bluetooth adapter of the mobile device, through which the Bluetooth adapter can perform basic operations on Bluetooth, such as: start device discovery, obtain paired devices, obtain Bluetooth devices through the mac Bluetooth address, etc.
(1) Get a local Bluetooth adapter instance
Method definition:
/**
* 作用:
* 获取本地蓝牙适配器实例
* 参数:
* 无
* 返回:
* 如果设备具备蓝牙功能,返回BluetoothAdapter 实例;否则,返回null对象。
*/
public static synchronized BluetoothAdapter getDefaultAdapter();
Instructions for use:
1. Get the handle of the default local Bluetooth adapter. Currently Android only supports one Bluetooth adapter, but the API can be extended to support more.
(2) Turn on Bluetooth
Method definition:
/**
* 作用:
* 打开蓝牙
* 参数:
* 无
* 返回:
* 如果蓝牙开始打开,则返回true;如果蓝牙打开发生问题,则返回false。
*/
public boolean enable();
Instructions for use:
1. BLUETOOTH_ADMIN permission is required.
2. This method will directly enable the underlying Bluetooth hardware and start all Bluetooth system services without the user's consent. Due to the different implementations of different Android device systems, some Android systems will also pop up a box to request user consent when calling this method.
3. Turning on Bluetooth can also be achieved by calling the startActivityForResult method and using the ACTION_REQUEST_ENABLE intent. This method will pop up a dialog box asking for permission to turn on Bluetooth. The result of the operation can be handled in the onActivityResult() method in the Activity.
4. The method is an asynchronous call: it will return the result immediately. If this call returns true, the adapter state will transition from STATE_OFF to STATE_TURNING_ON immediately, and to STATE_OFF or STATE_ON later. If this call returns false, there is a problem preventing the adapter from turning on, such as the device is in airplane mode, or Bluetooth is turned on. So the ACTION_STATE_CHANGED broadcast should also be listened to to track subsequent Bluetooth state changes.
(3) Turn off Bluetooth
Method definition:
/**
* 作用:
* 关闭蓝牙
* 参数:
* 无
* 返回:
* 如果蓝牙开始关闭,则返回true;如果蓝牙关闭发生问题,则返回false。
*/
public boolean disable();
Instructions for use:
1. BLUETOOTH_ADMIN permission is required.
2. This method will close all bluetooth connections, stop the bluetooth system service and turn off the underlying bluetooth hardware without the consent of the user. Due to the different implementations of different Android device systems, some Android systems will also pop up a box to request user consent when calling this method.
3. The method is an asynchronous call: it will return the result immediately. If this call returns true, the adapter state will transition from STATE_ON to STATE_TURNING_OFF immediately, and to STATE_OFF or STATE_ON later. If this call returns false, there is a problem preventing the adapter from shutting down, for example the adapter is already closed. So the ACTION_STATE_CHANGED broadcast should also be listened to to track subsequent Bluetooth state changes.
(4) Verify the Bluetooth MAC address
Method definition:
/**
* 作用:
* 验证蓝牙设备MAC地址是否有效。
* 参数:
* address:蓝牙MAC地址,字母必须大写,例如:"00:43:A8:23:10:F1"。
* 返回:
* 如果蓝牙MAC地址有效,则返回true;否则返回false。
*/
public static boolean checkBluetoothAddress(String address);
Instructions for use:
none
(5) Obtain the local Bluetooth MAC address
Method definition:
/**
* 作用:
* 获取本地蓝牙适配器的硬件地址(MAC地址)
* 参数:
* 无
* 返回:
* 本地的硬件地址,例如:"00:11:22:AA:BB:CC"。
*/
public String getAddress();
Instructions for use:
1. BLUETOOTH permission is required.
(6) Get the Bluetooth binding list
Method definition:
/**
* 作用:
* 获取与本机蓝牙所有绑定的远程蓝牙信息。
* 参数:
* 无
* 返回:
* 将本地蓝牙适配器绑定的一组BluetoothDevice对象返回。若出现错误返回null。
*/
public Set<BluetoothDevice> getBondedDevices();
Instructions for use:
1. BLUETOOTH permission is required.
2. If Bluetooth is not turned on, it will return an empty set.
(7) Get the Bluetooth name
Method definition:
/**
* 作用:
* 获取本地蓝牙适配器的蓝牙名称。
* 参数:
* 无
* 返回:
* 本地蓝牙名称。若出现错误,返回null。
*/
public String getName();
Instructions for use:
1. BLUETOOTH permission is required.
(8) Set the Bluetooth name
Method definition:
/**
* 作用:
* 设置本地蓝牙适配器的蓝牙名称。
* 参数:
* name:蓝牙名称。
* 返回:
* 设置成功返回true,否则返回false。
*/
public boolean setName(String name);
Instructions for use:
1. BLUETOOTH_ADMIN permission is required.
2. If Bluetooth is not turned on, the method will return false.
3. Although many remote devices can only display the first 40 characters, and some may be limited to 20, valid Bluetooth names are encoded in UTF-8 to a maximum of 248 bytes.
(9) Get the Bluetooth scan mode
Method definition:
/**
* 作用:
* 获取本地蓝牙适配器的当前蓝牙扫描模式。
* 参数:
* 无
* 返回:
* 当前蓝牙适配器的蓝牙扫描模式。
*/
public int getScanMode();
Instructions for use:
1. BLUETOOTH permission is required.
2. The Bluetooth scan mode determines whether the local Bluetooth adapter can be connected and discovered by remote Bluetooth devices.
3. If Bluetooth is not turned on, this method will return SCAN_MODE_NONE.
Bluetooth scan mode:
name | value (int) | meaning |
---|---|---|
SCAN_MODE_NONE | 20 | The device cannot scan and be scanned. |
SCAN_MODE_CONNECTABLE | twenty one | The device can scan for other Bluetooth devices. |
SCAN_MODE_CONNECTABLE_DISCOVERABLE | twenty three | The device can either scan for other devices or be discovered by other devices. |
(10) Get the status of the Bluetooth adapter
Method definition:
/**
* 作用:
* 获取本地蓝牙适配器的当前状态。
* 参数:
* 无
* 返回:
* 当前蓝牙适配器状态。
*/
public int getState();
Instructions for use:
1. BLUETOOTH permission is required.
Bluetooth adapter status:
name | value (int) | meaning |
---|---|---|
STATE_OFF | 10 | Indicates that the local bluetooth adapter is turned off |
STATE_TURNING_ON | 11 | Indicates that the local bluetooth adapter is on |
STATE_ON | 12 | Indicates that the local bluetooth adapter is on and ready for use |
STATE_TURNING_OFF | 13 | Indicates that the local bluetooth adapter is shutting down |
(11) Is the bluetooth turned on?
Method definition:
/**
* 作用:
* 判断当前蓝牙适配器是否打开
* 参数:
* 无
* 返回:
* 若蓝牙为打开状态,则返回true,否则返回false。
*/
public boolean isEnabled();
Instructions for use:
1. BLUETOOTH permission is required.
2. If Bluetooth is on and available, return true, getState()==STATE_ON is equivalent.
(12) Is bluetooth scanning?
Method definition:
/**
* 作用:
* 判断蓝牙适配器是否正在处于扫描过程中。
* 参数:
* 无
* 返回:
* 若蓝牙处于扫描状态,则返回true;否则返回false。
*/
public boolean isDiscovering();
Instructions for use:
1. BLUETOOTH permission is required.
2. If Bluetooth is not turned on, this method will return false.
3. Scanning for devices is a heavyweight process and should not attempt to establish a connection while scanning, and the existing Bluetooth connection will get limited bandwidth and high latency.
(13) Start scan
Method definition:
/**
* 作用:
* 开始扫描周边蓝牙设备。
* 参数:
* 无
* 返回:
* 若启动成功,返回true;否则返回false。
*/
public boolean startDiscovery();
Instructions for use:
1. BLUETOOTH_ADMIN permission is required.
2. The query scanning process is usually about 12 seconds.
3. This is an asynchronous call, which returns immediately. Register for ACTION_DISCOVERY_STARTED and ACTION_DISCOVERY_FINISHED broadcasts to determine exactly when discovery starts and finishes. Register ACTION_FOUND to be notified when a remote bluetooth device is discovered.
4. If Bluetooth is not turned on, this method will return false.
5. Scanning for devices is a heavyweight process and should not attempt to establish a connection while scanning, and the existing Bluetooth connection will get limited bandwidth and high latency. A scan operation can be canceled using cancelDiscovery().
(14) Cancel scan
Method definition:
/**
* 作用:
* 取消蓝牙搜索操作
* 参数:
* 无
* 返回:
* 如果取消成功, 则返回true; 如果取消失败, 返回false。
*/
public boolean cancelDiscovery()
1. BLUETOOTH_ADMIN permission is required.
2. If Bluetooth is not turned on, this method will return false.
3. Because Bluetooth search is a heavyweight process and consumes a lot of resources, this method must be called to cancel the search before connecting to a remote Bluetooth device.
(15) Get remote bluetooth device
Method definition:
/**
* 作用:
* 获取给定蓝牙硬件地址的BluetoothDevice对象。
* 参数:
* address:蓝牙MAC地址,字母必须大写,例如:"00:43:A8:23:10:F1"。
* 返回:
* 指定的远程蓝牙设备。
*/
public BluetoothDevice getRemoteDevice(String address);
Instructions for use:
1. If the MAC is invalid, an IllegalArgumentException will be thrown.
(16) Create an insecure Bluetooth service socket
Method definition:
/**
* 作用:
* 创建一个正在监听的不安全的带有服务记录的无线射频通信(RFCOMM)蓝牙端口。
* 参数:
* name:SDP记录下的服务器名,可以是任意字符串。
* uuid:SDP记录下的UUID。
* 返回:
* BluetoothServerSocket对象。
*/
public BluetoothServerSocket listenUsingInsecureRfcommWithServiceRecord(String name, UUID uuid);
Instructions for use:
1. BLUETOOTH permission is required.
2. The system will allocate an unused RFCOMM channel for listening.
3. When an error occurs, such as Bluetooth unavailable, insufficient permissions, channel occupied, etc., an IOException will be thrown.
4. The Bluetooth service socket created in this way is insecure and does not need to be paired when connecting.
(17) Create a secure Bluetooth service socket
Method definition:
/**
* 作用:
* 创建一个正在监听的安全的带有服务记录的无线射频通信(RFCOMM)蓝牙端口。
* 参数:
* name:SDP记录下的服务器名,可以是任意字符串。
* uuid:SDP记录下的UUID。
* 返回:
* BluetoothServerSocket对象。
*/
public BluetoothServerSocket listenUsingRfcommWithServiceRecord(String name, UUID uuid);
Instructions for use:
1. BLUETOOTH permission is required.
2. The system will allocate an unused RFCOMM channel for listening.
3. When an error occurs, such as Bluetooth unavailable, insufficient permissions, channel occupied, etc., an IOException will be thrown.
4. The bluetooth service socket created in this way is safe and needs to be paired when connecting.
<br/>
2. BluetoothDevice class
The BluetoothDevice object represents a remote Bluetooth device. Through this class, the physical address, name, connection status and other information of the remote device can be queried. This class is actually just a simple wrapper around a bluetooth hardware address, and objects of this class are immutable. Operations on this class are performed on the hardware of the remote Bluetooth device.
(1) Get the Bluetooth name
Method definition:
/**
* 作用:
* 获取远程蓝牙设备的蓝牙名称。
* 参数:
* 无
* 返回:
* 成功则返回蓝牙名称,若出现问题则返回null。
*/
public String getName();
Instructions for use:
1. BLUETOOTH permission is required.
2. When performing a device scan, the local adapter will automatically retrieve the remote names and will cache them. This method simply returns the name of this device from the cache.
(2) Get the Bluetooth MAC
Method definition:
/**
* 作用:
* 获取远程蓝牙设备的硬件地址
* 参数:
* 无
* 返回:
* 蓝牙硬件地址
*/
public String getAddress();
Instructions for use:
none
(3) Get the Bluetooth binding status
Method definition:
/**
* 作用:
* 获取远程蓝牙设备的绑定状态
* 参数:
* 无
* 返回:
* 蓝牙绑定状态
*/
public int getBondState();
Instructions for use:
1. BLUETOOTH permission is required.
Bluetooth binding status:
name | value (int) | meaning |
---|---|---|
BOND_NONE | 10 | The remote device is not bound. |
BOND_BONDING | 11 | Binding with remote device is in progress. |
BOND_BONDED | 12 | The remote device is bound. |
(4) Bind the remote device
Method definition:
/**
* 作用:
* 开始与远程蓝牙设备的绑定过程。
* 参数:
* 无
* 返回:
* 若成功开始绑定则返回true,否则返回false。
*/
public boolean createBond();
Instructions for use:
1. BLUETOOTH_ADMIN permission is required.
2. This is an asynchronous call, which returns immediately. Register to listen to the ACTION_BOND_STATE_CHANGED broadcast, and get its result notification in time when the binding process is completed.
3. The Android system service will handle the necessary user interaction to confirm and complete the binding process.
(5) Create an insecure Bluetooth socket
Method definition:
/**
* 作用:
* 创建不安全的蓝牙套接字。
* 参数:
* uuid:用于查找RFCOMM通道的服务记录UUID
* 返回:
* 一个准备好外界连接的RFCOMM蓝牙服务端口
*/
public BluetoothSocket createInsecureRfcommSocketToServiceRecord(UUID uuid);
Instructions for use:
1. BLUETOOTH permission is required.
2. The communication channel will not have an authenticated link key, i.e. it will be subject to a man-in-the-middle attack.
3. For Bluetooth 2.1 devices, the link will be encrypted because encryption is mandatory. For older devices (pre-Bluetooth 2.1), the link will not be encrypted.
4. It is intended to be used with listenUsingInsecureRfcommWithServiceRecord(String, UUID) for peer-to-peer Bluetooth applications.
5. When an error occurs, such as Bluetooth unavailable or insufficient permissions, an IOException will be thrown.
(6) Create a secure Bluetooth socket
Method definition:
/**
* 作用:
* 创建安全的蓝牙套接字。
* 参数:
* uuid:用于查找RFCOMM通道的服务记录UUID
* 返回:
* 一个准备好外界连接的RFCOMM蓝牙服务端口
*/
public BluetoothSocket createRfcommSocketToServiceRecord(UUID uuid);
Instructions for use:
1. BLUETOOTH permission is required.
2. Only authenticated socket connections can use this socket. Authentication refers to authenticating the link key to prevent man-in-the-middle attacks.
3. Communication on this socket will be encrypted.
4. This is designed for use with the peer-to-peer Bluetooth application listenUsingRfcommWithServiceRecord(String, UUID).
5. When an error occurs, such as Bluetooth unavailable or insufficient permissions, an IOException will be thrown.
<br/>
3. BluetoothServerSocket class
BluetoothServerSocket is a socket that listens to a Bluetooth service. Using BluetoothServerSocket, you can create a listening service port and use the accept method to block. When the method detects a connection, it will return a BluetoothSocket object to manage the connection. BluetoothServerSocket is thread-safe, the close method always immediately aborts ongoing operations and closes the Bluetooth service socket. BLUETOOTH permission is required.
(1) Block waiting for connection (no timeout)
Method definition:
/**
* 作用:
* 阻塞直到建立连接。
* 参数:
* 无
* 返回:
* 成功连接时连接的BluetoothSocket对象。
*/
public BluetoothSocket accept();
Instructions for use:
1. Once this call returns, it can be called again to accept subsequent incoming connections.
2. close() can be used to abort this call from another thread.
3. When an error occurs, such as the call is aborted or timed out, an IOException will be thrown.
(2) Block waiting for connection (with timeout)
Method definition:
/**
* 作用:
* 阻塞直到建立连接或超时。
* 参数:
* timeout:阻塞等待的超时时间。
* 返回:
* 成功连接时连接的BluetoothSocket对象。
*/
public BluetoothSocket accept(int timeout);
Instructions for use:
1. Once this call returns, it can be called again to accept subsequent incoming connections.
2. close() can be used to abort this call from another thread.
3. When an error occurs, such as the call is aborted or timed out, an IOException will be thrown.
(3) close
Method definition:
/**
* 作用:
* 关闭该监听服务端口,并释放所有关联的资源。
* 参数:
* 无
* 返回:
* 无
*/
public void close();
Instructions for use:
1. Calls to this method that will cause other threads to block on this socket immediately throw an IOException.
2. Closing this port will not close the BluetoothSocket object returned by the accept() method.
3. If there is a problem with the method call, an IOException will be thrown.
<br/>
4. BluetoothSocket class
BluetoothSocket is a Bluetooth socket. On the server side, use BluetoothServerSocket to create a listening server socket. When the connection is accepted by the BluetoothServerSocket, it will return a new BluetoothSocket to manage the connection. On the client side, a single BluetoothSocket is used to initiate and manage connections. The most common type of Bluetooth socket is RFCOMM, which is supported by the Android API. RFCOMM is a connection-oriented streaming transport via Bluetooth. It is also known as the Serial Port Specification (SPP). BluetoothSocket is thread safe and the close method always immediately aborts the ongoing operation and closes the socket. BLUETOOTH permission is required.
(1) Connection
Method definition:
/**
* 作用:
* 尝试连接到远程蓝牙服务器。
* 参数:
* 无
* 返回:
* 无
*/
public void connect();
Instructions for use:
1. This method will block until a connection is established or the connection fails. If this method returns without exception, this socket is now connected.
2. When there is a problem with the method call, such as connection failure, an IOException will be thrown.
(2) Whether to connect
Method definition:
/**
* 作用:
* 获取此套接字的连接状态,即是否与远程蓝牙服务连接。
* 参数:
* 无
* 返回:
* 若连接则返回true,否则返回false。
*/
public boolean isConnected();
Instructions for use:
none
(3) Obtain remote bluetooth device
Method definition:
/**
* 作用:
* 获取此套接字连接的远程蓝牙设备。
* 参数:
* 无
* 返回:
* 连接的远程蓝牙设备BluetoothDevice对象。
*/
public BluetoothDevice getRemoteDevice();
Instructions for use:
none
(4) Get the input stream
Method definition:
/**
* 作用:
* 获取与此套接字关联的输入流。
* 参数:
* 无
* 返回:
* 输入流对象。
*/
public InputStream getInputStream();
Instructions for use:
1. The input stream will be returned even if the socket is not yet connected, but operations on the stream will throw an IOException until the associated socket is connected.
2. When an error occurs in the method call, an IOException will be thrown.
3. The input stream object obtained by this method can read the data sent by the peer.
(5) Get the output stream
Method definition:
/**
* 作用:
* 获取与此套接字关联的输出流。
* 参数:
* 无
* 返回:
* 输出流对象。
*/
public OutputStream getOutputStream();
Instructions for use:
1. The output stream will be returned even if the socket is not yet connected, but operations on the stream will throw an IOException until the associated socket is connected.
2. When an error occurs in the method call, an IOException will be thrown.
3. The output stream object obtained by this method can send data to the peer.
(6) close
Method definition:
/**
* 作用:
* 关闭此流并释放与其关联的所有系统资源。如果流已经关闭,则调用此方法不起作用。
* 参数:
* 无
* 返回:
* 无
*/
public void close();
Instructions for use:
1. If there is a problem with the method call, an IOException will be thrown.
<br/>
3. Classic Bluetooth development process
1. Analysis of the classic Bluetooth development process
2. Bluetooth server implementation
(1) Add permissions to the project manifest file AndroidManifest.xml:
<!--如果使用了BLUETOOTH_ADMIN权限,那么必须使用BLUETOOTH权限-->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<!--android6.0后需要搜索周边蓝牙设备,需要添加以下两个权限-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!--要求设备硬件必须支持蓝牙-->
<uses-feature android:name="android.hardware.bluetooth" android:required="true"/>
(2) Get a local Bluetooth adapter:
BluetoothAdapter mAdapter= BluetoothAdapter.getDefaultAdapter();
(3) Turn on Bluetooth:
//方式一:通过Intent来向用户弹框请求打开蓝牙,可以重写onActivityResult来监听打开蓝牙的请求结果
//打开蓝牙
public void openBluetooth(){
if(mBluetoothAdapter==null){
//自定义方法,用来往TextView上添加提示信息
showTip("当前设备不支持蓝牙功能!");
return;
}
if(mBluetoothAdapter.isEnabled()){
showTip("蓝牙已打开");
return;
}
Intent intent=new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent,GlobalDef.REQ_CODE_OPEN_BT);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode==GlobalDef.REQ_CODE_OPEN_BT){
if(resultCode == Activity.RESULT_OK){
showTip("蓝牙打开成功");
}
else{
showTip("蓝牙打开失败");
}
}
}
//方式二:通过enable方法静默打开蓝牙,无需用户同意(部分Android系统使用该方法依然会弹框提示,向用户请求打开蓝牙)
mBluetoothAdapter.enable();
(4) Turn off Bluetooth
//关闭蓝牙,无需用户同意(部分Android系统使用该方法依然会弹框提示)
mBluetoothAdapter.disable();
(5) Allow bluetooth to be visible:
//方式一:通过Intent方式向用户请求允许蓝牙被搜索
//注:如果蓝牙没有开启,用户点击确定后,会首先开启蓝牙,继而设置蓝牙能被扫描
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
//设置蓝牙可见性的时间,默认持续时间为120秒,每个请求的最长持续时间上限为300秒
intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 120);
startActivity(intent);
//方式二:通过反射的方式来设置蓝牙可见性,且不会出现弹框
//注:如果蓝牙没有开启,通过此方式并不会直接打开蓝牙
/**
* 设置蓝牙可见
* @param adapter
* @param timeout 超时为0时,永久可见
*/
public static void setDiscoverableTimeout(BluetoothAdapter adapter, int timeout) {
//BluetoothAdapter adapter=BluetoothAdapter.getDefaultAdapter();
try {
Method setDiscoverableTimeout = BluetoothAdapter.class.getMethod("setDiscoverableTimeout", int.class);
setDiscoverableTimeout.setAccessible(true);
Method setScanMode =BluetoothAdapter.class.getMethod("setScanMode", int.class,int.class);
setScanMode.setAccessible(true);
setDiscoverableTimeout.invoke(adapter, timeout);
setScanMode.invoke(adapter, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE,timeout);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 关闭蓝牙可见
* @param adapter
*/
public static void closeDiscoverableTimeout(BluetoothAdapter adapter) {
try {
Method setDiscoverableTimeout = BluetoothAdapter.class.getMethod("setDiscoverableTimeout", int.class);
setDiscoverableTimeout.setAccessible(true);
Method setScanMode =BluetoothAdapter.class.getMethod("setScanMode", int.class,int.class);
setScanMode.setAccessible(true);
setDiscoverableTimeout.invoke(adapter, 1);
setScanMode.invoke(adapter, BluetoothAdapter.SCAN_MODE_CONNECTABLE,1);
} catch (Exception e) {
e.printStackTrace();
}
}
(6) Create a Bluetooth service socket and wait for other Bluetooth clients to connect:
try{
mSocketList=new LinkedList<BluetoothSocket>();//用来管理连接的蓝牙套接字
mExecutorService= Executors.newCachedThreadPool();//创建线程池
//创建蓝牙服务端
mServerSocket=mBluetoothAdapter.listenUsingRfcommWithServiceRecord("BluetoothTool", GlobalDef.BT_UUID);
mServerRunningFlag=true;
showTip("蓝牙服务端成功启动");
new Thread(){
@Override
public void run(){
try{
BluetoothSocket socket=null;
//循环等待蓝牙socket连接
while(mServerRunningFlag){
socket=mServerSocket.accept();//阻塞式
mSocketList.add(socket);
//SocketThread为自定义的线程类,用于管理BluetoothSocket的读写操作
mExecutorService.execute(new SocketThread(socket));
}
}catch (Exception e){
e.printStackTrace();
}
}
}.start();
}catch(IOException e){
e.printStackTrace();
showTip("服务端启动出现异常");
Log.e(TAG,"runServer IOException");
}
(7) After the connection is successful, data transmission is performed by obtaining the input and output streams of the BluetoothSocket:
// 获取流
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
// 写出、读入
byte[] temp=new byte[1024];
inputStream.read(temp);//当无数据时将阻塞等待
outputStream.write(temp);
(8) The following is a simple implementation of SocketThread for operating BluetoothSocket, for reference only:
class SocketThread extends Thread {
private BluetoothSocket mSocket=null;
private InputStream mIn;
private OutputStream mOut;
private boolean isOpen = false;
public SocketThread(BluetoothSocket socket) {
try {
mSocket=socket;
mIn = mSocket.getInputStream();
mOut = mSocket.getOutputStream();
isOpen = true;
Log.d(TAG, "a socket thread create");
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "create SocketThread fail");
}
}
@Override
public void run() {
int readLen=0;
byte[] buffer=new byte[1024];
try{
while(isOpen){
readLen=mIn.read(buffer);
if(readLen>0){
Log.i(TAG,"read data length="+readLen);
Log.i(TAG,"read data hex = "+StringUtil.bytesToHexString(buffer,0,readLen));
}
}
}catch (Exception e){
e.printStackTrace();
release();
}
}
/**
* 写入数据
* @param data
* @param offset
* @param len
*/
public void writeData(byte[] data,int offset,int len){
if (data == null || offset<0 || len<=0 || (len+offset)>data.length) {
Log.e(TAG,"BT writeData params fail");
return;
}
try {
mOut.write(data,offset,len);
mOut.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void release(){
Log.d(TAG,"A socketThread release");
try{
isOpen=false;
if(mOut!=null){
try{
mOut.close();
}catch (Exception e){
e.printStackTrace();
}
mOut=null;
}
if(mIn!=null){
try{
mIn.close();
}catch (Exception e){
e.printStackTrace();
}
mIn=null;
}
if(mSocket!=null){
try{
mSocket.close();
}catch (Exception e){
e.printStackTrace();
}
mSocket=null;
}
}catch (Exception e){
e.printStackTrace();
}
}
}
3. Bluetooth client implementation
(1) Add permissions, the same as the Bluetooth server.
(2) Obtain the local Bluetooth adapter, the same as the Bluetooth server.
(3) Turn on bluetooth, same as bluetooth server.
(4) Turn off the bluetooth, same as the bluetooth server.
(5) Allow bluetooth to be visible, same as bluetooth server.
(6) Define a Bluetooth broadcast receiver to receive broadcasts of Bluetooth searches, connection status changes, etc.:
class BluetoothBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent){
String action=intent.getAction();
Log.d(TAG,"Action received is "+action);
//蓝牙搜索
if(BluetoothDevice.ACTION_FOUND.equals(action)){
BluetoothDevice scanDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if(scanDevice == null || scanDevice.getName() == null){
return;
}
int btType=scanDevice.getType();
if(btType==BluetoothDevice.DEVICE_TYPE_LE || btType==BluetoothDevice.DEVICE_TYPE_UNKNOWN){
return;
}
Log.d(TAG, "bt name="+scanDevice.getName()+" address="+scanDevice.getAddress());
//将搜索到的蓝牙设备加入列表
deviceList.add(scanDevice);
short rssi=intent.getExtras().getShort(BluetoothDevice.EXTRA_RSSI);
rssiList.add(rssi);
listAdapter.notifyDataSetChanged();//通知ListView适配器更新
}
//蓝牙配对
else if(BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)){
BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if(mCurDevice!=null && btDevice.getAddress().equals(mCurDevice.getAddress())){
int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
if(state==BluetoothDevice.BOND_NONE){
showTip("已取消与设备" + btDevice.getName() + "的配对");
mFlag=-1;
}
else if(state==BluetoothDevice.BOND_BONDED){
showTip("与设备" + btDevice.getName() + "配对成功");
mFlag=1;
}
}
}
else if(BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)){
int blueState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0);
switch (blueState) {
case BluetoothAdapter.STATE_TURNING_ON:
Log.i(TAG,"onReceive---------STATE_TURNING_ON");
break;
case BluetoothAdapter.STATE_ON:
Log.i(TAG,"onReceive---------STATE_ON");
showTip("蓝牙当前状态:ON");
break;
case BluetoothAdapter.STATE_TURNING_OFF:
Log.i(TAG,"onReceive---------STATE_TURNING_OFF");
break;
case BluetoothAdapter.STATE_OFF:
Log.i(TAG,"onReceive---------STATE_OFF");
showTip("蓝牙当前状态:OFF");
break;
}
}
}
}
(7) Registration broadcast:
mBluetoothBroadcastReceiver=new BluetoothBroadcastReceiver();
IntentFilter filter=new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
mContext.registerReceiver(mBluetoothBroadcastReceiver,filter);
(8) Search for surrounding Bluetooth devices:
if(mBluetoothAdapter.isDiscovering()){
mBluetoothAdapter.cancelDiscovery();
}
//搜索到的蓝牙设备通过广播接收
mBluetoothAdapter.startDiscovery();
(9) Establish a connection with the Bluetooth server:
/**
* 蓝牙配对并连接
*/
public void bondAndConnect(BluetoothDevice mCurDevice){
//取消搜索
if(mBluetoothAdapter.isDiscovering()){
mBluetoothAdapter.cancelDiscovery();
}
if(mCurDevice==null){
showTip("远程蓝牙设备为空!");
return;
}
//当前蓝牙设备未配对,则先进行配对
if(mCurDevice.getBondState()==BluetoothDevice.BOND_NONE){
Log.d(TAG,"create bond to "+mCurDevice.getName());
boolean nRet= BluetoothUtil.createBond(mCurDevice);
if(!nRet){
showTip("createBond fail!");
return;
}
showLoadingDialog("正在与【"+mCurDevice.getName()+"】进行配对...");
mFlag=0;
while(mFlag==0){
SystemClock.sleep(250);
}
if(mFlag==-1){
showTip("与【"+mCurDevice.getName()+"】的蓝牙配对失败");
dismissLoadingDialog();
return;
}
}
if(mCurDevice.getBondState()==BluetoothDevice.BOND_BONDED){
showLoadingDialog("正在与【"+mCurDevice.getName()+"】进行连接...");
try {
//创建Socket
BluetoothSocket socket = mCurDevice.createRfcommSocketToServiceRecord(GlobalDef.BT_UUID);
//连接蓝牙服务套接字
socket.connect();
mThread=new SocketThread(socket);
mThread.start();
showTip(("成功与【"+mCurDevice.getName()+"】建立连接"));
} catch (IOException e) {
Log.d(TAG,"socket connect fail");
showTip(("连接【"+mCurDevice.getName()+"】失败"));
e.printStackTrace();
}
}
dismissLoadingDialog();
}
(10) After the connection is successful, data transmission is performed through the input and output streams, which is the same as the Bluetooth server.
<br/>
Fourth, matters needing attention and common problems
1. After Android 6.0, searching for Bluetooth devices requires location permission (dangerous permission, dynamic application is required).
2. When performing a Bluetooth search on a higher version of Android system, in addition to dynamically applying for location permissions, some may need to manually open the device's location information, otherwise the Bluetooth cannot be searched.
3. When searching for surrounding Bluetooth devices, the machine does not need to be in the visible state of Bluetooth. However, other devices must be in the visible state of Bluetooth before the machine can search. If the MAC address of a bluetooth device (in range) is known, a connection can be initiated to it at any time without turning on bluetooth visibility.
4. If Bluetooth is not already enabled on the device, enabling device detectability will automatically enable Bluetooth.
5. Searching for peripheral devices is a very heavy operation process for the Bluetooth adapter and consumes a lot of resources. After finding a device to connect, make sure to always use cancelDiscovery() to stop discovery before trying to connect. Also, if a device is already connected, performing a search operation may significantly reduce the bandwidth available for that connection, so searching for nearby Bluetooth should not be performed while connected.
6. When calling connect(), always make sure that the device is not searching. If a search operation is in progress, it can significantly slow down connection attempts and increase the likelihood of connection failures.
7. Search for peripheral devices, and obtain the name, mac address, rssi signal strength, etc. of peripheral Bluetooth devices through broadcasting.
8. BluetoothSocket is thread-safe. The close() method will terminate all operations performed by BluetoothSocket, and will also close the connection.
9. Classic Bluetooth transmits and receives data through streams. When reading data from streams, since the receiver does not know the boundaries between messages or how many bytes of data to extract at one time, it is easy to cause data sticking problems. . For this kind of problem, you can add a start and end character to each complete data packet, then the receiver can determine the range of data that needs to be read and processed.
<br/>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。