The whole process of Android classic Bluetooth development

1. Basic introduction

  The so-called Bluetooth (Bluetooth) technology is actually a short-range radio technology originally invented by Ericsson. The technology began with Ericsson's 1994 project, which was a study of low-power, low-cost wireless communication links between mobile phones and other accessories. The inventors hope to create a unified set of rules (standardized protocols) for communication between devices to resolve incompatible mobile electronic devices among users.
  On May 20, 1998, Sony established the "Special Interest Group SIG" (Special Interest Group SIG), the predecessor of the Bluetooth SIG, with industry leaders such as Lixin, International Business Machines, Intel, Nokia, and Toshiba. The goal is to develop a low-cost It is an international organization responsible for the formulation and promotion of Bluetooth specifications.

insert image description here

  The development of Bluetooth has experienced 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 versions between 1.x and 3.0 are called classic Bluetooth, and the Bluetooth starting from 4.x is called low-power Bluetooth, that is, Bluetooth ble. According to application, protocol type, etc., Bluetooth can be classified as follows:

insert image description here


2. Introduction to Classic Bluetooth API

  The Android platform includes support for the Bluetooth networking 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 applications can perform the following operations through the Bluetooth API:

  • Scan for other bluetooth devices
  • Query the paired Bluetooth devices of the local Bluetooth adapter
  • Establish RFCOMM channel
  • Connect to other devices through service discovery
  • Bi-directional data transfer with other devices
  • Manage multiple connections

The APIs related to classic Bluetooth development are introduced as follows:

1. BluetoothAdapter class

  BluetoothAdapter represents the local Bluetooth adapter of the mobile device, through which Bluetooth can perform basic operations, such as: start device discovery, obtain paired devices, obtain Bluetooth devices through mac Bluetooth addresses, etc.

(1) Obtain a local Bluetooth adapter instance

Method definition:

/**
 * 作用:
 *	获取本地蓝牙适配器实例
 * 参数:
 *	无
 * 返回:
 *	如果设备具备蓝牙功能,返回BluetoothAdapter 实例;否则,返回null对象。
 */
public static synchronized BluetoothAdapter getDefaultAdapter();

Instructions for use:

1. Obtain the handle of the default local Bluetooth adapter. Currently Android supports only one Bluetooth adapter, but the API can be extended to support many 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 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 the user's consent when calling this method.
3. To turn on Bluetooth, it can also be realized by calling the startActivityForResult method and using the ACTION_REQUEST_ENABLE intent. This method will pop up a dialog box to request permission to turn on Bluetooth. The operation result can be processed in the onActivityResult() method in the Activity.
4. This 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 later transition to STATE_OFF or STATE_ON. If this call returns false, something is preventing the adapter from turning on, such as the device is in airplane mode, or bluetooth is turned on. Therefore, you should also listen to the ACTION_STATE_CHANGED broadcast 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 Bluetooth system services and close the underlying Bluetooth hardware 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 the user's consent when calling this method.
3. This 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 later transition to STATE_OFF or STATE_ON. If this call returns false, something is preventing the adapter from shutting down, e.g. the adapter has been shut down. Therefore, you should also listen to the ACTION_STATE_CHANGED broadcast 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) Obtain 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, an empty set will be returned.

(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, this method will return false.
3. A valid Bluetooth name is at most 248 bytes encoded using UTF-8, although many remote devices can only display the first 40 characters, and some may be limited to 20.

(9) Obtain Bluetooth scan mode

Method definition:

/**
 * 作用:
 *	获取本地蓝牙适配器的当前蓝牙扫描模式。
 * 参数:
 *	无
 * 返回:
 *	当前蓝牙适配器的蓝牙扫描模式。
 */
public int getScanMode();

Instructions for use:

1. BLUETOOTH permission is required.
2. Bluetooth scanning 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 be scanned as well as being scanned.
SCAN_MODE_CONNECTABLE 21 The device can scan for other bluetooth devices.
SCAN_MODE_CONNECTABLE_DISCOVERABLE 23 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 turning on
STATE_ON 12 Indicates that the local Bluetooth adapter is turned on and ready for use
STATE_TURNING_OFF 13 Indicates that the local bluetooth adapter is shutting down

(11) Whether Bluetooth is turned on

Method definition:

/**
 * 作用:
 *	判断当前蓝牙适配器是否打开
 * 参数:
 *	无
 * 返回:
 *	若蓝牙为打开状态,则返回true,否则返回false。
 */
public boolean isEnabled();

Instructions for use:

1. BLUETOOTH permission is required.
2. If the bluetooth is on and available, then return true value, getState()==STATE_ON is equivalent.

(12) Whether Bluetooth is 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 devices is a heavyweight process. You should not try to establish a connection while scanning. At this time, the existing Bluetooth connection will get limited bandwidth and high latency.

(13) Start scanning

Method definition:

/**
 * 作用:
 *	开始扫描周边蓝牙设备。
 * 参数:
 *	无
 * 返回:
 *	若启动成功,返回true;否则返回false。
 */
public boolean startDiscovery();

Instructions for use:

1. BLUETOOTH_ADMIN permission is required.
2. Usually it takes about 12 seconds for the query and scanning process.
3. This is an asynchronous call and it will return immediately. Register for the ACTION_DISCOVERY_STARTED and ACTION_DISCOVERY_FINISHED broadcasts to determine exactly when discovery starts and finishes. Register for 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 devices is a heavyweight process. You should not try to establish a connection while scanning. At this time, the existing Bluetooth connection will get limited bandwidth and high latency. A scan operation can be canceled with 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 the Bluetooth search is a heavyweight process, it will consume a lot of resources, so this method must be called to cancel the search before connecting to the remote Bluetooth device.

(15) Get the 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 unsafe 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 authority, channel occupied, etc., an IOException will be thrown.
4. The Bluetooth service socket created in this way is not safe, and no pairing is required 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 authority, 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.


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 for a Bluetooth hardware address, and objects of this class are immutable. Operations on this class will be 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 remote names and will cache them. This method simply returns the name of this device from the cache.

(2) Obtain Bluetooth MAC

Method definition:

/**
 * 作用:
 *	获取远程蓝牙设备的硬件地址
 * 参数:
 *	无
 * 返回:
 *	蓝牙硬件地址
 */
public String getAddress();

Instructions for use:

none

(3) Obtain 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 and it will return immediately. Register to listen to the ACTION_BOND_STATE_CHANGED broadcast, and get the 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 unsafe 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 man-in-the-middle attacks.
3. For Bluetooth 2.1 devices, the link will be encrypted because encryption is mandatory. For older devices (devices prior to 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 is 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 is unavailable or insufficient permissions, an IOException will be thrown.


3. BluetoothServerSocket class

  BluetoothServerSocket is a listening Bluetooth service socket. Use BluetoothServerSocket to create a listening service port, use the accept method to block, when the method detects the 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, for example, the call is terminated 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, for example, the call is terminated or timed out, an IOException will be thrown.

(3) off

Method definition:

/**
 * 作用:
 *	关闭该监听服务端口,并释放所有关联的资源。
 * 参数:
 *	无
 * 返回:
 *	无
 */
public void close();

Instructions for use:

1. This method will cause other threads to block calls on this socket to 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.


4. BluetoothSocket class

  BluetoothSocket is a Bluetooth socket. On the server side, use BluetoothServerSocket to create a listening server socket. When a connection is accepted by the BluetoothServerSocket, it returns a new BluetoothSocket to manage the connection. On the client side, a single BluetoothSocket is used to initiate the connection and manage the connection. The most common Bluetooth socket type is RFCOMM, which is the type supported by the Android API. RFCOMM is a connection-oriented streaming transport over Bluetooth. It is also known as the Serial Port Practice Profile (SPP). BluetoothSocket is thread-safe, the close method always immediately aborts ongoing operations 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 this method call, such as a connection failure, an IOException will be thrown.

(2) Whether to connect

Method definition:

/**
 * 作用:
 *	获取此套接字的连接状态,即是否与远程蓝牙服务连接。
 * 参数:
 *	无
 * 返回:
 *	若连接则返回true,否则返回false。
 */
public boolean isConnected();

Instructions for use:

none

(3) Get the 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. Even if the socket is not yet connected, the input stream will return, but operations on the stream will throw an IOException until the associated socket is connected.
2. When the method call fails, 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. Even if the socket is not yet connected, the output stream will be returned, but operations on the stream will throw an IOException until the associated socket is connected.
2. When the method call fails, an IOException will be thrown.
3. The output stream object obtained by this method can send data to the peer.

(6) off

Method definition:

/**
 * 作用:
 *	关闭此流并释放与其关联的所有系统资源。如果流已经关闭,则调用此方法不起作用。
 * 参数:
 *	无
 * 返回:
 *	无
 */
public void close();

Instructions for use:

1. If there is a problem with the method call, an IOException will be thrown.


3. Classic Bluetooth development process

1. Analysis of classic bluetooth development process

insert image description here

2. Implementation of Bluetooth server

(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) Obtain the 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, same as Bluetooth server.
(2) Obtain the local Bluetooth adapter, same as the Bluetooth server.
(3) Turn on the bluetooth, same as the bluetooth server.
(4) Turn off Bluetooth, same as Bluetooth server.
(5) Allow Bluetooth to be visible, same as the Bluetooth server.
(6) Define the Bluetooth broadcast receiver, which is used to receive the broadcast of Bluetooth search, connection status change, 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) Register 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 peripheral 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.

4. Matters needing attention and common problems

1. After Android 6.0, location permission is required to search for Bluetooth devices (dangerous permission, dynamic application is required).
2. When searching for Bluetooth on a high-version Android system, in addition to dynamically applying for location permissions, some may also need to manually open the location information of the device, otherwise the Bluetooth cannot be searched.

insert image description here

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 this unit can search. Knowing the MAC address of a Bluetooth device (within valid range), you can initiate a connection to it at any time without opening Bluetooth visibility.
4. If Bluetooth is not already enabled on the device, enabling device discoverability will automatically enable Bluetooth.
5. Searching for peripheral devices is a very heavy operation process for Bluetooth adapters and consumes a lot of resources. After finding a device to connect to, make sure to always stop discovery with cancelDiscovery() before attempting to connect. Also, if a device is already connected, performing a search can significantly reduce the bandwidth available for that connection, so searching for nearby Bluetooth should not be performed while connected.
6. When calling connect(), always ensure that the device is not searching. If a seek operation is in progress, this will drastically slow down connection attempts and increase the chances of connection failures.
7. Search for peripheral devices, and get the name, mac address, and rssi signal strength of peripheral Bluetooth devices through broadcasting.
8. BluetoothSocket is thread-safe. The close() method will terminate all operations performed by BluetoothSocket and close the connection at the same time.
9. Classical Bluetooth transmits and receives data through streams. When reading data from streams, the recipient does not know the boundaries between messages and how many bytes of data to extract at one time, so it is easy to cause data stickiness. . For this kind of problem, you can add a start character and end character to each complete data packet, then the receiver can determine the range of data that needs to be read and processed.



The specific demonstration demo is placed on Github, and interested students can learn about it. If you have any questions, welcome to discuss and make progress together.

GitHub address: https://github.com/JINSHENGCCC/Android_Common/tree/master/Android-BT/src


V. Appendix

  1. In-depth understanding of Android Bluetooth Bluetooth - "Basics"
  2. Android Classic Bluetooth Development
  3. Android Bluetooth development - detailed development process of classic Bluetooth
  4. Bluetooth overview

Guess you like

Origin blog.csdn.net/weixin_56291477/article/details/123413036
Recommended