文章目录
Bluetooth overview
The Android platform includes support for the Bluetooth network stack, which allows a device to wirelessly exchange data with other Bluetooth devices. The application framework provides access to the Bluetooth functionality through the Android Bluetooth APIs. These APIs let applications wirelessly connect to other Bluetooth devices, enabling point-to-point and multipoint wireless features.
Using the Bluetooth APIs, an Android application can perform the following:
- Scan for other Bluetooth devices
- Query the local Bluetooth adapter for paired Bluetooth devices
- Establish RFCOMM channels
- Connect to other devices through service discovery
- Transfer data to and from other devices
- Manage multiple connections
This page focuses on Classic Bluetooth. Classic Bluetooth is the right choice for more battery-intensive operations, which include streaming and communicating between Android devices. For Bluetooth devices with low power requirements, Android 4.3 (API level 18) introduces API support for Bluetooth Low Energy. To learn more, see Bluetooth Low Energy.
This document describes different Bluetooth profiles, including the Health Device Profile. It then explains how to use the Android Bluetooth APIs to accomplish the four major tasks necessary to communicate using Bluetooth: setting up Bluetooth, finding devices that are either paired or available in the local area, connecting devices, and transferring data between devices.
The basics
In order for Bluetooth-enabled devices to transmit data between each other, they must first form a channel of communication using a pairing process. One device, a discoverable device, makes itself available for incoming connection requests. Another device finds the discoverable device using a service discovery process. After the discoverable device accepts the pairing request, the two devices complete a bonding process where they exchange security keys. The devices cache these keys for later use. After the pairing and bonding processes are complete, the two devices exchange information. When the session is complete, the device that initiated the pairing request releases the channel that had linked it to the discoverable device. The two devices remain bonded, however, so they can reconnect automatically during a future session as long as they’re in range of each other and neither device has removed the bond.
Bluetooth permissions
In order to use Bluetooth features in your application, you must declare two permissions. The first of these is BLUETOOTH
. You need this permission to perform any Bluetooth communication, such as requesting a connection, accepting a connection, and transferring data.
The other permission that you must declare is ACCESS_FINE_LOCATION
. Your app needs this permission because a Bluetooth scan can be used to gather information about the location of the user. This information may come from the user’s own devices, as well as Bluetooth beacons in use at locations such as shops and transit facilities.
Services running on Android 10 and higher cannot discover Bluetooth devices unless they have the ACCESS_BACKGROUND_LOCATION
permission. For more information on this requirement, see Access location in the background.
The following code snippet shows how to check for the permission.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (ContextCompat.checkSelfPermission(baseContext,
Manifest.permission.ACCESS_BACKGROUND_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
MyActivity.this,
new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION},
PERMISSION_CODE)
}
}
An exception to this permission requirement is when your app is installed on a device running Android 11 or higher and has used companion device pairing to associate a device. In this case, once a device is associated, apps can scan for their associated Bluetooth devices without requiring a location permission.
On devices running Android 8.0 (API level 26) and higher, you can use the CompanionDeviceManager
to perform a scan of nearby companion devices on behalf of your app without requiring the location permission. For more on this option, see Companion device pairing.
Note: If your app targets Android 9 (API level 28) or lower, you can declare the
ACCESS_COARSE_LOCATION
permission instead of theACCESS_FINE_LOCATION
permission.
If you want your app to initiate device discovery or manipulate Bluetooth settings, you must declare the BLUETOOTH_ADMIN
permission in addition to the BLUETOOTH
permission. Most applications need this permission solely for the ability to discover local Bluetooth devices. The other abilities granted by this permission should not be used, unless the application is a “power manager” that modifies Bluetooth settings upon user request.
Declare the Bluetooth permission(s) in your application manifest file. For example:
<manifest ... >
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- If your app targets Android 9 or lower, you can declare
ACCESS_COARSE_LOCATION instead. -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
...
</manifest>
See the reference for more information about declaring application permissions.
Set up bluetooth
Before your application can communicate over Bluetooth, you need to verify that Bluetooth is supported on the device, and if so, ensure that it is enabled.
If Bluetooth isn’t supported, then you should gracefully disable any Bluetooth features. If Bluetooth is supported, but disabled, then you can request that the user enable Bluetooth without leaving your application. This setup is accomplished in two steps, using the BluetoothAdapter
:
-
Get the
BluetoothAdapter
.The
BluetoothAdapter
is required for any and all Bluetooth activity. To get theBluetoothAdapter
, call the staticgetDefaultAdapter()
method. This returns aBluetoothAdapter
that represents the device’s own Bluetooth adapter (the Bluetooth radio). There’s one Bluetooth adapter for the entire system, and your application can interact with it using this object. IfgetDefaultAdapter()
returnsnull
, then the device doesn’t support Bluetooth. For example:BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (bluetoothAdapter == null) { // Device doesn't support Bluetooth }
-
Enable Bluetooth.
Next, you need to ensure that Bluetooth is enabled. Call
isEnabled()
to check whether Bluetooth is currently enabled. If this method returns false, then Bluetooth is disabled. To request that Bluetooth be enabled, callstartActivityForResult()
, passing in anACTION_REQUEST_ENABLE
intent action. This call issues a request to enable Bluetooth through the system settings (without stopping your application). For example:if (!bluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); }
A dialog appears requesting user permission to enable Bluetooth, as shown in Figure 1. If the user responds “Yes”, the system begins to enable Bluetooth, and focus returns to your application once the process completes (or fails).
Figure 1: The enabling Bluetooth dialog.
The
REQUEST_ENABLE_BT
constant passed tostartActivityForResult()
is a locally defined integer that must be greater than 0. The system passes this constant back to you in youronActivityResult()
implementation as therequestCode
parameter.If enabling Bluetooth succeeds, your activity receives the
RESULT_OK
result code in theonActivityResult()
callback. If Bluetooth was not enabled due to an error (or the user responded “No”) then the result code isRESULT_CANCELED
.
Optionally, your application can also listen for the ACTION_STATE_CHANGED
broadcast intent, which the system broadcasts whenever the Bluetooth state changes. This broadcast contains the extra fields EXTRA_STATE
and EXTRA_PREVIOUS_STATE
, containing the new and old Bluetooth states, respectively. Possible values for these extra fields are STATE_TURNING_ON
, STATE_ON
, STATE_TURNING_OFF
, and STATE_OFF
. Listening for this broadcast can be useful if your app needs to detect runtime changes made to the Bluetooth state.
Tip: Enabling discoverability automatically enables Bluetooth. If you plan to consistently enable device discoverability before performing Bluetooth activity, you can skip step 2 above. For more information, read the enabling discoverability, section on this page.
Find devices
Using the BluetoothAdapter
, you can find remote Bluetooth devices either through device discovery or by querying the list of paired devices.
Device discovery is a scanning procedure that searches the local area for Bluetooth-enabled devices and requests some information about each one. This process is sometimes referred to as discovering, inquiring, or scanning. However, a nearby Bluetooth device responds to a discovery request only if it is currently accepting information requests by being discoverable. If a device is discoverable, it responds to the discovery request by sharing some information, such as the device’s name, its class, and its unique MAC address. Using this information, the device that is performing the discovery process can then choose to initiate a connection to the discovered device.
Because discoverable devices might reveal information about the user’s location, the device discovery process requires location access. If your app is being used on a device that runs Android 8.0 (API level 26) or higher, use the Companion Device Manager API. This API performs device discovery on your app’s behalf, so your app doesn’t need to request location permissions.
Once a connection is made with a remote device for the first time, a pairing request is automatically presented to the user. When a device is paired, the basic information about that device—such as the device’s name, class, and MAC address—is saved and can be read using the Bluetooth APIs. Using the known MAC address for a remote device, a connection can be initiated with it at any time without performing discovery, assuming the device is still within range.
Note that there is a difference between being paired and being connected:
- To be paired means that two devices are aware of each other’s existence, have a shared link-key that can be used for authentication, and are capable of establishing an encrypted connection with each other.
- To be connected means that the devices currently share an RFCOMM channel and are able to transmit data with each other. The current Android Bluetooth API’s require devices to be paired before an RFCOMM connection can be established. Pairing is automatically performed when you initiate an encrypted connection with the Bluetooth APIs.
The following sections describe how to find devices that have been paired, or discover new devices using device discovery.
Note: Android-powered devices are not discoverable by default. A user can make the device discoverable for a limited time through the system settings, or an application can request that the user enable discoverability without leaving the application. For more information, see the enable discoverability section on this page.
Query paired devices
Before performing device discovery, it’s worth querying the set of paired devices to see if the desired device is already known. To do so, call getBondedDevices()
. This returns a set of BluetoothDevice
objects representing paired devices. For example, you can query all paired devices and get the name and MAC address of each device, as the following code snippet demonstrates:
Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
if (pairedDevices.size() > 0) {
// There are paired devices. Get the name and address of each paired device.
for (BluetoothDevice device : pairedDevices) {
String deviceName = device.getName();
String deviceHardwareAddress = device.getAddress(); // MAC address
}
}
To initiate a connection with a Bluetooth device, all that’s needed from the associated BluetoothDevice
object is the MAC address, which you retrieve by calling getAddress()
. You can learn more about creating a connection in the section about Connecting Devices.
Caution: Performing device discovery consumes a lot of the Bluetooth adapter’s resources. After you have found a device to connect to, be certain that you stop discovery with
cancelDiscovery()
before attempting a connection. Also, you shouldn’t perform discovery while connected to a device because the discovery process significantly reduces the bandwidth available for any existing connections.
Discover devices
To start discovering devices, simply call startDiscovery()
. The process is asynchronous and returns a boolean value indicating whether discovery has successfully started. The discovery process usually involves an inquiry scan of about 12 seconds, followed by a page scan of each device found to retrieve its Bluetooth name.
In order to receive information about each device discovered, your application must register a BroadcastReceiver for the ACTION_FOUND
intent. The system broadcasts this intent for each device. The intent contains the extra fields EXTRA_DEVICE
and EXTRA_CLASS
, which in turn contain a BluetoothDevice
and a BluetoothClass
, respectively. The following code snippet shows how you can register to handle the broadcast when devices are discovered:
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// Register for broadcasts when a device is discovered.
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(receiver, filter);
}
// Create a BroadcastReceiver for ACTION_FOUND.
private final BroadcastReceiver receiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// Discovery has found a device. Get the BluetoothDevice
// object and its info from the Intent.
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
String deviceName = device.getName();
String deviceHardwareAddress = device.getAddress(); // MAC address
}
}
};
@Override
protected void onDestroy() {
super.onDestroy();
...
// Don't forget to unregister the ACTION_FOUND receiver.
unregisterReceiver(receiver);
}
To initiate a connection with a Bluetooth device, all that’s needed from the associated BluetoothDevice
object is the MAC address, which you retrieve by calling getAddress()
. You can learn more about creating a connection in the section about Connecting Devices.
Caution: Performing device discovery consumes a lot of the Bluetooth adapter’s resources. After you have found a device to connect to, be certain that you stop discovery with
cancelDiscovery()
before attempting a connection. Also, you shouldn’t perform discovery while connected to a device because the discovery process significantly reduces the bandwidth available for any existing connections.
准备
IDE:
Android Studio 4.1.1
Build #AI-201.8743.12.41.6953283, built on November 5, 2020
Runtime version: 1.8.0_242-release-1644-b01 amd64
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
Windows 10 10.0
关于手机
查询已配对的设备
新建项目,选择 Empty Activity,在配置项目时,Minimum SDK
选择 API 16: Android 4.1 (Jelly Bean)
。
编辑 src\main\AndroidManifest.xml
应用清单文件:
- 声明 android.permission.BLUETOOTH 权限(第 5 行);
- 修改应用主题(第 9 行)。
<?xml version="1.0" encoding="utf-8"?>
<manifest ...>
<!-- Allows applications to connect to paired bluetooth devices. -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<application
...
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
...
</application>
</manifest>
新建 src\main\res\layout\recycler_view_bonded_bluetooth_device.xml
布局文件(RecyclerView 列表子项),用于显示已配对的设备:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FFFFFF">
<TextView
android:id="@+id/textViewName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingLeft="10dp"
android:paddingRight="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textViewAddress"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingLeft="10dp"
android:paddingRight="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textViewName" />
<FrameLayout
android:id="@+id/frameLayout"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
新建 RecyclerViewAdapter
适配器类:
package com.mk;
import android.bluetooth.BluetoothDevice;
import android.graphics.Rect;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {
private List<BluetoothDevice> dataSet;
private ViewHolder.OnClickListener listener;
public RecyclerViewAdapter(List<BluetoothDevice> dataSet) {
this.dataSet = dataSet;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recycler_view_bonded_bluetooth_device, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.binding(dataSet.get(position));
holder.setOnClickListener(listener);
}
@Override
public int getItemCount() {
return dataSet.size();
}
public void setItemOnClickListener(ViewHolder.OnClickListener listener) {
this.listener = listener;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
private BluetoothDevice bluetoothDevice;
private FrameLayout frameLayout;
private TextView textViewName;
private TextView textViewAddress;
public ViewHolder(@NonNull View itemView) {
super(itemView);
textViewName = (TextView) itemView.findViewById(R.id.textViewName);
textViewAddress = (TextView) itemView.findViewById(R.id.textViewAddress);
frameLayout = (FrameLayout) itemView.findViewById(R.id.frameLayout);
}
public void binding(BluetoothDevice bluetoothDevice) {
this.bluetoothDevice = bluetoothDevice;
String name = bluetoothDevice.getName();
String address = bluetoothDevice.getAddress();
address = address.substring(0, 2) + ":**:**:**:**:" + address.substring(15, 17);
textViewName.setText(name);
textViewAddress.setText(address);
}
public void setOnClickListener(OnClickListener listener) {
if (listener != null) {
frameLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
listener.onClick(bluetoothDevice);
}
});
}
}
public interface OnClickListener {
void onClick(BluetoothDevice bluetoothDevice);
}
}
public static class ItemDecoration extends RecyclerView.ItemDecoration {
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
@NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.set(0, 0, 0, 2);
}
}
}
编辑 src\main\res\layout\activity_main.xml
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#E3E3E3"
tools:context=".MainActivity">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarTheme"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toTopOf="@+id/buttonQuery"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar" />
<Button
android:id="@+id/buttonQuery"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
android:text="Query"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
编辑 MainActivity
:
package com.mk;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private static final int REQUEST_CODE_ENABLE_BLUETOOTH = 1;
private Context context;
private List<BluetoothDevice> bluetoothDevices = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Set a Toolbar to act as the ActionBar for this Activity window.
setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
//
context = this;
// Get a handle to the default local Bluetooth adapter.
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// Recycler View
RecyclerViewAdapter recyclerViewAdapter = new RecyclerViewAdapter(bluetoothDevices);
recyclerViewAdapter.setItemOnClickListener(new RecyclerViewAdapter.ViewHolder.OnClickListener() {
@Override
public void onClick(BluetoothDevice bluetoothDevice) {
Toast.makeText(context, bluetoothDevice.getName(), Toast.LENGTH_SHORT).show();
}
});
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
recyclerView.addItemDecoration(new RecyclerViewAdapter.ItemDecoration());
recyclerView.setAdapter(recyclerViewAdapter);
// Query paired devices
((Button) findViewById(R.id.buttonQuery)).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (bluetoothAdapter == null) {
Toast.makeText(context, "Your device doesn't support Bluetooth.", Toast.LENGTH_LONG).show();
} else if (!bluetoothAdapter.isEnabled()) {
startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), REQUEST_CODE_ENABLE_BLUETOOTH);
} else {
Set<BluetoothDevice> bondedDevices = bluetoothAdapter.getBondedDevices();
for (BluetoothDevice bluetoothDevice : bondedDevices) {
Log.d(TAG, "onClick: \n" + bluetoothDevice.getName() + "\n" + bluetoothDevice.getAddress() + "\n");
bluetoothDevices.add(bluetoothDevice);
recyclerViewAdapter.notifyItemInserted(bluetoothDevices.size());
}
}
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_ENABLE_BLUETOOTH) {
if (resultCode == RESULT_OK) {
}
}
}
}
发现设备
新建项目,选择 Empty Activity,在配置项目时,Minimum SDK
选择 API 16: Android 4.1 (Jelly Bean)
。
编辑 src\main\AndroidManifest.xml
应用清单文件:
- 声明 android.permission.BLUETOOTH 和 android.permission.BLUETOOTH_ADMIN 权限(第 4、6 行);
- 修改应用主题(第 10 行)。
<?xml version="1.0" encoding="utf-8"?>
<manifest ...>
<!-- Allows applications to connect to paired bluetooth devices. -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<!-- Allows applications to discover and pair bluetooth devices. -->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<application
...
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
...
</application>
</manifest>
新建 src\main\res\layout\recycler_view_bluetooth_device.xml
布局文件,用于显示被发现的蓝牙设备:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FFFFFF">
<TextView
android:id="@+id/textViewName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingLeft="10dp"
android:paddingRight="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textViewAddress"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingLeft="10dp"
android:paddingRight="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textViewName" />
<FrameLayout
android:id="@+id/frameLayout"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
新建 RecyclerViewAdapter
适配器类:
package com.mk;
import android.bluetooth.BluetoothDevice;
import android.graphics.Rect;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {
private List<BluetoothDevice> dataSet;
private ViewHolder.OnClickListener listener;
public RecyclerViewAdapter(List<BluetoothDevice> dataSet) {
this.dataSet = dataSet;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recycler_view_bluetooth_device, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.binding(dataSet.get(position));
holder.setOnClickListener(listener);
}
@Override
public int getItemCount() {
return dataSet.size();
}
public void setItemOnClickListener(ViewHolder.OnClickListener listener) {
this.listener = listener;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
private BluetoothDevice bluetoothDevice;
private TextView textViewName;
private TextView textViewAddress;
private FrameLayout frameLayout;
public ViewHolder(@NonNull View itemView) {
super(itemView);
textViewName = (TextView) itemView.findViewById(R.id.textViewName);
textViewAddress = (TextView) itemView.findViewById(R.id.textViewAddress);
frameLayout = (FrameLayout) itemView.findViewById(R.id.frameLayout);
}
public void binding(BluetoothDevice bluetoothDevice) {
this.bluetoothDevice = bluetoothDevice;
String name = bluetoothDevice.getName();
String address = bluetoothDevice.getAddress();
if (name == null) {
name = "unknown";
}
address = address.substring(0, 2) + ":**:**:**:**:" + address.substring(15, 17);
textViewName.setText(name);
textViewAddress.setText(address);
}
public void setOnClickListener(OnClickListener listener) {
if (listener != null) {
frameLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
listener.onClick(bluetoothDevice);
}
});
}
}
public interface OnClickListener {
void onClick(BluetoothDevice bluetoothDevice);
}
}
public static class ItemDecoration extends RecyclerView.ItemDecoration {
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
@NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.set(0, 0, 0, 2);
}
}
}
编辑 src\main\res\layout\activity_main.xml
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarTheme"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="16dp"
android:background="#CDCDCD"
app:layout_constraintBottom_toTopOf="@+id/buttonScan"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
app:layout_constraintVertical_bias="0.604" />
<Button
android:id="@+id/buttonScan"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
android:enabled="false"
android:text="Scan"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
编辑 MainActivity
:
package com.mk;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private static final int REQUEST_CODE_ENABLE_BLUETOOTH = 1;
private Context context;
private BluetoothAdapter bluetoothAdapter;
private List<BluetoothDevice> bluetoothDevices = new ArrayList<>();
private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) { // 发现设备
BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// BluetoothClass bluetoothClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS);
Log.d(TAG, "onReceive: \n" + bluetoothDevice.getName() + "\n" + bluetoothDevice.getAddress() + "\n");
String address = bluetoothDevice.getAddress();
for (BluetoothDevice device : bluetoothDevices) {
if (address.equals(device.getAddress()))
return;
}
bluetoothDevices.add(bluetoothDevice);
recyclerViewAdapter.notifyItemInserted(bluetoothDevices.size() - 1);
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { // 扫描完成
buttonScan.setEnabled(true);
buttonScan.setText("Scan");
}
}
};
private RecyclerViewAdapter recyclerViewAdapter;
private Button buttonScan;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Set a Toolbar to act as the ActionBar for this Activity window.
setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
//
context = this;
// Recycler View
recyclerViewAdapter = new RecyclerViewAdapter(bluetoothDevices);
recyclerViewAdapter.setItemOnClickListener(new RecyclerViewAdapter.ViewHolder.OnClickListener() {
@Override
public void onClick(BluetoothDevice bluetoothDevice) {
String name = bluetoothDevice.getName();
if (name == null) {
name = "unknown";
}
Toast.makeText(context, name, Toast.LENGTH_SHORT).show();
}
});
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
recyclerView.addItemDecoration(new RecyclerViewAdapter.ItemDecoration());
recyclerView.setAdapter(recyclerViewAdapter);
//
buttonScan = (Button) findViewById(R.id.buttonScan);
// Get a handle to the default local Bluetooth adapter.
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter == null) {
Toast.makeText(this, "Your device doesn't support Bluetooth!", Toast.LENGTH_LONG).show();
} else {
// Register for broadcasts when a device is discovered
this.registerReceiver(broadcastReceiver, new IntentFilter(BluetoothDevice.ACTION_FOUND));
// Register for broadcasts when discovery has finished
this.registerReceiver(broadcastReceiver, new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED));
buttonScan.setEnabled(true);
buttonScan.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (bluetoothAdapter.isEnabled()) {
if (bluetoothAdapter.isDiscovering()) {
bluetoothAdapter.cancelDiscovery();
}
if (bluetoothAdapter.startDiscovery()) { // Start the remote device discovery process.
Button button = (Button) v;
button.setEnabled(false);
button.setText("Scanning...");
int bluetoothDevicesSize = bluetoothDevices.size();
bluetoothDevices.removeAll(bluetoothDevices);
recyclerViewAdapter.notifyItemRangeRemoved(0, bluetoothDevicesSize);
} else {
Toast.makeText(MainActivity.this, "Scan failed, please try again!", Toast.LENGTH_SHORT).show();
}
} else {
startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), REQUEST_CODE_ENABLE_BLUETOOTH);
}
}
});
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (bluetoothAdapter != null) {
// Make sure we're not doing discovery anymore
bluetoothAdapter.cancelDiscovery();
// Unregister broadcast listeners
unregisterReceiver(broadcastReceiver);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_ENABLE_BLUETOOTH && resultCode == RESULT_OK) {
if (bluetoothAdapter.isEnabled()) {
if (bluetoothAdapter.isDiscovering()) {
bluetoothAdapter.cancelDiscovery();
}
if (bluetoothAdapter.startDiscovery()) { // Start the remote device discovery process.
buttonScan.setEnabled(false);
buttonScan.setText("Scanning...");
int bluetoothDevicesSize = bluetoothDevices.size();
bluetoothDevices.removeAll(bluetoothDevices);
recyclerViewAdapter.notifyItemRangeRemoved(0, bluetoothDevicesSize);
} else {
Toast.makeText(MainActivity.this, "Scan failed, please try again!", Toast.LENGTH_SHORT).show();
}
}
}
}
}