Android-蓝牙聊天demo
来源:白帽子耗子     阅读:931
一米
发布于 2018-12-17 22:38
查看主页

官方文档: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 devicesif (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 地址

免责声明:本文为用户发表,不代表网站立场,仅供参考,不构成引导等用途。 系统环境 服务器应用
相关推荐
人工智能通识-AI发展简史-讲义全篇
vue自己设置js图片碎片轮播图切换效果
2020 年疫情下的前台核心竞争力
沈阳小学外研版二年级上下册部分单词词组双语汇总
初识DataLake数据湖
首页
搜索
订单
购物车
我的