【Bluetooth】Bluetooth development based on Android

Android's Bluetooth API to complete the four necessary main tasks, using Bluetooth for device communication, mainly includes four parts: Bluetooth settings, search for devices (paired or visible), connection, and data transmission .

1. Basic knowledge

1. Bluetooth API

All Bluetooth APIs are in the android.bluetooth package. The following classes and interfaces are mainly required to realize these functions:

  • BluetoothAdapter
Represents the local Bluetooth adapter (Bluetooth transmitter) and is the entry point for all Bluetooth interactions. Through it, you can search for other Bluetooth devices, query the list of paired devices, create a BluetoothDevice through a known MAC address, and create a BluetoothServerSocket to monitor communications from other devices.

  • BluetoothDevice
Represents a remote Bluetooth device, use it to request the connection of the remote Bluetooth device or obtain the name, address, type and binding status of the remote Bluetooth device. (The information is encapsulated in bluetoothsocket).

  • BluetoothSocket
Represents the interface of a bluetooth socket (similar to the socket in tcp), which is the connection point for the application to communicate with other bluetooth devices through input and output streams.

  • BluetoothServerSocket
It means to open a service connection to monitor possible incoming connection requests (belonging to the server side). In order to connect two Bluetooth devices, one device must be used as a server to open a service socket. When the remote device initiates a connection request and is already connected, the Blueboothserversocket class will return a bluetoothsocket.

  • BluetoothClass
Describes the characteristics (profile) of a device or roughly which services (services) Bluetooth on the device can provide, but it is not trusted. For example, the device is a phone, computer or handheld device; the device can provide audio/telephony services, etc. It can be used for some UI prompts.

  • BluetoothProfile
  • BluetoothHeadset
Provides support for mobile phones using Bluetooth headsets . This includes both Bluetooth headset and hands-free (V1.5) models.

  • BluetoothA2dp
Defines high-quality audio that can be streamed from one device to another over a Bluetooth connection . "A2DP" stands for Advanced Audio Distribution Profile.

  • BluetoothHealth
Represents a Bluetooth service controlled by a medical device configuration agent

  • BluetoothHealthCallback
An abstract class that implements BluetoothHealth callbacks. You must extend this class and implement callback methods to receive updates on application registration status and Bluetooth channel status changes.

  • BluetoothHealthAppConfiguration
Representing the configuration of an application, a Bluetooth medical third-party application registers to communicate with a remote Bluetooth medical device.

  • BluetoothProfile.ServiceListener
An interface that notifies BluetoothProfile IPX clients when they have connected to or disconnected from a service (i.e. internal services running a specific profile).

2. Bluetooth permission

In order to use the Bluetooth function in your application, at least two permissions must be declared in AndroidManifest.xml : BLUETOOTH (any Bluetooth-related API must use this permission ) and BLUETOOTH_ADMIN (device search, Bluetooth settings, etc.).

In order to perform Bluetooth communication, such as connection requests, BLUETOOTH authorization is required to receive connections and transmit data.

The BLUETOOTH_ADMIN permission must be requested to initiate device discovery or manipulate Bluetooth settings. Most applications require this permission to be able to discover local bluetooth devices. Other capabilities granted by this permission should not be used unless the application is a "power manager" that will modify Bluetooth settings at user request.

Note: To request BLUETOOTH_ADMIN, you must have BLUETOOTH first.

2. Bluetooth settings

Before your application can communicate via Bluetooth, you need to verify that the device supports Bluetooth, and if so, make sure it is turned on .

If not supported, the Bluetooth function cannot be used. If Bluetooth is supported, but not available, you just requested the use of Bluetooth in your application. This is done in two steps, using the BluetoothAdapter .

1. Get the BluetoothAdapter

All Bluetooth activities request a BluetoothAdapter. To get a BluetoothAdapter, call the static method getDefaultAdapter(). This returns a BluetoothAdapter representing the device's own Bluetooth adapter (Bluetooth radio). This Bluetooth adapter is used throughout the system, and your application can interact with this object. If getDefaultAdapter() returns null, the device does not support Bluetooth. For example:

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
// Device does not support Bluetooth
}

2. Turn on Bluetooth

Secondly. You need to make sure bluetooth is working. Use isEnabled() to check whether Bluetooth is currently available. If this method returns false, Bluetooth cannot be used. To request Bluetooth usage, call startActivityForResult () with an ACTION_REQUEST_ENABLE action intent. Enabling Bluetooth via System Settings will issue a request (without stopping the Bluetooth application ) . For example:

if (mBluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

The REQUEST_ENABLE_BT constant is passed to startActivityForResult() as an integer (the value must be greater than 0), and the system passes it back to you as the requestCode parameter implemented in your onActivityResult().

If the call to Bluetooth is successful, your Activity will receive a RESULT_OK result in onActivityResult(). If the Bluetooth cannot be used due to an error (or the user responded with "NO") then the result returns RESULT_CANCELED.

In addition to onActivityResult(), you can also know whether the Bluetooth status has changed by listening to the broadcast Intent of ACTION_STATE_CHANGED . This Intent contains two fields EXTRA_STATE and EXTRA_PREVIOUS_STATE, representing the old and new states respectively. Possible values ​​are STATE_TURNING_ON, STATE_ON , STATE_TURNING_OFF, and STATE_OFF.

3. Search equipment

Use the BluetoothAdapter to find remote Bluetooth devices by searching for new devices or querying for paired devices .

Device discovery is the process of scanning for local Bluetooth-enabled devices and requesting some information from the discovered devices (sometimes receiving something like "discovering", "inquiring" or "scanning"). However, the searched local Bluetooth device will respond to a discovery request only after the discovery function is turned on, and the response information includes the device name, class, and unique MAC address. The initiating device can use this information to initiate a connection with the discovered device.
Once the first connection with the remote device is established, a pairing request is automatically submitted to the user. If the device is paired, basic information about the paired device (name, class, MAC address) is saved and can be read using the Bluetooth API. Using the known MAC address of the remote device, a connection can be initiated at any time without having to complete a search first (this assumes, of course, that the remote device is within reach).

It is important to remember that pairing and connection are two different concepts:

  • Pairing means that two devices are aware of each other's existence, share a link key (link-key) used to identify their identity, and can establish an encrypted connection with each other.
  • Connected means that the two devices now share an RFCOMM channel and are able to transfer data to each other.

Currently the Android Bluetooth API's require devices to be paired before establishing an RFCOMM channel (pairing is done automatically when an encrypted connection is initiated using the Bluetooth API).

The following describes how to query paired devices and search for new devices .

Note: Android power devices are not discoverable by default. Users can make it discoverable for a limited time through system settings, or can require users to enable the discovery function in the application.

1. Get the list of paired devices

Before searching for a device, it is worth querying the paired devices to see if the desired device already exists. This can be done by calling getBondedDevices (), which returns a result set describing the paired device BluetoothDevice. For example, an ArrayAdapter can be used to query all paired devices and display all device names to the user:

Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
// If there are paired devices
if (pairedDevices.size() > 0) {
    // Loop through paired devices
    for (BluetoothDevice device : pairedDevices) {
        // Add the name and address to an array adapter to show in a ListView
        mArrayAdapter.add(device.getName() + "n" + device.getAddress());
    }
};
The only information needed in a BluetoothDevice object to initiate a connection is the MAC address.

2. Scan devices

To start discovering devices, simply call startDiscovery (). This function is asynchronous and returns immediately after calling, and the return value indicates whether the search started successfully. The discovery process typically consists of a 12-second query scan followed by a page displaying the Bluetooth name of the device found.

The application can register a BroadcastReceiver with ACTION_FOUND Intent, and receive the message when each device is found. For each device, the system broadcasts ACTION_FOUND Intent, which carries extra field information EXTRA_DEVICE and EXTRA_CLASS, which contain a BluetoothDevice and a BluetoothClass respectively.

The following example shows how to register and handle the broadcast sent after a device is discovered:

code show as below:

// Create a BroadcastReceiver for ACTION_FOUND
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        // When discovery finds a device
        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
            // Get the BluetoothDevice object from the Intent
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            // Add the name and address to an array adapter to show in a ListView
            mArrayAdapter.add(device.getName() + "n" + device.getAddress());
        }
    }
};
// Register the BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy

Note: Completing device discovery is a heavyweight process for the Bluetooth adapter, consuming a lot of its resources. Once you have found a device to connect to, make sure you stop the discovery with cancelDiscovery() before attempting to connect. Also, if a connection is already maintained, performing a simultaneous discovery of devices will significantly reduce the bandwidth of the connection, so discovery should not be performed while a connection is in progress.

3 enable to be found

If you want the local device to be discovered by other devices, you can call the startActivityForResult(Intent, int) method with ACTION_REQUEST_DISCOVERABLE action Intent. This method submits a request to put the device in discoverable mode (without affecting the application) just set by the system. By default, devices become discoverable after 120 seconds. You can customize a value by additionally adding EXTRA_DISCOVERABLE_DURATION Intent, the maximum value is 3600 seconds, and 0 means that the device can always be discovered (if it is less than 0 or greater than 3600, it will be automatically set to 120 seconds). The following example sets the time to 300:

Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);

The device will remain in discoverable mode quietly for a specified period of time. If you want to be notified when the discoverable mode is changed, you can register a BroadcastReceiver with ACTION_SCAN_MODE_CHANGED Intent, which contains additional field information EXTRA_SCAN_MODE and EXTRA_PREVIOUS_SCAN_MODE to indicate the new and old scan modes respectively, and its possible values ​​​​are SCAN_MODE_CONNECTABLE_DISCOVERABLE (discoverable mode), SCAN_MODE_CONNECTAB LE (not in discoverable mode but still able to receive connections), SCAN_MODE_NONE (not in discoverable mode and unable to receive connections).
If you only need to connect to a remote device, you don't need to enable the discoverable function of the device. Only enable discoverability if the application hosts a server socket for incoming connections, because the remote device must first discover your device before initiating a connection.

4. Connect the device

In order to create a connection between two devices, you have to implement the server-side and client-side mechanisms in software, because one device must have to open a server socket, and the other has to initiate the connection (using the MAC address of the server-side device for initialization) ).
When both the server and the client have a BluetoothSocket on the same RFCOMM channel, a connection can be considered established between them. At this moment, each device can get an output stream and an input stream, and can start data transmission. This section describes how to initiate a connection between two devices.
The method of obtaining the BluetoothSocket between the server and the client is different. The server generates a BluetoothSocket when an incoming connection is accepted, and the client obtains the BluetoothSocket when opening an RFCOMM channel to the server.

  • One implementation technique is that each device automatically acts as a server, so each device has a server socket and listens for connections. Each device can then establish a connection to another device as a client.
  • Another alternative is that one device opens a server socket on demand, and the other device only initiates a connection to this device.

Note:  If the two devices are not paired before establishing a connection, the Android framework will automatically display a pairing request notification or a dialog box during the connection establishment process. Therefore, your application does not need to ensure that the devices are paired when attempting to connect to them. Your RFCOMM connection will continue after the user confirms the pairing, or fail if the user refuses or times out.

1. Connect as a server

If two devices are to be connected, one of them must act as a server, by holding an open BluetoothServerSocket object. The role of the server socket is to listen for incoming connections, and if a connection is accepted, provide a connected BluetoothSocket object. After obtaining the BluetoothSocket object from the BluetoothServerSocket, the BluetoothServerSocket can (and should) be discarded, unless you want to use it to receive more connections.

Here are the basic steps to establish a server socket and receive a connection:

1) Obtain a BluetoothServerSocket object by calling listenUsingRfcommWithServiceRecord(String, UUID) .

The string is the service identification name, and the system will automatically write it into a new Service Discovery Protocol (SDP) database entry on the device (the name is arbitrary, it can simply be the name of the application program). The UUID is also included in the SDP entry and will be the basis for the client device connection protocol. That is to say, when the client tries to connect to the device, it will carry a UUID to uniquely identify the service it wants to connect to. The UUID must match before the connection will be accepted.

2) Listen for connection requests by calling accept() .

This is a blocking call, and it will not return until a connection comes in or an exception occurs. A connection request will only be accepted if the remote device sends a connection request with a UUID that matches the UUID registered for the listening socket. If successful, accept() will return a connected BluetoothSocket object.

3) Unless you need to receive another connection, call close()).

close() releases the server socket and its resources, but does not close the connected BluetoothSocket object returned by accept(). Different from TCP/IP, RFCOMM only allows one client to connect to one channel at the same time, so in most cases it means that close() should be called immediately after BluetoothServerSocket accepts a connection request.

The accept() call should not be made on the main Activity UI thread, because it is a blocking call that prevents other interactions. It is often done in a new thread to do all the work of BluetoothServerSocket or BluetoothSocket to avoid blocking the UI thread. Note that all BluetoothServerSocket or BluetoothSocket methods are thread-safe.

Here is a sample code for a simple server component that accepts connections:

private class AcceptThread extends Thread {
    private final BluetoothServerSocket mmServerSocket;
    public AcceptThread() {
        // Use a temporary object that is later assigned to mmServerSocket,
        // because mmServerSocket is final
        BluetoothServerSocket tmp = null;
        try {
            // MY_UUID is the app's UUID string, also used by the client code
            tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
        } catch (IOException e) { }
        mmServerSocket = tmp;
    }
    public void run() {
        BluetoothSocket socket = null;
        // Keep listening until exception occurs or a socket is returned
        while (true) {
            try {
                socket = mmServerSocket.accept();
            } catch (IOException e) {
                break;
            }
            // If a connection was accepted
            if (socket != null) {
                // Do work to manage the connection (in a separate thread)
                manageConnectedSocket(socket);
                mmServerSocket.close();
                break;
            }
        }
    }
    /* *  Will cancel the listening socket, and cause the thread to finish * /
    public void cancel() {
        try {
            mmServerSocket.close();
        } catch (IOException e) { }
    }
}

In this example, only one incoming connection is accepted, and once the connection is accepted, the BluetoothSocket is acquired, the BluetoothSocket is sent to a separate thread, and the BluetoothServerSocket is closed and the loop is broken.

Note: After accept() returns BluetoothSocket, the socket is already connected, so the client should not call connect().

manageConnectedSocket() is a virtual method used to initialize the thread to transfer data.

Normally a BluetoothServerSocket should be closed immediately after handling an overheard connection. In this example, close() is called immediately after getting the BluetoothSocket. It is also necessary to provide a public method in the thread to close the private BluetoothSocket and stop the listening of the server socket.

2. Connect as a client

In order to realize the connection with the remote device, you must first obtain a BluetoothDevice object representing the remote device. Then use the BluetoothDevice object to get a BluetoothSocket to connect.

Here are the basic steps:

1) Use BluetoothDevice to call createRfcommSocketToServiceRecord(UUID) to obtain a BluetoothSocket object.
This initialized BluetoothSocket will connect to the BluetoothDevice. The UUID must match the UUID used by the server device when opening the BluetoothServerSocket (using java.util.UUID) listenUsingRfcommWithServiceRecord(String, UUID)). You can simply generate a UUID string and use that UUID on both the server and the client.

2) Call connect() to complete the connection
When this method is called, the system will complete an SDP lookup on the remote device to match the UUID. If the lookup is successful and the remote device accepts the connection, the RFCOMM channel is shared and connect() returns. This is also a blocking call, and an exception will be thrown whether the connection fails or times out (12 seconds).

Note: Make sure that you are not doing a device lookup while calling connect(). If you are doing a device lookup, the connection attempt will be significantly slower, as slow as failing.


Here's a sample thread that completes a Bluetooth connection:

private class ConnectThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final BluetoothDevice mmDevice;
    public ConnectThread(BluetoothDevice device) {
        // Use a temporary object that is later assigned to mmSocket,
        // because mmSocket is final
        BluetoothSocket tmp = null;
        mmDevice = device;
        // Get a BluetoothSocket to connect with the given BluetoothDevice
        try {
            // MY_UUID is the app's UUID string, also used by the server code
            tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
        } catch (IOException e) { }
        mmSocket = tmp;
    }
    public void run() {
        // Cancel discovery because it will slow down the connection
        mBluetoothAdapter.cancelDiscovery();
        try {
            // Connect the device through the socket. This will block
            // until it succeeds or throws an exception
            mmSocket.connect();
        } catch (IOException connectException) {
            // Unable to connect; close the socket and get out
            try {
                mmSocket.close();
            } catch (IOException closeException) { }
            return;
        }
        // Do work to manage the connection (in a separate thread)
        manageConnectedSocket(mmSocket);
    }
    /* *  Will cancel an in-progress connection, and close the socket * /
    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) { }
    }
}

Note  : to cancelDiscovery()) is called before the connect operation. Before connecting, no matter whether the search is performed or not, the call is safe and does not require confirmation (of course, if there is a need for confirmation, you can call isDiscovering())).
manageConnectedSocket() is a virtual method used to initialize the thread to transfer data.
After processing the BluetoothSocket, remember to call close() to close the connected socket and clean up all internal resources.

5. Management connection

If two devices are already connected, they both already have their own connected BluetoothSocket objects. That's where the fun starts, because you can share data between devices. Using BluetoothSocket, transferring any data is usually easy:

1) Obtain the input and output streams through the socket to handle the transmission (using getInputStream() and getOutputStream() respectively  ).

2) Use read(byte[]) and write(byte[]) to read and write.

That's all.

Of course, there are still many details to consider. First of all, you need to use a dedicated thread to read and write streams. It's only important because both read(byte[]) and write(byte[]) are blocking calls. read(byte[]) blocks until there is data to read in the stream. write(byte[]) usually does not block, but it may block if the remote device does not call read(byte[]) fast enough to cause the intermediate buffer to fill. So the main loop in the thread should be used to read the InputStream. There should also be a separate method in the thread to finish writing the OutputStream.

Here is an example as described above:

private class ConnectedThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final InputStream mmInStream;
    private final OutputStream mmOutStream;
    public ConnectedThread(BluetoothSocket socket) {
        mmSocket = socket;
        InputStream tmpIn = null;
        OutputStream tmpOut = null;
        // Get the input and output streams, using temp objects because
        // member streams are final
        try {
            tmpIn = socket.getInputStream();
            tmpOut = socket.getOutputStream();
        } catch (IOException e) { }
        mmInStream = tmpIn;
        mmOutStream = tmpOut;
    }
    public void run() {
        byte[] buffer = new byte[1024];  // buffer store for the stream
        int bytes; // bytes returned from read()
        // Keep listening to the InputStream until an exception occurs
        while (true) {
            try {
                // Read from the InputStream
                bytes = mmInStream.read(buffer);
                // Send the obtained bytes to the UI activity
                mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
                        .sendToTarget();
            } catch (IOException e) {
                break;
            }
        }
    }
    /*  Call this from the main activity to send data to the remote device * /
    public void write(byte[] bytes) {
        try {
            mmOutStream.write(bytes);
        } catch (IOException e) { }
    }
    /*  Call this from the main activity to shutdown the connection * /
    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) { }
    }
}

The required stream is obtained in the constructor. Once executed, the thread will wait for the data from the InputStream. When read(byte[]) returns the bytes read from the stream, the data is sent to the main Activity through the member Handler of the parent class, and then continues to wait for the data in the read stream.
Sending data out simply calls the thread's write() method.
It is important to call the thread's cancel() method so that the connection can be terminated at any time by closing the BluetoothSocket. It should always be called after processing a Bluetooth connection.

Six, use the configuration file

Starting with Android 3.0, the Bluetooth API includes support for Bluetooth profiles. The Bluetooth profile is a wireless interface specification for communication between Bluetooth-based devices. The Android Bluetooth API implements the following Bluetooth profile :

  • Headset: The Headset profile provides support for Bluetooth headsets on mobile phones. Android provides the BluetoothHeadset class, which is a protocol used to control the Bluetooth Headset Service through IPC (interprocess communication). BluetoothHeadset contains both Bluetooth Headset profile and Hands-Free profile, and also includes support for AT commands.
  • A2DP: Advanced Audio Distribution Profile (A2DP) profile, advanced audio transmission mode. Android provides the BluetoothA2dp class, which is a protocol to control Bluetooth A2DP through IPC.
  • HDP: Android 4.0 (API level 14) introduced support for Bluetooth Medical Device Profile (HDP), which allows you to create Bluetooth-enabled medical devices, applications that use Bluetooth communication, such as heart rate monitors, blood, thermometers and scales, etc. wait.

 The following are the basic steps to use a profile:

  1. Get the default Bluetooth adapter.
  2. Use getProfileProxy() to establish a connection to a profile protocol object associated with a profile. In the example below, the profile protocol object is an instance of BluetoothHeadset.
  3. Set the BluetoothProfile. ServiceListener. The listener notifies the BluetoothProfile IPC client when the client connects or disconnects from the server
  4. In android.bluetooth.BluetoothProfile) onServiceConnected(), get the handle of a profile protocol object.
  5. Once you have a profile protocol object, you can use it to monitor the connection status and complete other operations related to the profile.

   For example, the following code snippet shows how to connect to a BluetoothHeadset protocol object to control the Headset profile:

BluetoothHeadset mBluetoothHeadset;
// Get the default adapter
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// Establish connection to the proxy.
mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);
private BluetoothProfile.ServiceListener mProfileListener = new BluetoothProfile.ServiceListener() {
    public void onServiceConnected(int profile, BluetoothProfile proxy) {
        if (profile == BluetoothProfile.HEADSET) {
            mBluetoothHeadset = (BluetoothHeadset) proxy;
        }
    }
    public void onServiceDisconnected(int profile) {
        if (profile == BluetoothProfile.HEADSET) {
            mBluetoothHeadset = null;
        }
    }
};
// ... call functions on mBluetoothHeadset
// Close proxy connection after use.
mBluetoothAdapter.closeProfileProxy(mBluetoothHeadset)

Guess you like

Origin blog.csdn.net/cocoduco/article/details/124921440