Android Bluetooth Development Practice

Content

Previously, I wrote an article, Android Socket Programming Practice, explaining how to achieve local area network communication through ServerSocket and Socket, but the communication environment at that time was under WIFI conditions. In fact, Android has supported the Bluetooth framework since version 2.0 of the SDK, allowing devices to wirelessly exchange data with other Bluetooth devices.

Workflow

首先两个设备上都要有蓝牙设备或者专业一点叫蓝牙适配器,以手机和电脑为例我画了如下流程图。其次在手机上进行扫描,扫描周围蓝蓝牙设备,先找到手机附近的电脑,然后给它发出一个信号需要进行蓝牙的配对,再次返回一个信号说明手机和电脑已经配对成功了,最后配对成功后可以进行文件传输了。这是一个最基本的一个流程。
这里写图片描述

Bluetooth API and Common Operations

Android provides Bluetooth APIs to perform the following different operations:

API Analysis

All classes related to Bluetooth development in Android are under the android.bluetooth package, as shown in the figure below, with only 8 classes:

这里写图片描述
但是我们需要用到的就三个而已:BluetoothAdapter、BluetoothDevice、BluetoothServerSocket和BluetoothSocket,下面分别讲解。

BluetoothAdapter
As the name suggests, the Bluetooth adapter requires continuous operation of many methods in the BluetoothAdapter until we establish a bluetoothSocket connection. The commonly used ones are as follows:

  • getDefaultAdapter()—Static method to get the default BluetoothAdapter, in fact, this is the only way to obtain a BluetoothAdapter
  • getName()—Get the local Bluetooth name
  • getAddress()—Get the local Bluetooth address
  • getState()—Get the current state of the local Bluetooth adapter (this may be more needed during debugging)
  • getRemoteDevice(String address)—Get the remote Bluetooth device based on the Bluetooth address
  • startDiscovery()—Start searching, this is the first step of the search
  • cancelDiscovery()—Cancel the search, which means that when we are searching for devices, calling this method will stop the search
  • isDiscovering()—Determine whether it is currently looking for devices, return true if so
  • enable()—Turn on Bluetooth, this method will turn on Bluetooth without prompting, more often we need to ask the user whether to turn it on, the following two lines of code also turn on Bluetooth, but will prompt the user:
Intent enabler=new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enabler,reCode);
  • disable()—Turn off Bluetooth
  • isEnabled()—Check if Bluetooth is on, returns true if on, otherwise returns false
  • listenUsingRfcommWithServiceRecord(String name,UUID uuid)—Create and return BluetoothServerSocket based on name and UUID, this is the first step to create a BluetoothSocket server (for Android 2.3 and below)
  • listenUsingInsecureRfcommWithServiceRecord(String name,UUID uuid)—Create and return BluetoothServerSocket based on name and UUID (for Android 2.3 and above)
  • getScanMode()—Get the local Bluetooth scan status, enumeration values: BluetoothAdapter.SCAN\_MODE\_CONNECTABLE\_DISCOVERABLE, BluetoothAdapter.SCAN\_MODE\_CONNECTABLE, BluetoothAdapter.NONE
  • getBondedDevices()—Get information about bonded devices, return value is of type Set<\BluetoothDevice>

BluetoothDevice
As the name suggests, this class describes a Bluetooth device.

  • getState()—Get Bluetooth status. It should be noted that listening is only possible when in BluetoothAdapter.STATE _ON state.
  • createRfcommSocketToServiceRecord(UUIDuuid)—Create and return a BluetoothSocket based on UUID (for Android 2.3 and below).
  • createInsecureRfcommSocketToServiceRecord(UUIDuuid)—Create and return a BluetoothSocket based on UUID (for Android 2.3 and above). This method is also our purpose for obtaining BluetoothDevice: creating BluetoothSocket.
  • getName()—Get device Bluetooth name.
  • getAddress()—Get device Bluetooth address.
  • getBondState()—Get bond state, enumeration values: BluetoothDevice.BOND_BONDED, BluetoothDevice.BOND_BONDING, BluetoothDevice.BOND_NONE.

BluetoothServerSocket
If you remove Bluetooth, everyone should be familiar with it. Since it is a Socket, the methods should be similar. This class has only three methods:

  • accept()—Accept client connection
  • accept(int timeout)—Accept client connection and specify connection timeout. Note: (1) When executing these two methods, the thread will be blocked until a client connection request is received (or after expiration), so it should be run in a new thread!
(2) Both of these methods return a BluetoothSocket, and the final connection is also a connection between two BluetoothSockets, one on the server side and one on the client side.
  • close()—close ServerSocket

BluetoothSocket
The socket for client interaction, in contrast to BluetoothServerSocket, with a total of 5 methods:

  • connect()—connect
  • close()—close
  • getInputStream()—get input stream
  • getOutputStream()—get output stream
  • getRemoteDevice()—get remote device, here refers to getting the remote Bluetooth device specified by bluetoothSocket connection

Scan other Bluetooth devices

mBtAdapter.startDiscovery()

Get the list of scanning devices

Define BroadcastReceiver, not much to say about BroadcastReceiver, it's not the topic of today's discussion, the code is as follows

 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (BluetoothDevice.ACTION_FOUND.equals(action)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (device.getBondState() != BluetoothDevice.BOND_BONDED) { mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress()); } } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { setProgressBarIndeterminateVisibility(false); setTitle(R.string.select_device); if (mNewDevicesArrayAdapter.getCount() == 0) { String noDevices = getResources().getText(R.string.none_found).toString(); mNewDevicesArrayAdapter.add(noDevices);
                }
            }
        }
    };

In this way, whenever a new device is found or the search is completed, the corresponding operations are executed in the two if statements of the above code, but the premise is that you must first register the BroadcastReceiver, the specific code is as follows.

/** Register for broadcasts when a device is discovered*/
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
this.registerReceiver(mReceiver, filter); filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
this.registerReceiver(mReceiver, filter);

Note: You can scan other devices, provided that the other devices are currently in a scannable state. So how can you make yourself scannable?

if (mBluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); /**300S can be scanned within*/ discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
      startActivity(discoverableIntent);
}

Connect to Other Devices

We know that Socket communication is achieved through sockets to connect both ends. Bluetooth is the same, it establishes a connection between two ends through BluetoothSocket. The server side (BluetoothServerSocket) and the client side (BluetoothSocket) need to specify the same UUID in order to establish a connection, because the method to establish a connection will block the thread, so both the server side and the client side should start a new thread.

  1. Server side:
/**The UUID format is generally "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" which can be applied for at http://www.uuidgenerator.com*/
BluetoothServerSocket serverSocket = mAdapter.listenUsingInsecureRfcommWithServiceRecord(NAME_INSECURE, MY_UUID_INSECURE);
.....
socket = mmServerSocket.accept();
  1. Client:
/**Do you remember we just obtained the BluetoothDevice in BroadcastReceiver?*/
BluetoothSocket clienSocket=device.createInsecureRfcommSocketToServiceRecord(MY_UUID_INSECURE);
clienSocket.connect();

After that, both parties can communicate through the stream using the BluetoothSocket at both ends.

Development Practice

First, to operate Bluetooth, you need to add permissions in AndroidManifest.xml:

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

Main page MainActivity code:

}

Search other device interface DeviceListActivity code:

/** * This Activity appears as a dialog. It lists any paired devices and * devices detected in the area after discovery. When a device is chosen * by the user, the MAC address of the device is sent back to the parent * Activity in the result Intent. */
public class DeviceListActivity extends Activity { private static final String TAG = "DeviceListActivity"; private static final boolean D = true; public static String EXTRA_DEVICE_ADDRESS = "device_address"; private BluetoothAdapter mBtAdapter; private ArrayAdapter<String> mPairedDevicesArrayAdapter; private ArrayAdapter<String> mNewDevicesArrayAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); setContentView(R.layout.device_list); setResult(Activity.RESULT_CANCELED); Button scanButton = (Button) findViewById(R.id.button_scan); scanButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { doDiscovery(); v.setVisibility(View.GONE); } }); mPairedDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name); mNewDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name); ListView pairedListView = (ListView) findViewById(R.id.paired_devices); pairedListView.setAdapter(mPairedDevicesArrayAdapter); pairedListView.setOnItemClickListener(mDeviceClickListener); ListView newDevicesListView = (ListView) findViewById(R.id.new_devices); newDevicesListView.setAdapter(mNewDevicesArrayAdapter); newDevicesListView.setOnItemClickListener(mDeviceClickListener); IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); this.registerReceiver(mReceiver, filter); filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); this.registerReceiver(mReceiver, filter); mBtAdapter = BluetoothAdapter.getDefaultAdapter(); Set<BluetoothDevice> pairedDevices = mBtAdapter.getBondedDevices(); if (pairedDevices.size() > 0) { findViewById(R.id.title_paired_devices).setVisibility(View.VISIBLE); for (BluetoothDevice device : pairedDevices) { mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress()); } } else { String noDevices = getResources().getText(R.string.none_paired).toString(); mPairedDevicesArrayAdapter.add(noDevices); } } @Override protected void onDestroy() { super.onDestroy(); if (mBtAdapter != null) { mBtAdapter.cancelDiscovery(); } this.unregisterReceiver(mReceiver); } /** * Start device discover with the BluetoothAdapter */ private void doDiscovery() { if (D) Log.d(TAG, "doDiscovery()"); setProgressBarIndeterminateVisibility(true); setTitle(R.string.scanning); findViewById(R.id.title_new_devices).setVisibility(View.VISIBLE); if (mBtAdapter.isDiscovering()) { mBtAdapter.cancelDiscovery(); } mBtAdapter.startDiscovery(); } private OnItemClickListener mDeviceClickListener = new OnItemClickListener() { public void onItemClick(AdapterView<?> av, View v, int arg2, long arg3) { mBtAdapter.cancelDiscovery(); String info = ((TextView) v).getText().toString(); String address = info.substring(info.length() - 17); Intent intent = new Intent(); intent.putExtra(EXTRA_DEVICE_ADDRESS, address); setResult(Activity.RESULT_OK, intent); finish(); } }; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (BluetoothDevice.ACTION_FOUND.equals(action)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (device.getBondState() != BluetoothDevice.BOND_BONDED) { mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress()); } } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { setProgressBarIndeterminateVisibility(false); setTitle(R.string.select_device); if (mNewDevicesArrayAdapter.getCount() == 0) {
                    String noDevices = getResources().getText(R.string.none_found).toString();
                    mNewDevicesArrayAdapter.add(noDevices);
                }
            }
        }
    };
}

Finally, it is the service for both parties to establish connection and communication:

Summary
The article discusses Bluetooth programming in Android, highlighting its capabilities for wireless data exchange since SDK version 2.0. It outlines the basic workflow for connecting two devices, such as a phone and a computer, through Bluetooth pairing. The process involves scanning for nearby devices, initiating a pairing request, and confirming successful pairing before file transfer can occur. The article details the Bluetooth API, which is contained within the android.bluetooth package, focusing on key classes: BluetoothAdapter, BluetoothDevice, BluetoothServerSocket, and BluetoothSocket. It explains the functions of BluetoothAdapter, such as enabling/disabling Bluetooth, starting and canceling device discovery, and retrieving device information. BluetoothDevice class methods allow interaction with remote devices, while BluetoothServerSocket and BluetoothSocket facilitate server-client communication. The article also covers how to scan for devices, register a BroadcastReceiver to handle found devices, and ensure a device is discoverable. Finally, it emphasizes the importance of using the same UUID for both server and client sockets to establish a successful connection. Overall, the article serves as a practical guide for implementing Bluetooth communication in Android applications.