Android - Bluetooth overview(不完整)

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 the ACCESS_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:

  1. Get the BluetoothAdapter.

    The BluetoothAdapter is required for any and all Bluetooth activity. To get the BluetoothAdapter, call the static getDefaultAdapter() method. This returns a BluetoothAdapter 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. If getDefaultAdapter() returns null, then the device doesn’t support Bluetooth. For example:

    BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if (bluetoothAdapter == null) {
        // Device doesn't support Bluetooth
    }
    
  2. 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, call startActivityForResult(), passing in an ACTION_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.

    Figure 1: The enabling Bluetooth dialog.

    The REQUEST_ENABLE_BT constant passed to startActivityForResult() is a locally defined integer that must be greater than 0. The system passes this constant back to you in your onActivityResult() implementation as the requestCode parameter.

    If enabling Bluetooth succeeds, your activity receives the RESULT_OK result code in the onActivityResult() callback. If Bluetooth was not enabled due to an error (or the user responded “No”) then the result code is RESULT_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 应用清单文件:

  1. 声明 android.permission.BLUETOOTH 权限(第 5 行);
  2. 修改应用主题(第 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 应用清单文件:

  1. 声明 android.permission.BLUETOOTHandroid.permission.BLUETOOTH_ADMIN 权限(第 4、6 行);
  2. 修改应用主题(第 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();
                }
            }
        }
    }
}

参考

Connectivity - Bluetooth - Overview

Create dynamic lists with RecyclerView

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值