Android-蓝牙聊天demo

作者: 白帽子耗子 | 来源:发表于2018-12-17 17:48 被阅读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 地址

    相关文章

      网友评论

        本文标题:Android-蓝牙聊天demo

        本文链接:https://www.haomeiwen.com/subject/nkeckqtx.html