Android蓝牙(API介绍)

1.简介

通过蓝牙API,可以实现以下内容:

扫描其他蓝牙设备

查询配对蓝牙设备的本地蓝牙适配器

创建RFCOMM通道

通过服务发现连接其他设备

与其他设备进行数据交互

管理多连接

2.基本要素

蓝牙相关的API都存在android.bluetooth包内,主要包括以下几个类和接口:

BluetoothAdapter

代表本地蓝牙适配器。BluetoothAdapter是所有蓝牙设备交互的入口。可以实现查找设备、遍历配对设备、通过已知的MAC地址实例化BluetoothDevice、创建BluetoothServerSocket与其他设备进行通信。

BluetoothDevice

代表一个远程蓝牙设备。可用于,通过BluetoothSocket请求与远程设备的连接,或者查询设备信息(名字、地址、级别与配对状态)。

BluetoothSocket

代表蓝牙socket接口(类似于TCP Socket)。这是允许应用与其他蓝牙设备进行数据交换的连接点,采用InputStream和OutputStream。

BluetoothServerSocket

代表监听外来请求的开放服务socket(类似于TCP ServerSocket)。为连接两个Android设备,其中一个设备用这个类必须开放服务socket。当远程蓝牙设备向此设备进行连接请求时,当连接被接受时,BluetoothServerSocket会返回一个连接的BluetoothSocket

BluetoothClass

描述一个蓝牙设备的基本特点和性能。它是一个只读的属性集合,定义了设备主要和次要的级别和服务。然而,它并没有完全描述设备所支持的所有的蓝牙配置文件和服务,但是,对于设备类型来说是非常有用的。

BluetoothProfile

代表蓝牙协议的接口。是设备间基于蓝牙通信的无线接口说明。例如Hands-Free Profile (HFP)。

BluetoothHeadset

提供用于手机的蓝牙耳机支持。包括Bluetooth Headset和Hands-Free (v1.5) 协议。

BluetoothA2dp

定义高质量音频可以从一个设备通过蓝牙连接传输到另一个设备。"A2DP" 代表Advanced Audio Distribution Profile.

BluetoothHealth

代表健康设备配置文件协议,控制蓝牙设备。HDP Health Device Profile.

BluetoothHealthCallback

一个抽象类,用于实现BluetoothHealth回调。必须继承这个类并实现回调方法来接受应用注册状态和蓝牙通道状态改变。

BluetoothHealthAppConfiguration

代表一个蓝牙健康的第三方应用与远程蓝牙健康设备注册通信应用配置。

BluetoothProfile.ServiceListener

一个当服务(运行特殊配置文件的内部服务)连接与断开时用于通知BluetoothProfileIPC代理的接口。

3.权限

<manifest...>

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

<uses-permissionandroid:name="android.permission. BLUETOOTH_ADMIN"/>

</manifest>

4.配置蓝牙

使用蓝牙进行通信前,必须验证设备支持蓝牙,并且蓝牙可用。主要分为两步:

获取BluetoothAdapter

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

if (mBluetoothAdapter == null) {

// Device does not support Bluetooth

}

使蓝牙可用

private static final int REQUEST_ENABLE_BT = 1024;

if (!mBluetoothAdapter.isEnabled()) {

Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);

startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);

}

protect void onActivityResult(int requestCode, int resultCode, Intent data){

if(requestCode == REQUEST_ENABLE_BT){

if(resultCode == RESULT_OK)  // YES 用户允许

if(resultCode == RESULT_CANCELED)  // NO 用户取消

}

}

监听蓝牙状态改变

String ACTION_STATE_CHANGED = "android.bluetooth.adapter.action.STATE_CHANGED"

String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED"

String ACTION_AUDIO_STATE_CHANGED =

"android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED"

常量在BluetoothAdapter中定义。

ACTION_STATE_CHANGED

当本地蓝牙适配器的状态改变时,采用此广播发送。intent中包含两个状态EXTRA_STATEEXTRA_PREVIOUS_STATE,分别表示当前状态和上一个状态。

可能包含 的值为:STATE_OFF,STATE_TURNING_ON,STATE_ON,STATE_TURNING_OFF

5.查找设备

BluetoothAdapter可以查询远程蓝牙设备,也可以获取配对设备列表。

获取配对列表

SetpairedDevices = 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());

}

}

搜索设备

调用boolean startDiscovery ()开始搜索,正确开始搜索时返回true。然后采用广播监听的形式获取蓝牙设备。

调用boolean cancelDiscovery ()停止搜索,一般在有远程设备连接时调用。

// 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

使能被发现

private static final int REQUEST_BT_DISCOVERABLE = 102;

Intent discoverableIntent = new

Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);

discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);

startActivityForResult(discoverableIntent,REQUEST_BT_DISCOVERABLE);

最大可查找时间为1小时,如果设为0时表示一直能被发现。

onActivityResult()中监听返回值,如果resultCode为间隔值,说明返回成功,如果返回码为RESULT_CANCELED,返回失败。

6.连接设备

要在两个设备间创建连接,需要同时实现服务端和客户端机制,因为一个设备必须打开服务socket,另一个必须初始化连接(用服务端设备的MAC地址进行初始化连接)。当服务端和客户端之间在同一个RFCOMM通道存在一个连接的BluetoothSocket时被认为是相互连接的。只有这样,每一个设备可以获取输入输出流并数据交换。

服务端和客户端可以采用不同的方式获取需要的BluetoothSocket。服务端接受外来连接,接收到BluetoothSocket。客户端想服务端打开一个RFCOMM通道时,接收到BluetoothSocket

作为服务端连接

1.调用listenUsingRfcommWithServiceRecord(String, UUID)获取BluetoothServerSocket

String 表示服务的名字;UUID代表Universally Unique Identifier。

2.调用accept()监听连接请求

如果成功,将返回一个连接的BluetoothSocket

3.要接受其他连接是,调用close()

4.样例

private class AcceptThread extends Thread {

private final BluetoothServerSocket mmServerSocket;

public AcceptThread() {

// Use a temporary object that is later assigned to mmServerSocket,

// because mmServerSocket is final

BluetoothServerSocket tmp = null;

try {

// MY_UUID is the app's UUID string, also used by the client code

tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME,

MY_UUID);

} catch (IOException e) { }

mmServerSocket = tmp;

}

public void run() {

BluetoothSocket socket = null;

// Keep listening until exception occurs or a socket is returned

while (true) {

try {

socket = mmServerSocket.accept();

} catch (IOException e) {

break;

}

// If a connection was accepted

if (socket != null) {

// Do work to manage the connection (in a separate thread)

manageConnectedSocket(socket);

mmServerSocket.close();

break;

}

}

}

/** Will cancel the listening socket, and cause the thread to finish */

public void cancel() {

try {

mmServerSocket.close();

} catch (IOException e) { }

}

}

作为客户端连接

1.调用BluetoothDevicecreateRfcommSocketToServiceRecord(UUID)方法,获取BluetoothSocket

2.调用connect()初始化连接

3.样例

private class ConnectThread extends Thread {

private final BluetoothSocket mmSocket;

private final BluetoothDevice mmDevice;

public ConnectThread(BluetoothDevice device) {

// Use a temporary object that is later assigned to mmSocket,

// because mmSocket is final

BluetoothSocket tmp = null;

mmDevice = device;

// Get a BluetoothSocket to connect with the given BluetoothDevice

try {

// MY_UUID is the app's UUID string, also used by the server code

tmp = device.createRfcommSocketToServiceRecord(MY_UUID);

} catch (IOException e) { }

mmSocket = tmp;

}

public void run() {

// Cancel discovery because it will slow down the connection

mBluetoothAdapter.cancelDiscovery();

try {

// Connect the device through the socket. This will block

// until it succeeds or throws an exception

mmSocket.connect();

} catch (IOException connectException) {

// Unable to connect; close the socket and get out

try {

mmSocket.close();

} catch (IOException closeException) { }

return;

}

// Do work to manage the connection (in a separate thread)

manageConnectedSocket(mmSocket);

}

/** Will cancel an in-progress connection, and close the socket */

public void cancel() {

try {

mmSocket.close();

} catch (IOException e) { }

}

}

7.管理连接

调用socket的getInputStream()getOutputStream()获得输入输出流

通过read(byte[])write(byte[])向流读写数据

样例

private class ConnectedThread extends Thread {

private final BluetoothSocket mmSocket;

private final InputStream mmInStream;

private final OutputStream mmOutStream;

public ConnectedThread(BluetoothSocket socket) {

mmSocket = socket;

InputStream tmpIn = null;

OutputStream tmpOut = null;

// Get the input and output streams, using temp objects because

// member streams are final

try {

tmpIn = socket.getInputStream();

tmpOut = socket.getOutputStream();

} catch (IOException e) { }

mmInStream = tmpIn;

mmOutStream = tmpOut;

}

public void run() {

byte[] buffer = new byte[1024];  // buffer store for the stream

int bytes; // bytes returned from read()

// Keep listening to the InputStream until an exception occurs

while (true) {

try {

// Read from the InputStream

bytes = mmInStream.read(buffer);

// Send the obtained bytes to the UI activity

mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)

.sendToTarget();

} catch (IOException e) {

break;

}

}

}

/* Call this from the main activity to send data to the remote device */

public void write(byte[] bytes) {

try {

mmOutStream.write(bytes);

} catch (IOException e) { }

}

/* Call this from the main activity to shutdown the connection */

public void cancel() {

try {

mmSocket.close();

} catch (IOException e) { }

}

}

8.采用Profiles工作

android蓝牙API提供下列协议的实现。

Headset:

BluetoothHeadset类,通过IPC控制蓝牙耳机服务的代理类;包括Bluetooth Headset 和 Hands-Free (v1.5) profiles;包括对AT指令的支持,见第9。

A2DP:

BluetoothA2dp类,通过IPC控制蓝牙A2DP服务的代理类。

Health Device:

BluetoothHealth类,蓝牙外接健康设备。

获取默认的蓝牙适配器。

getProfileProxy()

创建与协议对象的连接。

设置BluetoothProfile.ServiceListener。当与服务连接或断开时,监听器通知BluetoothProfileIPC代理。

onServiceConnected()中获取协议代理对象。

一旦拥有了协议代理对象,就可以用它管理连接状态,执行与协议相关的其他操作。

样例

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);

Vendor-specific AT指令

从3.0开始,应用可以注册接收由headsets设备发送的预定义的AT指令系统广播。例如,应用可以接收表明连接设备电量低的广播并且通知用户,或其他需要的行为。可以创建一个广播接收器用ACTION_VENDOR_SPECIFIC_HEADSET_EVENT作为intent的ACTION,处理headset设备的AT指令。

String ACTION = “android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT”

Health Device Profile

创建一个HDP应用

1.获取BluetoothHealth协议对象的引用。与HFP和A2DP类似,必须调用getProfileProxy()BluetoothProfile.ServiceListenerHEALTH协议类型

,建立协议代理对象的连接。

2.创建BluetoothHealthCallback回调和注册应用配置(BluetoothHealthAppConfiguration) 。

3.建立与健康设备的连接。

4.当连接成功,采用文件描述器想设备进行读写。

5.结束后,关闭通道,取消注册应用。

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

推荐阅读更多精彩内容

  • 蓝牙 注:本文翻译自https://developer.android.com/guide/topics/conn...
    RxCode阅读 8,466评论 11 99
  • 公司的项目最近需要用到蓝牙开发的相关内容,因此特地查阅了Google官方文档的内容并进行二次整理,希望能对需要学习...
    Chuckiefan阅读 32,244评论 44 123
  • 我早就发现课本教材远离经典是个极大缺失,小学生心无杂念,学习效果好,应多读钻石版的书籍,教材编写外国寓言过于简单,...
    我为云朵阅读 277评论 0 2
  • UITableView是iOS开发中常用到的控件,UITableView继承自UIScrollView,很强大,多...
    墨迹momo阅读 408评论 0 0
  • 女儿今年九月一号开始上幼儿园了,当时她才二岁四个月。经过两个月的适应,她已经接受了幼儿园,接送女儿上幼儿园的任务也...
    心遥远阅读 135评论 0 0