Android开发Android开发Android开发经验谈

Android-蓝牙聊天demo

2018-12-17  本文已影响7人  白帽子耗子

官方文档:https://developer.android.com/guide/topics/connectivity/bluetooth

Android 中将蓝牙分为传统蓝牙低功耗蓝牙(Bluetooth low energy)两种。后者的优势在于快速搜索,快速连接,超低功耗保持连接和数据传输,同时低功耗带来的缺点是数据传输速率低,所以多用在可穿戴式设备。

在这里我们主要介绍使用传统蓝牙来实现一个聊天的数据传输 demo。以下内容基本都是基于官方文档的二次阐述,以及一些疑惑的查找到的解答,最后在 demo 里面有对蓝牙的相关操作进行了封装。先贴个图看看效果吧:


蓝牙.png

基础知识

BluetoothAdapter: 本地蓝牙适配器,我们在发现设备,配对的时候都得用上它。

BluetoothDevice: 远程蓝牙设备,就是代表着你可以连接的一个设备,里面存储名字,MAC地址等信息。

BluetoothSocket 和 BluetoothServerSocket: 蓝牙套接字,和 TCP 的 Socket 相似。一台设备开启一个 ServerSocket 并监听,另一台设备开启 Socket 进行连接,以此实现一个端对端的连接和数据传输。

UUID: 唯一识别符。它被用于唯一标识应用的蓝牙服务(不是表示蓝牙设备)。

Q1:为什么网上的大多数例子都是使用 00001101-0000-1000-8000-00805F9B34FB 这个UUID?
A1:这是因为一个蓝牙设备里面可以提供诸多服务,如A2DP(蓝牙音频传输)、HEADFREE(免提)、SPP(串口通信) 等等。而上面的字符串码就是 SPP 的 UUID,基本蓝牙板上默认就是这个值,我们可以通过UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")来将字符串转成 UUID。
在连接蓝牙串口板我们往往就会使用上面的UUID,但是如果 Android 端对端的话,建议自己自己设定 UUID,这样别人的 UUID 就连不上了。

实现一个蓝牙聊天demo

要实现一个蓝牙聊天demo,首先我们有两台有蓝牙功能的设备,这里我用了两台手机。按照流程一般来说要开启蓝牙-搜索设备-配对设备-连接-通信。如此就能实现一个基本的蓝牙通信。

第一步:权限

在 Android 中没有权限寸步难行。要使用蓝牙,还需要声明相应的权限。

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

BLUETOOTH 是基本的权限,用于你的蓝牙连接,数据传输等。

BLUETOOTH_ADMIN 一般应只用于发现本地蓝牙设备。

除非该应用是将要应用户请求修改蓝牙设置的“超级管理员”,否则不应使用此权限所授予的其他能力。

另:如果要使用 BLUETOOTH_ADMIN 权限,则还必须拥有 BLUETOOTH 权限。

此外会发现我这里比官方文档还多了个 ACCESS_COARSE_LOCATION,这是因为我在实测过程中,我的测试机Android 8.0 系统中,蓝牙扫描没有扫描出信息,但是系统是有的。在网上一番寻找之后发现在 Android 6.0 之后还需要一个模糊定位的权限,否则扫描功能无效。

关于动态权限申请在此不作累述,小伙伴们可以自己去实现。

第二步:启动蓝牙

1、获取 BluetoothAdapter

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
    //TODO 设备不支持蓝牙,阻断用户操作
}

2、启动蓝牙

if(!mBlueAdapter.isEnabled()){
    //请求蓝牙
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

系统将会弹窗提示用户是否开启蓝牙,用户的选择将在 onActivityResult() 中得到反馈。同意的时候收到 RESULT_OK,拒绝的时候收到 RESULT_CANCELED。

第三步:查找设备

1、查找已配对设备

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

2、查找未知设备

mBlueAdapter.startDiscovery()

查找未知设备只需要调用 startDiscovery() 即可,这是一个异步操作,系统一般会在后台进程进行一个 12 秒的查询扫描。查找出来的信息我们需要在广播中进行监听才可得知。

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            mUnpaireList.add(device);
            mUnpaireAdapter.notifyDataSetChanged();
        }
    }
};
//注册广播
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mBtReceiver, filter);
//同时别忘了销毁时注销广播

第四步:配对连接

在这里我们往往需要一台设备做服务器端一台做客户端,实际上就是 app 开启了一个服务器线程让蓝牙的 socket 可以连接。连接完成后再使用 I/O Stream 进行数据交互。

1、服务器线程

我们需要用 listenUsingInsecureRfcommWithServiceRecord(String,UUID) 获取 BluetoothServerSocket。

Q2:listenUsingRfcommWithServiceRecord()listenUsingInsecureRfcommWithServiceRecord() 有什么区别?
A2:从名字来看似乎是安全不安全的区别,但是实际上我并没有找到相关资料佐证。也有文章描述客户端的 socket 创建createRfcommSocketToServiceRecord 是安卓2.3系统及以下用的,新的安卓要用 createInsecureRfcommSocketToServiceRecord,所以对应着服务器端也用Insercure吧。

服务器监听中,由于 accept() 方法是阻塞的,所以需要子线程中处理。

private class AcceptThread extends Thread {
    private final BluetoothServerSocket mmServerSocket;

    public AcceptThread() {
        BluetoothServerSocket tmp = null;
        try {
            tmp = mBluetoothAdapter.listenUsingInsecureRfcommWithServiceRecord(NAME, MY_UUID);
        } catch (IOException e) { }
        mmServerSocket = tmp;
    }

    public void run() {
        BluetoothSocket socket = mmServerSocket.accept();
        mmServerSocket.clost();
        mInputStream = socket.getInputStrem();
        mOutputStream = socket.getOutputStream();
        byte[] buffer = new byte[1024];  
        int bytes;
        while (true) {
            try{
                //读取buffer信息打印出来
                bytes = mInputStream.read(buffer);
                String s = new String(buffer, 0, bytes);
                sendHandlerMsg(s);
            } carch(IOException e){
                break;
            }
        }
    }
}

2、客户端连接

客户端连接和服务端连接相似。当然首先你要获取到要配对的设备 BluetoothDevice,然后获取 BluetoothSocket ,使用 mSocket.connect() 连接即可。他们的逻辑基本相同,在官方文档中也有相关的描述。

在这里因为实际上我的需求是使用手机连接一个硬件设备,所以我选择封装了一个蓝牙工具类,把蓝牙开启连接等客户端相关操作封装到 BluetoothManager 中。其中 ConnectThread 和 ReadThread 抽成两个Runnable 放在线程池中处理。当 socket 连接成功后获取到 IO 流来进行读写操作。读操作因为属于阻塞操作放在子线程。代码这里就不贴了,文末有此 demo 的地址。有兴趣的也可以自己去实现一下。

第五步:其他

剩下的就是布局和交互逻辑的实现,这里就不在一一阐述了。

总结

蓝牙的相关操作感觉和 Socket 非常地相似,都是进行端对端绑定,然后进行数据传输。所以同理也应该会存在类似 Socket 的各种问题,比如说丢包,断开连接需要心跳检测,重连机制等等。这个demo只是对API进行了一定程度的整合,还存有不少的问题。

github 地址

上一篇下一篇

猜你喜欢

热点阅读