Android BLE Bluetooth development, connect Bluetooth devices for communication

Android BLE Bluetooth development, connect Bluetooth devices for communication

1. Introduction

This article is mainly based on Android's official low-power Bluetooth connection service.

Explain how to connect to Bluetooth devices via UUID. If you don't know much about GATT services, this article should be able to help you a little.

Official document address: https://developer.android.google.cn/guide/topics/connectivity/bluetooth-le?hl=zh_cn#connect

2. Concept

If you are an old user, you should know that Bluetooth devices used to be high power consumption components. It was impossible to turn them on for a long time. After Bluetooth 4.0, Bluetooth communication, power consumption, and anti-interference have been significantly improved. At the same time, the cost of Bluetooth has also been reduced.

Then there was the explosion of various wearable devices we have today, such as bracelets, Bluetooth headsets, Bluetooth electronic scales, Bluetooth speakers, etc.

At the same time, other industrial or external devices have also begun to support Bluetooth communication in large quantities because of the reduced energy consumption and cost.

For low-power Bluetooth communication, Android 4.3 (API 18) introduced the BLE library. We can directly use the Bluetooth BLE library in the Android SDK without importing additional dependent libraries.

In the past, when developing Bluetooth communication, you also needed to implement Bluetooth pairing. You needed to actively jump to the phone settings interface to pair with the PIN code, and then you could connect to Bluetooth after the pairing was successful.

By using the BLE library, we can connect directly through the UUID of the Bluetooth device (through the GATT service), and can connect directly in the current application without going through the system settings.

The automatic matching links of various bracelets on the market, the automatic connection of electronic scales, etc. all communicate and link through GATT.

2.1 Terminology

  • GATT: The full name is: Generic Attribute Profile, which is translated as: General Attribute Profile. The GATT profile is a general specification for sending and receiving short data fragments called "attributes ATT" on the BLE link. Currently, all low-power application profiles are based on GATT.
  • ATT: The full name is: Attribute Protocol, which is translated as: attribute protocol. It is the building block of GATT, and the relationship between the two is also called GATT/ATT. Each attribute is uniquely identified by a universal unique identifier (UUID), which is a 128-bit standardized format of a string ID used to uniquely identify information. The attributes transmitted by ATT are in the format of characteristics and services.
  • Characteristic: A characteristic consists of a value and zero or more descriptors that describe the characteristic value. You can think of a characteristic as a type, which is similar to a class.
  • Descriptors: Descriptors are defined properties that describe a characteristic value. For example, a descriptor can specify a human-readable description, an acceptable range for characteristic values, or a unit of measurement specific to a characteristic value.
  • Service — A service is a collection of characteristics. For example, you might have a service called "Heart Rate Monitor" that includes characteristics such as "Heart Rate Measurement".

The above terminology is introduced from the Android official website

2.2 Communication Process

Suppose we have a Bluetooth external device (Device) and a Bluetooth-enabled mobile device (Phone). The communication steps between the two are:

  1. Device turns on Bluetooth. (Usually, these devices turn on Bluetooth by default after they are turned on.)
  2. Phone turns on Bluetooth.
  3. Phone discovers Device.
  4. Phone creates a Bluetooth connection with Device.
  5. Phone creates a Gatt client and connects to the Device Gatt server.
  6. Phone obtains messages from Device through the Gatt service function and sends messages to Device.

This is the whole process. I will also introduce this communication process below.

3. Development

Based on my usage, I will introduce the complete Bluetooth development and configuration process from scratch. Here is a reference for you

The main language is Java

3.1 Permissions

To use Bluetooth functionality in your app, you must declare the BLUETOOTH permission. This permission is required to perform any Bluetooth communication, such as requesting a connection, accepting a connection, and transferring data.

At the same time, location permission is also required because Bluetooth LE beacons are usually associated with locations. If the ACCESS_FINE_LOCATION permission is not enabled, we will not be able to discover Bluetooth devices.

That is, executing the Bluetooth scanning API will not get any results (PS: The error log in Logcat will tell you that you need to enable location permissions, otherwise you cannot scan and discover Bluetooth devices).

 <!-- Bluetooth search pairing -->
< uses - permission android : name = "android.permission.BLUETOOTH" />
< uses - permission android : name = "android.permission.ACCESS_COARSE_LOCATION" />
< uses - permission android : name = "android.permission.ACCESS_FINE_LOCATION" />
< uses - permission android : name = "android.permission.BLUETOOTH_ADMIN" />
< uses - permission android : name = "android.permission.BLUETOOTH_SCAN" />
<!-- Control Bluetooth activation -->
< uses - permission android : name = "android.permission.BLUETOOTH_CONNECT" />

<!-- If the application must be installed on a device that supports Bluetooth, you can set the value of required below to true. -->
< uses - feature
android : name = "android.hardware.bluetooth_le"
android : required = "false" />

android.permission.ACCESS_FINE_LOCATION​ is a permission for higher versions of API 28. If you want to support lower versions, you need to apply for <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />.

If we want to perform Bluetooth scanning, we need to apply for the permission: <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />

If you want to perform a Bluetooth connection, turn Bluetooth on and off. You need to apply for: <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> permission

The above two permissions are only valid on API 31. For lower versions, you need to apply:

<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> permission is enough.

After the permissions are configured, it is time to develop the code.

Regardless of whether it is a high version or a low version, it is safest to apply for all permissions.

3.2 Check whether the device supports Bluetooth

Usually, mobile phones have Bluetooth. However, if we use other Android devices, such as TVs, tablets, all-in-one computers, etc., we cannot guarantee whether they have Bluetooth.

If you are unsure, you can check the availability of BLE by using the following code.

 @Override
protected void onCreate ( @Nullable Bundle savedInstanceState ) {
super . onCreate ( savedInstanceState );
if ( ! getPackageManager (). hasSystemFeature ( PackageManager . FEATURE_BLUETOOTH_LE )) {
//Bluetooth devices are not supported
finish ();
} else {
//Support Bluetooth devices
}

Whether Bluetooth is turned on or not does not affect the test results. It checks whether the device has Bluetooth function, not whether Bluetooth is turned on. The following will introduce how to determine whether Bluetooth is turned on

3.3 Turn on Bluetooth

Once our device supports Bluetooth and the permissions are configured, the next step is to obtain the BluetoothAdapter object.

 final BluetoothManager bluetoothManager = ( BluetoothManager ) getSystemService ( Context . BLUETOOTH_SERVICE );
BluetoothAdapter bluetoothAdapter = bluetoothManager . getAdapter ();

Our subsequent control of the Bluetooth status is achieved through this method.

First, check whether Bluetooth is enabled. You can use the isEnabled() method to check:

 if ( bluetoothAdapter == null || ! bluetoothAdapter . isEnabled ()) {
//Open the Bluetooth link of the device
bluetoothAdapter . enable (); // Enable Bluetooth
//Dynamically determine whether you have location permission ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION, and then perform a Bluetooth scan
} else {
//Dynamically determine whether you have the location permission ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION, and then perform a Bluetooth scan
}

We can actually use bluetoothAdapter.enable() to enable Bluetooth directly. If Bluetooth is not enabled, we can enable it directly.

The result of this method is not returned in real time. If we want to know whether Bluetooth is turned on, we need to listen to the broadcast of Bluetooth status. The following will introduce broadcast monitoring.

PS: This method requires the android.Manifest.permission.BLUETOOTH_CONNECT permission to use.

The official recommendation is that we use Intent to let the system settings turn on Bluetooth.

 if ( bluetoothAdapter == null || ! bluetoothAdapter .isEnabled ( ) ) {
Intent enableBtIntent = new Intent ( BluetoothAdapter .ACTION_REQUEST_ENABLE ) ;
startActivityForResult ( enableBtIntent , REQUEST_ENABLE_BT ) ;
}

But now the startActivityForResult method is obsolete. We can use Launcher to call:

 ActivityResultLauncher < Intent > launcher = registerForActivityResult ( new ActivityResultContracts .StartActivityForResult ( ) , result -> {
if ( result .getResultCode ( ) == RESULT_OK ) {
// Process the returned result
}
} ) ;

The launcher above needs to be initialized in the onCreate method of Activity. Then configure it where the Bluetooth settings interface needs to be launched:

 Intent enableBtIntent = new Intent ( BluetoothAdapter . ACTION_REQUEST_ENABLE ); //Create a Bluetooth startup intent
launcher . launch ( enableBtIntent ); // Just use launcher to start this intent.

If Android Studio displays a code error warning when we use bluetoothAdapter.enable();​, we can add the @SuppressLint("MissingPermission") annotation to the method used in the code.

3.4 Broadcast Monitoring

In fact, whether this broadcast monitoring is necessary depends on your actual situation. It is not necessarily necessary.

First, create a dynamic broadcast object:

 public class BluetoothFoundReceiver extends BroadcastReceiver {
@Override
public void onReceive ( Context context , Intent intent ) {
String action = intent . getAction ();
//After monitoring the Bluetooth status, send a message
try {
if ( BluetoothAdapter . ACTION_DISCOVERY_STARTED . equals ( action )) {
//Start scanning
} else if ( BluetoothAdapter . ACTION_DISCOVERY_FINISHED . equals ( action )) {
//End scanning
} else if ( BluetoothDevice . ACTION_FOUND . equals ( action )) {
//Discover the device. Every time you scan a device, it will be triggered once
BluetoothDevice device = intent . getParcelableExtra ( BluetoothDevice . EXTRA_DEVICE );
//We can get the Bluetooth device
} else if ( action . equals ( BluetoothAdapter . ACTION_STATE_CHANGED )) {
//Bluetooth switch status

int statue = bluetoothAdapter . getState ();
switch ( status ) {
case BluetoothAdapter . STATE_OFF :
Log . e ( TAG , "Bluetooth status: ,Bluetooth off" );
break ;
case BluetoothAdapter . STATE_ON :
Log . e ( TAG , "Bluetooth status: ,Bluetooth is on" );

break ;
case BluetoothAdapter . STATE_TURNING_OFF :
Log . e ( TAG , "Bluetooth status: Bluetooth is turning off" );
break ;
case BluetoothAdapter . STATE_TURNING_ON :
Log . e ( TAG , "Bluetooth status:,Bluetooth is turning on" );
break ;
}
}
} catch ( Exception e ) {
e . printStackTrace ();
}
}
}

Then register for broadcasting:

 bluetoothFoundReceiver = new BluetoothFoundReceiver ();
IntentFilter filter = new IntentFilter ();
filter . addAction ( BluetoothAdapter . ACTION_STATE_CHANGED ); //Connect Bluetooth, disconnect Bluetooth
filter . addAction ( BluetoothDevice . ACTION_FOUND ); // Find the device's broadcast
filter . addAction ( BluetoothAdapter . ACTION_DISCOVERY_FINISHED ); //Search completed broadcast
filter . addAction ( BluetoothDevice . ACTION_BOND_STATE_CHANGED ); //State changes when pairing starts or when pairing succeeds
registerReceiver ( bluetoothFoundReceiver , filter );

After registration is complete, you need to unregister in the onDestroy method:

 @Override
protected void onDestroy () {
if ( bluetoothFoundReceiver != null )
unregisterReceiver ( bluetoothFoundReceiver ); //Stop monitoring
super . onDestroy ();
}

In fact, we only need to monitor the Bluetooth status BluetoothAdapter.ACTION_STATE_CHANGED Other device search and pairing are not necessary, because the efficiency of device search that triggers broadcast is too low, and it will take longer to search repeatedly. The device cannot be found.

3.5 Bluetooth device search

The recommended search method in the official documentation is:

 bluetoothAdapter . startLeScan ( leScanCallback ); //Find
bluetoothAdapter . stopLeScan ( leScanCallback ); //Stop searching

But now this method is outdated. The replacement method is:

 BluetoothLeScanner scanner = bluetoothAdapter . getBluetoothLeScanner ();
// No permission verification
ScanCallback callback = new ScanCallback () {
@Override
public void onScanResult ( int callbackType , ScanResult result ) {
BluetoothDevice device = result.getDevice () ; //Get the device
// Log.e(TAG, "Device found" + device.getName());
}

@Override
public void onScanFailed ( int errorCode ) {
super . onScanFailed ( errorCode );
Log . e ( TAG , "Search error" + errorCode );
}
};
scanner .startScan ( callback );

The onScanResult method is a callback triggered in the child thread. We cannot directly operate the UI object in this method.

Secondly, scanning a Bluetooth device will trigger a message callback. We can get a BluetoothDevice object. That is to say, this method will trigger multiple callbacks.

Therefore, it is recommended that after scanning our Bluetooth device, we actively call scanner.stopScan(callback); to stop scanning.

PS: This search method will not trigger Bluetooth traversal broadcast. If we turn on the broadcast to monitor device scanning, there will be no callback in the broadcast if the startScan method is used.

The above is a general search mode, we can also configure our own filtering conditions. For example:

 ScanFilter sn = new ScanFilter . Builder (). setDeviceName ( "The name of the Bluetooth device" ). setServiceUuid ( ParcelUuid . fromString ( "Service UUID of our device" )). build ();
List < ScanFilter > scanFilters = new ArrayList <> ();
scanFilters . add ( sn );
scanner . startScan ( scanFilters , new ScanSettings . Builder () . build (), callback );

In the ScanFilter object, we can configure the information of the Bluetooth device we want to find. It can be setDeviceName, setServiceUuid, setDeviceAddress, setServiceSolicitationUuid, etc.

The ScanSettings object can define our scanning mode. By configuring this item, we can improve the scanning efficiency.

By default, the following is performed: SCAN_MODE_LOW_POWER Performs Bluetooth LE scanning in low power mode. This is the default scanning mode as it consumes the least power.

3.5.1 startDiscovery

If the above method does not meet our needs, you can use:

 if ( bluetoothAdapter . isDiscovering ()) { // Is it scanning?
bluetoothAdapter . cancelDiscovery (); //Stop scanning
}
//Search for Bluetooth
bluetoothAdapter .startDiscovery ();

We can directly use bluetoothAdapter to scan. After this method is triggered, the system will perform Bluetooth scanning, just like we click Bluetooth scanning in the settings interface of the mobile phone.

The above method has no callback because all Bluetooth device discoveries will be delivered via broadcast events.

You need to listen to the content introduced above to obtain the scanned devices in real time.

There are several disadvantages to using the above approach:

1. Slow efficiency and time-consuming.

2. Repeated scanning will fail. It cannot be said that it fails, but the system will block the request for repeated scanning. The key problem is that this blocking operation is customized by the mobile phone manufacturer.

PS: Whether you use BluetoothLeScanner​ or bluetoothAdapter.startDiscovery() to search for Bluetooth devices, it is not recommended to scan repeatedly. Otherwise, you may not be able to scan the device, or there will be no scan results. This is because scanning is a time-consuming and power-consuming operation.

3.6 Link Gatt

When we scan a Bluetooth device, we will get a BluetoothDevice object. Then we use the BluetoothDevice object to create a GATT service for subsequent Bluetooth communication.

 BluetoothDevice device ; // After we get the device object through scanning, we create the Gatt service
BluetoothGatt bluetoothGatt = device . connectGatt ( this , false , gattCallback );

There is nothing much to introduce about the first parameter context.

The second parameter autoConnect: is a boolean value object. False means directly connecting to the Bluetooth device. True means automatically connecting when the Bluetooth device is available.

The third parameter BluetoothGattCallback is the various callbacks of the Gatt service.

We use the gattCallback callback content to obtain the connection status with the Bluetooth device, data communication content, etc.

The following is a detailed introduction to several methods of the BluetoothGattCallback object.

 String SERVICE_UUID = "00000-000000-000000-000000" ; //This is the ServiceUUID of the Bluetooth device I want to connect to

BluetoothGattCallback gattCallback = new BluetoothGattCallback () {
//GATT link status callback
@Override
public void onConnectionStateChange ( BluetoothGatt gatt , int status , int newState ) {
super . onConnectionStateChange ( gatt , status , newState );
if ( newState == BluetoothProfile . STATE_CONNECTED ) {
gatt . discoverServices ();
Log . v ( TAG , "Connection successful" );
} else if ( newState == BluetoothProfile . STATE_DISCONNECTED ) {
Log . e ( TAG , "Connection disconnected" );
} else if ( newState == BluetoothProfile . STATE_CONNECTING ) {
//TODO In the actual process, this method is not called
Log . e ( TAG , "Connecting...." );
}
}
//Get the callback after GATT service discovery
@Override
public void onServicesDiscovered ( BluetoothGatt gatt , int status ) {
if ( status == BluetoothGatt . GATT_SUCCESS ) {
Log . e ( TAG , "GATT_SUCCESS" ); //Service discovery
for ( BluetoothGattService bluetoothGattService : gatt . getServices ()) {
Log . e ( TAG , "Service_UUID" + bluetoothGattService . getUuid ()); // We can traverse all Service objects of the Bluetooth device. Then by comparing the UUID of the Service, we can distinguish what business the service belongs to
if ( SERVICE_UUID . equals ( bluetoothGattService . getUuid () . toString ())) {

for ( BluetoothGattCharacteristic characteristic : bluetoothGattService . getCharacteristics ()) {
prepareBroadcastDataNotify ( gatt , characteristic ); //Configure message notification for attributes that meet the conditions
}
return ; //End the loop operation
}
}
} else {
Log . e ( TAG , "onServicesDiscovered received: " + status );
}
}

//Automatically monitor after the Bluetooth device sends a message
@Override
public void onCharacteristicChanged ( BluetoothGatt gatt , BluetoothGattCharacteristic characteristic ) {
// readUUID is the message reading UUID value of the Bluetooth device I want to connect to, and is compared with the UUID of the notification characteristic. This can avoid contamination from other messages.
if ( READ_UUID . equals ( characteristic . getUuid () . toString ())) {
try {
String chara = new String ( characteristic . getValue (), "UTF-8" );
Log . e ( TAG , "Message content: " + chara );
} catch ( UnsupportedEncodingException e ) {
e . printStackTrace ();
}
}
}
};

We can judge the current communication status with the Bluetooth device through the link success and link disconnection.

After we successfully compare the UUID of the Service, we can get the Characteristic object of the Service. This object is also the characteristic. By registering the characteristic, we can implement the monitoring and sending of messages.

3.7 Register message listener-setCharacteristicNotification

 @SuppressLint ( "MissingPermission" )
private void prepareBroadcastDataNotify ( BluetoothGatt mBluetoothGatt , BluetoothGattCharacteristic characteristic ) {
Log . e ( TAG , "CharacteristicUUID:" + characteristic . getUuid (). toString ());
int charaProp = characteristic . getProperties ();
//Judge whether the attribute supports message notification
if (( charaProp | BluetoothGattCharacteristic . PROPERTY_NOTIFY ) > 0 ) {
BluetoothGattDescriptor descriptor =
characteristic . getDescriptor ( UUID . fromString ( UUIDManager . READ_DEDSCRIPTION_UUID ));
if ( descriptor != null ) {
//Register message notification
mBluetoothGatt .setCharacteristicNotification ( characteristic , true );
descriptor . setValue ( BluetoothGattDescriptor . ENABLE_NOTIFICATION_VALUE );
mBluetoothGatt.writeDescriptor ( descriptor ) ;
}
}
}

In the example above: READ_DEDSCRIPTION_UUID = "00002902-0000-1000-8000-00805f9b34fb" is fixed, no matter what kind of bluetooth device you connect to.

When registering a message monitor, the UUID value is 00002902-0000-1000-8000-00805f9b34fb. This is reserved by the Android system and is used for dynamic monitoring.

If you don't want to use this dynamic monitoring, you need to write your own thread to actively poll and obtain the messages sent by the Bluetooth device.

At this point, we can actually monitor the Bluetooth device in real time and get the message content.

3.8 Writing data to a Bluetooth device

If we want to push content to a Bluetooth device, when discovering services, onServicesDiscovered traverses the characteristics and ensures that it is the characteristic object used to write messages. Select to hold the characteristic, and then pass:

 String data = "0x12" ;
BluetoothGattCharacteristic writeCharact = bluetoothGattService .
getCharacteristic ( UUID . fromString ( WRITE_UUID ));
//Find the UUID as a write feature and check if it has write permission
if ( writeCharact == null || writeCharact . getProperties () != BluetoothGattCharacteristic . PROPERTY_WRITE ) {
return ; //This feature does not have write permission. So it cannot be passed in
}
// After the data is passed to Bluetooth
// Will call back the write method in BluetoothGattCallback
writeCharact . setWriteType ( BluetoothGattCharacteristic . WRITE_TYPE_NO_RESPONSE );
// Convert the data to be transmitted into hexadecimal
writeCharact.setValue ( data ) ;
bluetoothGatt.writeCharacteristic (writeCharact ) ;

3.9 Closing the Connection

When the Bluetooth communication ends or the interface is closed, we need to close the GATT service to reduce resource usage.

 if ( bluetoothGatt != null ) {
bluetoothGatt . close ();
bluetoothGatt.disconnect () ;
bluetoothGatt = null ;
}

You can also turn off the BluetoothGattCallback callback monitoring:

 gattCallback . disConnectBlue (); //Disable GATT service callback monitoring

4. Summary

This is the end of Bluetooth connection and reading.

After we find the Bluetooth device through bluetoothAdapter, we pair the Bluetooth device with the phone through GATT service. We directly compare UUIDs, and no longer need PIN codes for pairing.

(PS: Some devices with higher security requirements still require active PIN pairing. PIN pairing can only be performed through the Bluetooth function item in the system device interface.)

After successfully connecting to the GATT service, you can query the various characteristics of the server. Different characteristics correspond to a function. There are characteristics for sending messages and characteristics for receiving messages.

At the same time, a Bluetooth device object may have multiple service functions.

If you don't want to write thread variables to poll messages sent by the device, you can register a message listener and let the BLE framework help you poll and then notify you.

<<:  React Native vs. Kotlin: A Quick Comparison

>>:  Google now releases the second beta version of the Android 14 system developer preview

Recommend

A First Look at Apple WatchKit

With Apple releasing the latest version of Watch ...

Top 10 Brand Marketing Trends in 2022

Last Sunday, we discussed with our friends on “Br...

What? The upcoming Lunar Year of the Rabbit has 384 days

2022 has passed, and the brand new year of 2023 h...

A total of 34,162 Range Rover Evoque/Discovery Sport vehicles were recalled

Recently, Jaguar Land Rover Automotive Trading (S...

The use of OkHttp and simple encapsulation

Preface Network programming is essential in Andro...

Do you know the first river in our country named after a poem?

Qishui soup, Gradually the carriage curtains and ...