Android 蓝牙开发 入门级(史上最全)

3. 蓝牙权限

在开始开发之前,需要在Android项目的AndroidManifest.xml文件中声明蓝牙相关的权限。对于基本的蓝牙操作,需要以下权限:

<!-- 允许应用程序连接到配对的蓝牙设备 -->
<uses-permission android:name="android.permission.BLUETOOTH" />

<!-- 允许应用程序发现和配对蓝牙设备 -->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

从Android 6.0(API级别23)开始,蓝牙扫描需要定位权限,因为蓝牙扫描可以被用来粗略地定位用户。

因此,如果你的应用目标是API级别23或更高,并且需要进行蓝牙扫描,你还需要添加:

<!-- 用于Android 6.0及以上版本的蓝牙扫描 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

从Android 10(API级别29)开始,ACCESS_FINE_LOCATION权限被分为更细粒度的权限,如果你的应用需要在后台访问位置,还需要声明:

<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
4. 用户权限请求

对于运行时权限(如位置权限),你需要在应用运行时请求用户授权。这通常在你的应用尝试进行蓝牙扫描之前完成。

这是请求权限的一个基本示例:

(1)安卓原生检查权限和申请权限

if (ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.ACCESS_FINE_LOCATION)
        != PackageManager.PERMISSION_GRANTED) {
    ActivityCompat.requestPermissions(thisActivity,
            new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
            MY_PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
}

在这里,MY_PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION是一个应用定义的整数常量。将在回调方法onRequestPermissionsResult中使用它来接收请求结果

(2)第三方库xxPermissions

if (ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
                // 检查蓝牙连接权限,如果没有,则请求权限
                getPermission(Manifest.permission.BLUETOOTH_CONNECT);
                return;
            }
// 获取权限的方法
    private void getPermission(String permission) {
        // 请求权限
        XXPermissions.with(getContext())
                .permission(permission) // 申请单个权限
                .request(new OnPermissionCallback() {
                    @Override
                    public void onGranted(@NonNull List<String> permissions, boolean allGranted) {
                        if (!allGranted) {
                            // 如果未全部授予权限,则显示提示信息
                            Toast.makeText(getContext(), "获取部分权限成功,但部分权限未正常授予", Toast.LENGTH_SHORT).show();
                            Log.d("TAG", "获取部分权限成功");
                            return;
                        }
                        // 权限全部成功授予后的操作
                        Log.d("TAG", "获取权限成功");
                    }

                    @Override
                    public void onDenied(@NonNull List<String> permissions, boolean doNotAskAgain) {
                        if (doNotAskAgain) {
                            // 如果用户选择不再询问,则提示用户手动开启权限,并跳转到应用设置页面
                            Toast.makeText(getContext(), "被永久拒绝授权,请手动授予权限", Toast.LENGTH_SHORT).show();
                            Log.d("TAG", "被永久拒绝授权,请手动授予权限");
                            XXPermissions.startPermissionActivity(getContext(), permissions);
                        } else {
                            // 如果权限被拒绝但没有选择不再询问,则显示失败提示
                            Toast.makeText(getContext(), "获取权限失败", Toast.LENGTH_SHORT).show();
                            Log.d("TAG", "获取权限失败");
                        }
                    }
                });
    }

经典蓝牙BT开发流程

备注:后面几节讲解的都是以BLE为例子

这就是开始使用蓝牙开发所需了解的基础知识。

我们继续下一节。

第二节:启用蓝牙和检查设备支持

在这一节中,我们将学习如何在你的应用中启用蓝牙功能,并检查用户的设备是否支持蓝牙。

1. 获取蓝牙适配器

首先,你需要获取BluetoothAdapter的实例,它是所有蓝牙操作的入口点。

BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

如果getDefaultAdapter()返回null,则表示设备不支持蓝牙

2. 检查蓝牙是否启用

接下来,检查蓝牙是否已经启用。使用BluetoothAdapterisEnabled**()**方法可以检查蓝牙是否启用:

if (bluetoothAdapter != null && !bluetoothAdapter.isEnabled()) {
    // 蓝牙未启用
}

bluetoothAdapter != null 说明设备支持蓝牙

!bluetoothAdapter.isEnabled() 说明蓝牙没启用

3. 请求用户启用蓝牙

如果蓝牙未启用,你可以请求用户启用它

这可以通过启动一个Intent来请求用户启用蓝牙,而不是直接调用enable()方法,因为**enable()**方法无需用户同意即可启用蓝牙,这可能不是最佳用户体验。

Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);

startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);

在这里,REQUEST_ENABLE_BT是应用定义的整数请求码,用于在你的Activity的onActivityResult回调中接收结果。

4. onActivityResult回调

当用户响应启用蓝牙的请求时,系统会调用你的Activity的onActivityResult方法。

你可以在这里检查请求的结果:

@Override

protected void onActivityResult(int requestCode, int resultCode, Intent data) {

    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == REQUEST_ENABLE_BT) {

        if (resultCode == RESULT_OK) {

            // 用户启用了蓝牙

        } else {

            // 用户未启用蓝牙

        }

    }

}

这就是如何在你的应用中启用蓝牙并检查设备是否支持蓝牙的基本步骤,理解这些步骤是进行蓝牙开发的基础。

第三节:发现设备和获取已配对设备

在这一节中,我们将学习如何发现附近的蓝牙设备以及如何获取已经与你的设备配对的蓝牙设备列表。

1. 获取已配对设备

你的Android设备可能已经与一些蓝牙设备配对过了。

要获取这些已配对的设备列表,你可以使用BluetoothAdapter的**getBondedDevices()**方法:

Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
if (pairedDevices.size() > 0) {
    // 至少有一个已配对设备
    for (BluetoothDevice device : pairedDevices) {
        String deviceName = device.getName();
        String deviceHardwareAddress = device.getAddress(); // MAC地址
         Log.d("TAG", "蓝牙设备名称:"+deviceName);
         Log.d("TAG", "蓝牙设备地址:"+deviceHardwareAddress);
    
                                                    }
}
2. 发现新设备

要发现附近的蓝牙设备,你需要调用BluetoothAdapter的startDiscovery()方法。这个方法是异步的,发现过程通常会持续12秒

你需要注册一个BroadcastReceiver来监听BluetoothDevice.ACTION_FOUND广播,这个广播会在发现新设备时发送。

// 注册广播接收器以监听发现的设备
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(receiver, filter);

// 发现设备
if (bluetoothAdapter.startDiscovery()) {
    // 发现过程成功启动
}

疑问:

BluetoothDevice.ACTION_FOUND 是一个什么值?

解答:

  • BluetoothDevice.ACTION_FOUND 是一个字符串常量,用于在广播中标识已找到一个蓝牙设备,值是"android.bluetooth.device.action.FOUND"。
  • 当你的应用调用BluetoothAdapter的startDiscovery()方法开始扫描附近的蓝牙设备时,每发现一个设备,系统就会发送这个ACTION_FOUND的广播。
  • 你的应用可以通过注册一个BroadcastReceiver来监听这个广播,以便获取每个发现的蓝牙设备的信息。

在你的BroadcastReceiver中,你可以获取发现的设备信息:

private final BroadcastReceiver receiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
            // 从Intent中获取发现的BluetoothDevice
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            String deviceName = device.getName();
            String deviceHardwareAddress = device.getAddress(); // MAC地址
        }
    }
};

疑问:

action有几种?

解答:

- BluetoothDevice.ACTION_ACL_CONNECTED:当与远程设备建立低级别(ACL)连接时发送的广播。

- BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED:当系统要断开与远程设备的低级别(ACL)连接时发送的广播。

- BluetoothDevice.ACTION_ACL_DISCONNECTED:当与远程设备的低级别(ACL)连接断开时发送的广播。

- BluetoothDevice.ACTION_BOND_STATE_CHANGED:当远程设备的配对状态发生变化时发送的广播。

- BluetoothDevice.ACTION_NAME_CHANGED:当远程设备的名称发生变化时发送的广播。

- BluetoothDevice.ACTION_PAIRING_REQUEST:在需要配对时发送的广播,通常是因为配对过程需要输入PIN码确认配对

- BluetoothAdapter.ACTION_DISCOVERY_STARTED:当设备发现开始时发送的广播。

- BluetoothAdapter.ACTION_DISCOVERY_FINISHED:当设备发现结束时发送的广播。

- BluetoothAdapter.ACTION_STATE_CHANGED:当蓝牙适配器的状态发生变化时发送的广播,例如蓝牙被开启或关闭。

监听这些action可以让你的应用更好地与蓝牙设备交互。

例如,

通过监听ACTION_BOND_STATE_CHANGED,你的应用可以知道何时一个设备已经成功配对,或者配对失败。

通过监听ACTION_ACL_CONNECTED和ACTION_ACL_DISCONNECTED,你的应用可以知道何时设备连接或断开连接。

可以在这些节点做一些处理。

3. 停止发现

由于发现过程会消耗大量资源,一旦你找到了你感兴趣的设备,或者你想停止扫描,你应该使用**cancelDiscovery()**方法来停止发现过程:

bluetoothAdapter.cancelDiscovery();
4. 清理

记得在不再需要时,**unregisterReceiver(receiver)**注销你的BroadcastReceiver,例如,在你的Activity或Fragment的onDestroy()方法中:

@Override

protected void onDestroy() {

    super.onDestroy();

    // 确保我们不再监听广播

    unregisterReceiver(receiver);

}

这就是如何发现新设备以及获取已配对设备的基本步骤。

理解这些步骤对于开发能够与其他蓝牙设备交互的应用是非常重要的。

当你准备好了,请告诉我,以便我们继续下一节。

第四节:连接到蓝牙设备

在这一节中,我们将学习如何使用BluetoothSocket来连接到一个蓝牙设备。连接到设备是进行数据交换的前提。

1. 创建BluetoothSocket

要与远程蓝牙设备建立连接,首先需要创建一个BluetoothSocket

BluetoothSocket代表了蓝牙套接字的接口,它是两个设备之间通信的通道。

对于大多数用途,你将使用**createRfcommSocketToServiceRecord(UUID)**方法来创建BluetoothSocket。

这个方法需要一个UUID参数,这个UUID必须是你想要连接的远程设备上的蓝牙服务的UUID。UUID代表通用唯一标识符,用于唯一标识你的应用的蓝牙服务。

BluetoothDevice device = ... // 获取一个BluetoothDevice对象

UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb"); // 示例UUID

BluetoothSocket socket = device.createRfcommSocketToServiceRecord(MY_UUID);

有几个概念:

  • 服务UUID
  • 特征UUID
  • 描述UUID
  • 公共标准UUID
2. 连接到远程设备

创建BluetoothSocket后,你可以调用它的**connect()**方法来尝试与远程设备建立连接。

这个方法是**阻塞调用****(**直到连接成功或抛出异常)。

try {

    socket.connect();

    // 连接成功,可以开始进行数据交换

} catch (IOException e) {

    // 连接失败

    e.printStackTrace();

}
3. 数据交换

连接成功后,你可以通过BluetoothSocket的输入输出流来进行数据的读写。

try {

    InputStream inputStream = socket.getInputStream();

    OutputStream outputStream = socket.getOutputStream();

    // 从InputStream读取数据

    byte[] buffer = new byte[1024];

    int bytes;

    bytes = inputStream.read(buffer);

    String receivedData = new String(buffer, 0, bytes);

    // 向OutputStream写入数据

    String dataToSend = "Hello, Bluetooth!";

    outputStream.write(dataToSend.getBytes());

} catch (IOException e) {

    // 处理异常

}
4. 断开连接

完成数据交换后,应该关闭BluetoothSocket来释放资源

try {

    socket.close();

} catch (IOException e) {

    // 处理异常

}

注意事项

- 连接过程可能会花费一些时间,建议在一个单独的线程中进行连接操作,以避免阻塞UI线程。
- 确保你的UUID与远程设备提供的服务UUID匹配。
- 在连接过程中和之后,要妥善处理异常。

疑问:

我听同事说发送数据还有片段发送,是怎么样的

解答:

在蓝牙通信中,由于蓝牙协议本身的限制以及不同设备的性能差异,发送大量数据时直接发送整个数据包可能会导致数据传输失败或者效率低下。

因此,将大数据分割成小片段(fragments)进行发送是一种常见的做法,这就是所谓的“片段发送”或“数据分片”。

1.数据分片的基本思想

数据分片的基本思想,是将大的数据包分割成多个较小的数据片段,然后逐个片段地发送这些数据。接收方收到所有片段后,再将它们重新组合成原始的数据包。

2.如何实现数据分片?

实现数据分片的一个简单方法是:

  • (1)确定片段大小:
    • 首先,你需要确定每个数据片段的大小。
      • 这个大小需要根据你的应用需求和蓝牙硬件的能力来决定。一个常见的做法是使用512字节作为每个片段的大小。
  • (2)分割数据
    • 将你的数据分割成多个片段。
      • 如果数据大小不是片段大小的整数倍,最后一个片段可能会更小。
  • (3)发送数据片段:
    • 逐个发送每个数据片段。
      • 在每个片段发送之后,可能需要等待接收方的确认,再发送下一个片段。
  • (4)接收方重组数据:
    • 接收方需要按照发送顺序接收并存储每个数据片段。
      • 一旦所有片段都被接收,接收方就可以将它们组合成原始的数据包。
示例代码

以下是一个简化的示例,展示了如何将一个大的字节数组分割成多个片段并发送:

OutputStream outputStream = bluetoothSocket.getOutputStream();

byte[] largeData = ...; // 假设这是要发送的大数据

int chunkSize = 512; // 片段大小

int offset = 0;

while (offset < largeData.length) {

    // 计算剩余数据大小

    int remaining = largeData.length - offset;

    int byteCount = Math.min(remaining, chunkSize); // 这次发送的字节数

    // 发送数据片段

    outputStream.write(largeData, offset, byteCount);

    outputStream.flush(); // 确保数据被发送

    offset += byteCount; // 更新偏移量

    // 可选:等待接收方的确认

}

// 数据发送完毕
注意事项

- 数据分片会增加通信的复杂度,特别是在需要确保数据完整性和顺序的情况下。
- 接收方需要有相应的逻辑来处理接收到的数据片段,包括片段的重组和可能的错误处理。
- 在实际应用中,可能还需要考虑如何处理数据传输中的错误,例如重传丢失的片段。

通过数据分片,你可以更可靠地通过蓝牙发送大量数据,同时还可以提高数据传输的效率。

示例代码

获取蓝牙设备电量

public  void getBluetoothDeviceBattery() {
    BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
    //获取BluetoothAdapter的Class对象
    Class<BluetoothAdapter> bluetoothAdapterClass = BluetoothAdapter.class;
    try {
        //反射获取蓝牙连接状态的方法
        Method method = bluetoothAdapterClass.getDeclaredMethod("getConnectionState", (Class[]) null);
        //打开使用这个方法的权限
        method.setAccessible(true);
        int state = (int) method.invoke(btAdapter, (Object[]) null);

        if (state == BluetoothAdapter.STATE_CONNECTED) {
            //获取在系统蓝牙的配对列表中的设备--!已连接设备包含在其中
            if (ActivityCompat.checkSelfPermission(this,
                    android.Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
                Log.e(TAG,  "没有权限");
                return;
            }
            Set<BluetoothDevice> devices = btAdapter.getBondedDevices();
            for (BluetoothDevice device : devices) {
                Method batteryMethod = BluetoothDevice.class.getDeclaredMethod("getBatteryLevel", (Class[]) null);
                batteryMethod.setAccessible(true);
                Method isConnectedMethod = BluetoothDevice.class.getDeclaredMethod("isConnected", (Class[]) null);
                isConnectedMethod.setAccessible(true);
                boolean isConnected = (boolean) isConnectedMethod.invoke(device, (Object[]) null);
                int level = (int) batteryMethod.invoke(device, (Object[]) null);
                if (device != null && level > 0 && isConnected) {
                    String deviceName = device .getName();
                  //  LogUtils.d(deviceName + "    电量:  " + level);
                    Log.e(TAG, deviceName + "    电量:  " + level);
                }
            }
        } else {
           // ToastUtils.showLong("No Connected Bluetooth Devices Found");
            Log.e(TAG,  "No Connected Bluetooth Devices Found");
        }
    } catch (Exception e) {
        Log.e(TAG,  "Exception"+e.toString());
        e.printStackTrace();
    }

}

总之,这就是如何连接到蓝牙设备并进行数据交换的基本步骤。理解这些步骤对于开发能够与其他蓝牙设备交互的应用是非常重要的。

当你准备好了,请告诉我,以便我们继续下一节。

第五节:管理蓝牙连接

在这一节中,我们将讨论如何管理蓝牙连接,包括:数据的接收处理、连接的维护和断开连接的正确方式。

1. 接收数据

在前一节中,我们讨论了如何发送数据。接收数据同样重要,通常涉及到从BluetoothSocket的输入流中读取数据。

由于数据接收是阻塞操作,推荐在单独的线程中进行读取操作,以避免阻塞UI线程。

class ConnectedThread extends Thread {

    private final BluetoothSocket socket;

    private final InputStream inputStream;

    private final OutputStream outputStream;

    public ConnectedThread(BluetoothSocket socket) {

        this.socket = socket;

        InputStream tmpIn = null;

        OutputStream tmpOut = null;

        try {

            tmpIn = socket.getInputStream();

            tmpOut = socket.getOutputStream();

        } catch (IOException e) {

            e.printStackTrace();

        }

        inputStream = tmpIn;

        outputStream = tmpOut;

    }

    public void run() {

        byte[] buffer = new byte[1024];  // 缓冲区

        int bytes; // 读取的字节数

        while (true) {

            try {

                // 从InputStream读取数据

                bytes = inputStream.read(buffer);

                String receivedData = new String(buffer, 0, bytes);

                // 处理接收到的数据

            } catch (IOException e) {

                // 连接丢失时的处理

                break;

            }

        }

    }

    // 调用此方法从外部发送数据

    public void write(byte[] bytes) {

        try {

            outputStream.write(bytes);

        } catch (IOException e) {

            e.printStackTrace();

        }

    }

## 最后

**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

**深知大多数网络安全工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

**因此收集整理了一份《2024年网络安全全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。**

![img](https://img-blog.csdnimg.cn/img_convert/7f46e133d2309b3cce47b133b18ddefc.png)

![img](https://img-blog.csdnimg.cn/img_convert/c4e21f4df1e0e0cb2119d57f1548847c.png)

![img](https://img-blog.csdnimg.cn/img_convert/c42a99bedf2b1015309d1b0a78848432.png)

![img](https://img-blog.csdnimg.cn/img_convert/7e8ac7602d80ceacd66bcbdc266372db.png)

![img](https://img-blog.csdnimg.cn/img_convert/398495c12d78614ef574cea1dc7e15fd.png)

 

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上网络安全知识点!真正的体系化!**

[**如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!**](https://bbs.csdn.net/topics/618653875)

**由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!**

id write(byte[] bytes) {

        try {

            outputStream.write(bytes);

        } catch (IOException e) {

            e.printStackTrace();

        }

    }

## 最后

**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

**深知大多数网络安全工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

**因此收集整理了一份《2024年网络安全全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。**

[外链图片转存中...(img-lq79p9sw-1715454959838)]

[外链图片转存中...(img-Ju3RXjYV-1715454959839)]

[外链图片转存中...(img-uPM1FttO-1715454959839)]

[外链图片转存中...(img-AkKHygTu-1715454959839)]

[外链图片转存中...(img-o6jBnLLa-1715454959840)]

 

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上网络安全知识点!真正的体系化!**

[**如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!**](https://bbs.csdn.net/topics/618653875)

**由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!**

  • 19
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值