This is the 12th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021
In this actual project, the communication function between mobile terminal and Bluetooth 4.0 is required. Therefore, I will share my Android code with you this time. Later, I will think about uploading the framework to Github for you to exchange and learn.
1. Bluetooth initialization
Private void init_ble() {// Mobile hardware supports Bluetooth if (! GetPackageManager ().hasSystemFeature(packagemanager.feature_bluetooth_le)) {toast.maketext (this, "BLE not supported ", Toast.LENGTH_SHORT).show(); } // initializing Bluetooth adapter. // obtaining the Bluetooth adapter that holds the phone locally Final BluetoothManager BluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); mBluetoothAdapter = bluetoothManager.getAdapter(); / / open the bluetooth permissions if (mBluetoothAdapter = = null | |! mBluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent( BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); }}Copy the code
Firstly, determine whether the hardware of the mobile phone supports bluetooth low power. If so, obtain the Bluetooth adapter of the mobile phone. Then determine whether the Bluetooth permission has been enabled.
You can also override the onActivityResult method here to give an indication of bluetooth success, but I won’t write it here.
2. Implementation of simple Bluetooth Fragment
Fragments interface
Here, we use a Fragment alone to demonstrate bluetooth function. The interface is as follows:
The layout is very simple, mainly a few scan and disconnect, the bottom three buttons have nothing to do with this function, is to send commands, the middle of the ListView shows the devices that can be connected.
Ok, now we will focus on the specific program flow.
Definition of functional controls
Including the following:
Private Button btnScan; Private Button btnDiscoonect; BluetoothAdapter mBluetoothAdapter; Private ArrayList<Integer> rsSIS; MleDeviceListAdapter mleDeviceListAdapter; Private Boolean connected = false; private String status = "disconnected"; / / bluetooth eigenvalue private ArrayList < ArrayList < BluetoothGattCharacteristic > > mGattCharacteristics = new ArrayList<ArrayList<BluetoothGattCharacteristic>>(); private static BluetoothGattCharacteristic target_chara = null; // List private ListView lvDevice; Private Boolean mScanning; private boolean scan_flag; // Device information public String deviceName; public String deviceAddress; public String deviceRssi; Private static final long SCAN_PERIOD = 10000;Copy the code
Scanning bluetooth devices
case R.id.btn_scan_dev:
if (scan_flag) {
boolean gps_able = false;
mleDeviceListAdapter = new LeDeviceListAdapter();
lvDevice.setAdapter(mleDeviceListAdapter);
gps_able = isGpsEnable(getActivity());
if (gps_able == true) {
scanLeDevice(true);
} else {
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
int requestCode = 1;
this.startActivityForResult(intent, requestCode);
}
} else {
scanLeDevice(false);
btnScan.setText("Scan Device");
}
break;
Copy the code
Note: you might be confused about the GPS operation, but on Android6.0 and later, you have to add location permissions dynamically to scan bluetooth.
First, check whether the current scan has been performed with the initial flag set to true. Then initialize the adapter of the ListView and check whether the permission of the current position is enabled. If so, the scan starts.
Private void scanLeDevice(final Boolean enable) {if (enable) {/* Start scanning for Bluetooth devices, Take mLeScanCallback callback function * / Log. I (" SCAN ", "the begin..." ); mScanning = true; scan_flag = false; btnScan.setText("Stop Scanning"); mBluetoothAdapter.startLeScan(mLeScanCallback); / / TODO: VERSION / * if (android. OS. Build. VERSION. SDK_INT < 21) bluetoothAdapter. StartLeScan (this); else{ bluetoothLeScanner.startScan(callBack); }*/ } else { Log.i("Stop", "stoping................" ); mScanning = false; mBluetoothAdapter.stopLeScan(mLeScanCallback); scan_flag = true; }}Copy the code
The core calls the Bluetooth adapter’s startLeScan method or StartScan method, depending on your API version. Now we need to rewrite our LeScanCallback function to scan the Bluetooth information and add it to our ListView.
/** * Bluetooth scan callback function to scan bluetooth devices, BluetoothDevice, You can get the information such as name MAC * * / private BluetoothAdapter. LeScanCallback mLeScanCallback = new BluetoothAdapter. LeScanCallback () { @Override public void onLeScan(final BluetoothDevice device, final int rssi, byte[] scanRecord) { // TODO Auto-generated method stub getActivity().runOnUiThread(new Runnable() { @Override public Void the run () {/ * speak the scanning to the device information output to the listview adapter * / mleDeviceListAdapter addDevice (device, the rssi); mleDeviceListAdapter.notifyDataSetChanged(); }}); System.out.println("Address:" + device.getAddress()); System.out.println("Name:" + device.getName()); System.out.println("rssi:" + rssi); }};Copy the code
Note that we are running in a Fragment, and to modify the UI we need to use the runOnUiThread method above.
That completes scanning for Bluetooth devices and adding the ability to update the ListView.
Bluetooth device connection
We will connect to the device when clicking on the corresponding device Item displayed in the ListView:
/ * * / listview click function lvDevice. SetOnItemClickListener (new AdapterView. OnItemClickListener () {@ Override public void onItemClick(AdapterView<? > arg0, View v, int position, long id) { // TODO Auto-generated method stub final BluetoothDevice device = mleDeviceListAdapter .getDevice(position); Fbleutils.setdevicename (device.getName()); fbleutils.setDevicename (device.getName()); fBleUtils.setDeviceAddress(device.getAddress()); fBleUtils.setRssi(rssis.get(position).toString()); fBleUtils.setBluetoothDevice(device); fBleUtils.setBluetoothAdapter(mBluetoothAdapter); DeviceName = device.getName(); deviceAddress = device.getAddress(); deviceRssi = rssis.get(position).toString(); if (device == null) return; / / scan to a bluetooth device is true if (mScanning) {/ * * / stop scanning equipment mBluetoothAdapter. StopLeScan (mLeScanCallback); mScanning = false; Intent Intent = new Intent(getActivity(), bluetoothleservice.class); getActivity().bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE); / / registered radio receiver getActivity (). RegisterReceiver (mGattUpdateReceiver, makeGattUpdateIntentFilter ()); if (mBluetoothLeService ! = null) {/ / based on the bluetooth address, connect final Boolean result = mBluetoothLeService. Connect (deviceAddress); Log.d(TAG, "Connect request result=" + result); } } catch (Exception e) { e.printStackTrace(); // TODO: handle exception } } });Copy the code
First get the information about the device Item we clicked, then turn off the scan.
Then we need to start our Bluetooth service class (which is described in the next section), which essentially starts the Bluetooth service.
Finally, we register a broadcast receiver for the current activity according to the defined filter (receiving the message from the Bluetooth service class) and establish the connection according to the address.
/* BluetoothLeService binding callback function */ private final ServiceConnection mServiceConnection = new ServiceConnection() {@override public void onServiceConnected(ComponentName componentName, IBinder service) { mBluetoothLeService = ((BluetoothLeService.LocalBinder) service).getService(); if (! mBluetoothLeService.initialize()) { Log.e(TAG, "Unable to initialize Bluetooth"); } // Automatically connects to the device upon successful start-up // Initialization. Connected devices mBluetoothLeService. Connect (deviceAddress); } @Override public void onServiceDisconnected(ComponentName componentName) { } };Copy the code
Register broadcast receiver – Bluetooth message receiver
Specific registration 2.4 has been explained, here is the receiver content:
/** ** Private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); If (BluetoothLeService. ACTION_GATT_CONNECTED. Equals (action)) {/ / Gatt connection success mConnected = true; status = "connected"; UpdateConnectionState (status); System.out.println("BroadcastReceiver :" + "device connected"); Toast.maketext (getActivity(),"AM_Tool connected successfully!" ,Toast.LENGTH_SHORT).show(); } else if (BluetoothLeService. ACTION_GATT_DISCONNECTED / / Gatt connection. The equals (action)) {mConnected = false; status = "disconnected"; UpdateConnectionState (status); System.out.println("BroadcastReceiver :" + "device disconnected"); Toast.maketext (getActivity(),"AM_Tool connection failed!" ,Toast.LENGTH_SHORT).show(); } else if (BluetoothLeService. ACTION_GATT_SERVICES_DISCOVERED / / find the GATT server. Equals (action)) {/ / Show all the supported DisplayGattServices (mbluetooth service .getSupportedGattServices()); System.out.println("BroadcastReceiver :" + "device SERVICES_DISCOVERED"); } else if (BluetoothLeService. ACTION_DATA_AVAILABLE. Equals (action)) / / valid data {/ / processing data sent to come over displayData(intent.getExtras().getString( BluetoothLeService.EXTRA_DATA)); System.out.println("BroadcastReceiver onData:" + intent.getStringExtra(BluetoothLeService.EXTRA_DATA)); }}};Copy the code
/* Update connection status */ private void updateConnectionState(String status) {tvStatus.settext (status); if (status.equals(“connected”)){ btnDiscoonect.setEnabled(true); }else { btnDiscoonect.setEnabled(false); }}
Copy the code
Filter:
/ * intent filters * / private static IntentFilter makeGattUpdateIntentFilter () {final IntentFilter IntentFilter = new IntentFilter(); intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED); intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED); intentFilter.addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED); intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE); return intentFilter; }Copy the code
In fact, the logic is simple. According to the type of action transmitted by the Bluetooth service class intent, the corresponding action has been added through Filter.
It mainly prompts users and changes UI according to the bluetooth connection: when the Gatt connection is successful, it prompts the connection is successful and updates the TextView, and the same applies when the connection fails.
The displayGattServices method is also described below, but it should be fixed and not need to be changed if it is used.
The following displayData is an important function for us developers. It mainly obtains data packets received by Bluetooth service. In short, it is the data sent by the lower computer or terminal after protocol processing:
/** * @param @param rev_string(accepted data) * @return void * @throws * @title: displayData * @description: TODO(display the received data in two other fragments!!) */ private void displayData(final String rev_string) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { String[] direction = rev_string.split("="); If (direction [0]. Equals (" left_distance ")) {fBleUtils. SetLeftXLocation (60.0 f - Float. ParseFloat (direction [1])); fBleUtils.setLeftDistance(Float.parseFloat(direction[1])); } else if (direction [0]. Equals (" center_distance ")) {fBleUtils. SetCenterYLocation (60.0 f - Float. ParseFloat (direction [1])); fBleUtils.setCenterDistance(Float.parseFloat(direction[1])); }else if (direction[0].equals("right_distance")){ fBleUtils.setRightFloatDistance(Float.parseFloat(direction[1])); fBleUtils.setRightDistance(Float.parseFloat(direction[1])); } tvStatus.setText(rev_string); mDrawerActivity.setBleUtils(fBleUtils); btnDiscoonect.setEnabled(true); }}); }Copy the code
Here we update our corresponding data TextView and utility class with this data
Bluetooth message sending
//TODO: select off and on infrared ranging module case R.i.btn_left_operation: If (btnleftop.gettext ().equals("L_Open")) {toast.makeText (getActivity()," open the left infrared rangefiner ", toast.length_short).show(); target_chara.setValue("3"); / / call the bluetooth service write eigenvalue method to send data mBluetoothLeService. WriteCharacteristic (target_chara); btnLeftOp.setText(R.string.left_close_status); } else {toast.makeText (getActivity()," close left infrared rangefinder ", toast.length_short).show(); target_chara.setValue("2"); / / call the bluetooth service write eigenvalue method to send data mBluetoothLeService. WriteCharacteristic (target_chara); btnLeftOp.setText(R.string.left_open_status); } break;Copy the code
Here, we take a command send button as an example. First, we need to assign a value to the characteristic value target_chara, and then call the command of bluetooth service to send the characteristic value. The premise is that we have connected to the corresponding device.
Above, we have basically completed a set of basic development processes such as Bluetooth initialization – scanning and discovering Bluetooth devices – connection of Bluetooth devices – reading of Bluetooth receiving information – sending of Bluetooth information. If we only use Bluetooth, the above information and steps should meet the needs. Below, I will analyze the functions of bluetooth service in detail to help you better understand Bluetooth communication.
3. BluetoothLeService
This Bluetooth service class runs in the background, helping us to connect with Bluetooth devices and communicate with the broadcast receiver of Bluetooth Fragment to complete the function of Receiving and sending Bluetooth data.
Definition of functional controls
private final static String TAG = "BluetoothLeService"; // luetoothLeService.class.getSimpleName(); private List<Sensor> mEnabledSensors = new ArrayList<Sensor>(); Private BluetoothManager mBluetoothManager; private BluetoothAdapter mBluetoothAdapter; private String mBluetoothDeviceAddress; private BluetoothGatt mBluetoothGatt; private int mConnectionState = STATE_DISCONNECTED; private static final int STATE_DISCONNECTED = 0; private static final int STATE_CONNECTING = 1; private static final int STATE_CONNECTED = 2; public final static String ACTION_GATT_CONNECTED = "com.example.bluetooth.le.ACTION_GATT_CONNECTED"; public final static String ACTION_GATT_DISCONNECTED = "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED"; public final static String ACTION_GATT_SERVICES_DISCOVERED = "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED"; public final static String ACTION_DATA_AVAILABLE = "com.example.bluetooth.le.ACTION_DATA_AVAILABLE"; public final static String EXTRA_DATA = "com.example.bluetooth.le.EXTRA_DATA"; // public final static UUID UUID_HEART_RATE_MEASUREMENT =zzzzzzzzzzzzz // UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT); private OnDataAvailableListener mOnDataAvailableListener;Copy the code
BluetoothGatt is more important here, this class through the terminal device with a given address after the connection, through this class to bluetooth communication operations.
The Bluetooth service is initialized
This method is called from the BluetoothLeService binding callback inside BleFragment:
private final ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder service) { mBluetoothLeService = ((BluetoothLeService.LocalBinder) service).getService(); if (! mBluetoothLeService.initialize()) { Log.e(TAG, "Unable to initialize Bluetooth"); } // Automatically connects to the device upon successful start-up // Initialization. Connected devices mBluetoothLeService. Connect (deviceAddress); } @Override public void onServiceDisconnected(ComponentName componentName) { } };Copy the code
First, obtain the service, then initialize the service, and then connect the Bluetooth device according to the address. The initialization code is as follows:
Public Boolean initialize() {// For API level 18 and above, Get a reference to BluetoothAdapter // through // bluetoothmanager. if (mBluetoothManager == null) {// get a reference to BluetoothManager of the system mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); if (mBluetoothManager == null) { Log.e(TAG, "Unable to initialize BluetoothManager."); return false; } } mBluetoothAdapter = mBluetoothManager.getAdapter(); if (mBluetoothAdapter == null) { Log.e(TAG, "Unable to obtain a BluetoothAdapter."); return false; } return true; }Copy the code
The initialization here is to get the Bluetooth adapter for the current system
Bluetooth connection
In the last section, we directly used the connect method of the service class in the Fragment to connect. Here we analyze the function of the Fragment:
/ / to connect to a remote bluetooth public Boolean connect (final String address) {if (mBluetoothAdapter = = null | | address = = null) {Log w. (TAG, "BluetoothAdapter not initialized or unspecified address."); return false; } // Previously connected device. Try to reconnect. if (mBluetoothDeviceAddress ! = null && address.equals(mBluetoothDeviceAddress) && mBluetoothGatt ! = null) { Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection."); If (mConnectionState = STATE_CONNECTING)// BluetoothGatt {mConnectionState = STATE_CONNECTING; return true; } else { return false; }} / / Final BluetoothDevice device = mbluetoothAdapter.getremoteDevice (address); if (device == null) { Log.w(TAG, "Device not found. Unable to connect."); return false; } // We want to directly connect to the device, So we are setting the // autoConnect // parameter to false. /* Call connectGatt in device to connect to remote device */ mBluetoothGatt = device.connectGatt(this, false, mGattCallback); Log.d(TAG, "Trying to create a new connection."); mBluetoothDeviceAddress = address; mConnectionState = STATE_CONNECTING; System.out.println("device.getBondState==" + device.getBondState()); return true; }Copy the code
First judge whether you have got the Bluetooth adapter, and then judge whether the address is correct and whether the Gatt has been obtained (the first connection is certainly not).
Then the remote Bluetooth device is obtained through the address, and then the Gatt of the corresponding device is obtained. Therefore, the first connection obtains Gatt () and binds the callback function mGattCallback. The second connection will enter the second IF, and perform the conncet method of Gatt to connect and judge the connection result.
BluetoothGattCallback, the Gatt callback function
Private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {@override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { String intentAction; If (newState == bluetoothprofile.state_connected) {intentAction = ACTION_GATT_CONNECTED; mConnectionState = STATE_CONNECTED; /* broadcastUpdate(intentAction); Log.i(TAG, "Connected to GATT server."); // Attempts to discover services after successful connection. Log.i(TAG, "Attempting to start service discovery:" + mBluetoothGatt.discoverServices()); } else if (newState == bluetoothprofile.state_disconnected)// Connection failed {intentAction = ACTION_GATT_DISCONNECTED; mConnectionState = STATE_DISCONNECTED; Log.i(TAG, "Disconnected from GATT server."); broadcastUpdate(intentAction); }} /* * Override onServicesDiscovered, BluetoothGatt * * */ @override public void onServicesDiscovered(BluetoothGatt, BluetoothGatt, Int status) {if (status == broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED) {broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED); Log.i(TAG, "--onServicesDiscovered called--"); } else { Log.w(TAG, "onServicesDiscovered received: " + status); System.out.println("onServicesDiscovered received: " + status); }} @override public void onCharacteristicRead(BluetoothGatt, BluetoothGatt, BluetoothGattCharacteristic characteristic, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { Log.i(TAG, "--onCharacteristicRead called--"); Byte [] sucString = characteristic.getValue(); String string = new String(sucString); // Broadcast data to Ble_Fragment broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); }} /* * Override public void onCharacteristicChanged(BluetoothGatt, BluetoothGattCharacteristic characteristic) { System.out.println("++++++++++++++++"); broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); } @override public void onCharacteristicWrite(BluetoothGatt, BluetoothGatt, BluetoothGattCharacteristic characteristic, int status) { Log.w(TAG, "--onCharacteristicWrite--: " + status); Abstract Update(ACTION_DATA_AVAILABLE, characteristic); } @override public void onDescriptorRead(BluetoothGatt descriptor, BluetoothGatt descriptor, int status) { // TODO Auto-generated method stub // super.onDescriptorRead(gatt, descriptor, status); Log.w(TAG, "----onDescriptorRead status: " + status); byte[] desc = descriptor.getValue(); if (desc ! = null) { Log.w(TAG, "----onDescriptorRead value: " + new String(desc)); }} / / @override public void onDescriptorWrite(BluetoothGatt descriptor, BluetoothGatt descriptor, int status) { // TODO Auto-generated method stub // super.onDescriptorWrite(gatt, descriptor, status); Log.w(TAG, "--onDescriptorWrite--: " + status); } @override public void onReadRemoteRssi(BluetoothGatt, int rssi, BluetoothGatt, int rssi, BluetoothGatt, int rssi, int status) { // TODO Auto-generated method stub // super.onReadRemoteRssi(gatt, rssi, status); Log.w(TAG, "--onReadRemoteRssi--: " + status); broadcastUpdate(ACTION_DATA_AVAILABLE, rssi); } @Override public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { // TODO Auto-generated method stub // super.onReliableWriteCompleted(gatt, status); Log.w(TAG, "--onReliableWriteCompleted--: " + status); }};Copy the code
Broadcast status update:
Private void broadcastUpdate(final String action, int rssi) {final Intent Intent = new Intent(action); intent.putExtra(EXTRA_DATA, String.valueOf(rssi)); sendBroadcast(intent); } private void broadcastUpdate(final String action) {final Intent Intent = new Intent(action); sendBroadcast(intent); } /* broadcastUpdate(final String action, final BluetoothGattCharacteristic characteristic) { final Intent intent = new Intent(action); Final byte[] data = characteristic.getValue(); if (data ! = null && data.length > 0) { final StringBuilder stringBuilder = new StringBuilder(data.length); for (byte byteChar : data) { stringBuilder.append(String.format("%02X ", byteChar)); Log.i(TAG, "***broadcastUpdate: byteChar = " + byteChar); } intent.putExtra(EXTRA_DATA, new String(data)); System.out.println("broadcastUpdate for read data:" + new String(data)); } sendBroadcast(intent); }Copy the code
There seems to be a lot of code here, but it is mainly to respond to various states of Bluetooth terminal, and cooperate with the broadcast receiver registered in Fragment to complete the handover work between front and back.
The broadcastUpdate method stores information of different states into the EXTRA_DATA of the Intent, and then sends a broadcast that matches the previous broadcast receiver’s filter to ensure it is received.
Other methods
The service class also has several other methods. The code is relatively simple and is directly posted below:
/** * @Title: disconnect * @Description: TODO bluetooth connection (cancelled) * @ return void * @ throws * / public void disconnect () {if (mBluetoothAdapter = = null | | mBluetoothGatt = = null) { Log.w(TAG, "BluetoothAdapter not initialized"); return; } mBluetoothGatt.disconnect(); }Copy the code
/** * After using a given BLE device, the app must call this method to ensure * resources are released properly. */ /** * @Title: close * @Description: TODO(Close all Bluetooth connections) * @return void * @throws */ public void close() {if (mBluetoothGatt == null) {return; } mBluetoothGatt.close(); mBluetoothGatt = null; }Copy the code
To connect, you need to retrieve the Gatt again
/** * @Title: readCharacteristic * @Description: TODO(read characteristic values) * @param @param characteristic (read characteristic values) * @return void Return type * @throws */ public void readCharacteristic(BluetoothGattCharacteristic characteristic) { if (mBluetoothAdapter == null || mBluetoothGatt == null) { Log.w(TAG, "BluetoothAdapter not initialized"); return; } mBluetoothGatt.readCharacteristic(characteristic); } / / writer eigenvalue public void writeCharacteristic (BluetoothGattCharacteristic characteristic) {if (mBluetoothAdapter = = null || mBluetoothGatt == null) { Log.w(TAG, "BluetoothAdapter not initialized"); return; } mBluetoothGatt.writeCharacteristic(characteristic); } / / read RSSi public void readRssi () {if (mBluetoothAdapter = = null | | mBluetoothGatt = = null) {Log w. (TAG, "BluetoothAdapter not initialized"); return; } mBluetoothGatt.readRemoteRssi(); }Copy the code