Android-蓝牙聊天demo

官方文档: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 之后还需要一个模糊定位的权限,否则扫描功能无效。

google 文档:为给用户提供更严格的数据保护,从此版本(6.0)开始,对于使用 WLAN API 和 Bluetooth API 的应用,Android 移除了对设备本地硬件标识符的编程访问权。WifiInfo.getMacAddress()方法和 BluetoothAdapter.getAddress() 方法现在会返回常量值 02:00:00:00:00:00
现在,要通过蓝牙和 WLAN 扫描访问附近外部设备的硬件标识符,您的应用必须拥有 ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION 权限。

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

第二步:启动蓝牙

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 地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • 最近项目使用蓝牙,之前并没有接触,还是发现了很多坑,查阅了很多资料,说的迷迷糊糊,今天特查看官方文档。 说下遇到的...
    King9527阅读 1,767评论 0 1
  • Android平台支持蓝牙网络协议栈,实现蓝牙设备之间数据的无线传输。本文档描述了怎样利用android平台提供的...
    Camming阅读 3,226评论 0 3
  • 蓝牙 注:本文翻译自https://developer.android.com/guide/topics/conn...
    RxCode阅读 8,575评论 11 99
  • 前言 最近在做Android蓝牙这部分内容,所以查阅了很多相关资料,在此总结一下。 基本概念 Bluetooth是...
    猫疏阅读 14,395评论 7 113
  • Android 平台包含蓝牙网络堆栈支持,凭借此项支持,设备能以无线方式与其他蓝牙设备交换数据。应用框架提供了通过...
    虎三呀阅读 756评论 0 1