神奇的蓝牙之即时通讯

时间好快,转眼又一个月了,每天工作很忙,也没有什么时间学习,下一篇打算研究一下 framwork 的一些东西。

蓝牙

Android 平台包含蓝牙网络堆栈支持,凭借此项支持,设备能以无线方式与其他蓝牙设备交换数据。应用框架提供了通过 Android Bluetooth API 访问蓝牙功能的途径。 这些 API 允许应用以无线方式连接到其他蓝牙设备,从而实现点到点和多点无线功能。

传统蓝牙

使用蓝牙进行通信的四项主要任务:

  • 设置蓝牙
  • 查找局部区域内的配对设备或可用设备
  • 连接设备
  • 在设备之间传输数据

1、声明权限

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

2、设置蓝牙

  • a、获取 BluetoothAdapter
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
    // Device does not support Bluetooth
}
  • b、启用蓝牙
if (!mBluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

您的应用还可以选择侦听 ACTION_STATE_CHANGED 广播 Intent,每当蓝牙状态发生变化时,系统都会广播此 Intent。 此广播包含额外字段

  • EXTRA_STATE : 新的蓝牙状态
  • EXTRA_PREVIOUS_STATE : 旧的蓝牙状态

这些额外字段可能的值包括 :

  • STATE_TURNING_ON
  • STATE_ON
  • STATE_TURNING_OFF
  • STATE_OFF

侦听此广播适用于检测在您的应用运行期间对蓝牙状态所做的更改。

3、查找设备

设备发现是一个扫描过程,它会搜索局部区域内已启用蓝牙功能的设备,然后请求一些关于各台设备的信息。

但局部区域内的蓝牙设备仅在其当前已启用可检测性时才会响应发现请求。

利用此信息,执行发现的设备可以选择发起到被发现设备的连接。

请记住,被配对与被连接之间存在差别。被配对意味着两台设备知晓彼此的存在,具有可用于身份验证的共享链路密钥,并且能够与彼此建立加密连接。 被连接意味着设备当前共享一个 RFCOMM 通道,并且能够向彼此传输数据。 当前的 Android Bluetooth API 要求对设备进行配对,然后才能建立 RFCOMM 连接。

  • a、查询配对的设备
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());
    }
}
  • b、发现设备

要开始发现设备,只需调用 startDiscovery()。该进程为异步进程,并且该方法会立即返回一个布尔值,指示是否已成功启动发现操作。

针对 ACTION_FOUND Intent 注册一个 BroadcastReceiver,以便接收每台发现的设备的相关信息。 针对每台设备,系统将会广播 ACTION_FOUND Intent。此 Intent 将携带额外字段

  • EXTRA_DEVICE // 包含 BluetoothDevice
  • EXTRA_CLASS // 包含 BluetoothClass
// Create a BroadcastReceiver for ACTION_FOUND
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        // When discovery finds a device
        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
            // Get the BluetoothDevice object from the Intent
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            // Add the name and address to an array adapter to show in a ListView
            mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
        }
    }
};
// Register the BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy
  • c、启用可检测性

如果您希望将本地设备设为可被其他设备检测到,请使用

ACTION_REQUEST_DISCOVERABLE

操作 Intent ,您可以通过添加 :

EXTRA_DISCOVERABLE_DURATION 

Intent Extra 来定义不同的持续时间。 应用可以设置的最大持续时间为 3600 秒,值为 0 则表示设备始终可检测到。 任何小于 0 或大于 3600 的值都会自动设为 120 秒。 例如,以下片段会将持续时间设为 300 秒:

Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);

如果您希望在可检测到模式发生变化时收到通知,您可以针对

ACTION_SCAN_MODE_CHANGED Intent 

注册 BroadcastReceiver。 它将包含额外字段

  • EXTRA_SCAN_MODE
  • EXTRA_PREVIOUS_SCAN_MODE

二者分别告知您新的和旧的扫描模式。 每个字段可能的值包括:

  • SCAN_MODE_CONNECTABLE_DISCOVERABLE :可检测到模式
  • SCAN_MODE_CONNECTABLE :未处于可检测到模式但仍能接收连接
  • SCAN_MODE_NONE :未处于可检测到模式并且无法接收连接

4、连接设备

要在两台设备上的应用之间创建连接,必须同时实现服务器端和客户端机制,因为其中一台设备必须开放服务器套接字,而另一台设备必须发起连接(使用服务器设备的 MAC 地址发起连接)。 当服务器和客户端在同一 RFCOMM 通道上分别拥有已连接的 BluetoothSocket 时,二者将被视为彼此连接。

蓝牙套接字接口(与 TCP Socket 相似)。这是允许应用通过 InputStream 和 OutputStream 与其他蓝牙设备交换数据的连接点。

  • a、连接为服务器

以下是设置服务器套接字并接受连接的基本过程:

1、通过调用 listenUsingRfcommWithServiceRecord(String, UUID) 获取 BluetoothServerSocket。

2、通过调用 accept() 开始侦听连接请求。

3、除非您想要接受更多连接,否则请调用 close()。

这将释放服务器套接字及其所有资源,但不会关闭 accept() 所返回的已连接的 BluetoothSocket。

accept() 调用不应在主 Activity UI 线程中执行,因为它是阻塞调用,并会阻止与应用的任何其他交互。 在您的应用所管理的新线程中使用 BluetoothServerSocket 或 BluetoothSocket 完成所有工作。

  • b、连接为客户端

以下是基本过程:

1、使用 BluetoothDevice,通过调用 createRfcommSocketToServiceRecord(UUID) 获取 BluetoothSocket。

2、通过调用 connect() 发起连接。

系统将会在远程设备上执行 SDP 查找,以便匹配 UUID。 如果查找成功并且远程设备接受了该连接,它将共享 RFCOMM 通道以便在连接期间使用,并且 connect() 将会返回。 此方法为阻塞调用。

  • c、管理连接

在成功连接两台(或更多台)设备后,每台设备都会有一个已连接的 BluetoothSocket。 这一点非常有趣,因为这表示您可以在设备之间共享数据。 利用 BluetoothSocket,传输任意数据的一般过程非常简单:

获取 InputStream 和 OutputStream,二者分别通过套接字以及 getInputStream() 和 getOutputStream() 来处理数据传输。

使用 read(byte[]) 和 write(byte[]) 读取数据并写入到流式传输。

就这么简单。

5、使用配置文件

从 Android 3.0 开始,Bluetooth API 便支持使用蓝牙配置文件。 蓝牙配置文件是适用于设备间蓝牙通信的无线接口规范。

对于连接到无线耳机的手机,两台设备都必须支持 “免提配置文件”。

Android Bluetooth API 提供了以下蓝牙配置文件的实现:

  • 耳机

耳机配置文件提供了蓝牙耳机支持,以便与手机配合使用。

  • A2DP

高级音频分发配置文件 (A2DP) 定义了高质量音频如何通过蓝牙连接和流式传输,从一个设备传输到另一个设备。 Android 提供了 BluetoothA2dp 类,它是用于通过 IPC 来控制蓝牙 A2DP 服务的代理。

  • 健康设备

Android 4.0(API 级别 14)引入了对蓝牙健康设备配置文件 (HDP) 的支持。 这允许您创建应用,使用蓝牙与支持蓝牙功能的健康设备进行通信,例如心率监测仪、血糖仪、温度计、台秤等等。

以下是使用配置文件的基本步骤:

1、获取默认适配器(请参阅设置蓝牙)。

2、使用 getProfileProxy() 建立到配置文件所关联的配置文件代理对象的连接。在以下示例中,配置文件代理对象是一个 BluetoothHeadset 的实例。

3、设置 BluetoothProfile.ServiceListener。此侦听程序会在 BluetoothProfile IPC 客户端连接到服务或断开服务连接时向其发送通知。

4、在 onServiceConnected() 中,获取配置文件代理对象的句柄。

5、获得配置文件代理对象后,可以立即将其用于监视连接状态和执行其他与该配置文件相关的操作。

以下代码片段显示了如何连接到 BluetoothHeadset 代理对象,以便能够控制耳机配置文件:

BluetoothHeadset mBluetoothHeadset;

// Get the default adapter
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

// Establish connection to the proxy.
mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);

private BluetoothProfile.ServiceListener mProfileListener = new BluetoothProfile.ServiceListener() {
    public void onServiceConnected(int profile, BluetoothProfile proxy) {
        if (profile == BluetoothProfile.HEADSET) {
            mBluetoothHeadset = (BluetoothHeadset) proxy;
        }
    }
    public void onServiceDisconnected(int profile) {
        if (profile == BluetoothProfile.HEADSET) {
            mBluetoothHeadset = null;
        }
    }
};

// ... call functions on mBluetoothHeadset

// Close proxy connection after use.
mBluetoothAdapter.closeProfileProxy(mBluetoothHeadset);

供应商特定的 AT 命令

从 Android 3.0 开始,应用可以注册接收耳机所发送的预定义的供应商特定 AT 命令的系统广播(例如 Plantronics +XEVENT 命令)。

例如,应用可以接收指示所连接设备的电池电量的广播,并根据需要通知用户或采取其他操作。 为

ACTION_VENDOR_SPECIFIC_HEADSET_EVENT intent

创建广播接收器,以处理耳机的供应商特定 AT 命令。

下面是获取蓝牙耳机电池电量:

 /**
     * Handle {@link BluetoothHeadset#ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intent
     * @param intent must be {@link BluetoothHeadset#ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intent
     */
    @VisibleForTesting
    void onVendorSpecificHeadsetEvent(Intent intent) {
        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
        if (device == null) {
            Log.e(TAG, "onVendorSpecificHeadsetEvent() remote device is null");
            return;
        }
        String cmd =
                intent.getStringExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD);
        if (cmd == null) {
            Log.e(TAG, "onVendorSpecificHeadsetEvent() command is null");
            return;
        }
        int cmdType = intent.getIntExtra(
                BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE, -1);
        // Only process set command
        if (cmdType != BluetoothHeadset.AT_CMD_TYPE_SET) {
            debugLog("onVendorSpecificHeadsetEvent() only SET command is processed");
            return;
        }
        Object[] args = (Object[]) intent.getExtras().get(
                BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS);
        if (args == null) {
            Log.e(TAG, "onVendorSpecificHeadsetEvent() arguments are null");
            return;
        }
        int batteryPercent = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
        switch (cmd) {
            case BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT:
                batteryPercent = getBatteryLevelFromXEventVsc(args);
                break;
            case BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV:
                batteryPercent = getBatteryLevelFromAppleBatteryVsc(args);
                break;
        }
        if (batteryPercent != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
            updateBatteryLevel(device, batteryPercent);
            infoLog("Updated device " + device + " battery level to "
                    + String.valueOf(batteryPercent) + "%");
        }
    }

健康设备配置文件

  • 源设备:在 HDP 中定义的角色。源设备是将医疗数据传输到 Android 手机或平板电脑等智能设备的健康设备(体重秤、血糖仪、温度计等)。

  • 汇集设备:在 HDP 中定义的角色。在 HDP 中,汇集设备是接收医疗数据的智能设备。 在 Android HDP 应用中,汇集设备表示为 BluetoothHealthAppConfiguration 对象。

  • 注册:指的是注册特定健康设备的汇集设备。

  • 连接:指的是开放健康设备与 Android 手机或平板电脑等智能设备之间的通道。

创建 HDP 应用

以下是创建 Android HDP 应用所涉及的基本步骤:

1、获取 BluetoothHealth 代理对象的引用。

2、与常规耳机和 A2DP 配置文件设备相似,您必须使用 BluetoothProfile.ServiceListener 和 HEALTH 配置文件类型来调用 getProfileProxy(),以便与配置文件代理对象建立连接。

3、创建 BluetoothHealthCallback 并注册充当健康汇集设备的应用配置 (BluetoothHealthAppConfiguration)。

4、建立到健康设备的连接。一些设备将会发起该连接。 对于这类设备,无需执行该步骤。
成功连接到健康设备后,使用文件描述符对健康设备执行读/写操作。

5、接收的数据需要使用实现了 IEEE 11073-xxxxx 规范的健康管理器进行解释。

完成后,关闭健康通道并取消注册该应用。该通道在长期闲置时也会关闭。

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

推荐阅读更多精彩内容