在写这个App之前我上官网找了他的官方文档,里面有教大家怎么查找设备,配对设备,连接设备,使设备可发现已经如何传输数据。但是官网上的主要是面向于手机的连接(自认为),用Socket连接,虽然我还不太清楚Socket原理和机制。我做的这个是连接一个自定义的模块(CC2540),profile是自定义的,用socket好像连接不上,我是用BluetoothGatt连接的。一般手机上的每一个Service都有国际上规定的UUID,例如串口服务,信息同步服务等。这个百度上都可以查到。我在另一篇博文上,将各个Service的UUID列举出来[http://blog.csdn.net/yiyi_g/article/details/51864203]。对于自定义的蓝牙用Socket的话在连接的这一个部分就会发生错误。
首先对官网上的一些方法做一个总结。这里有一个中文版的API[http://www.android-doc.com/guide/topics/connectivity/bluetooth.html]可以参考
所有蓝牙API都在android.bluetooth包下,下面有一些类和接口的摘要,你可能需要它们来建立蓝牙连接。
BluetoothAdapter 本地蓝牙适配器。是所有蓝牙交互的入口。
BluetoothDevice 代表一个远程的设备
BluetoothSocket 代表一个蓝牙socket的接口,这是一个连接点,语序一个应用于其他蓝牙设备通过InputStream和OutputStream交换数据
BluetoothServerSocket 代表一个开放服务器的socket,监听接受的请求
BluetoothClass 描述一个蓝牙设备的基本特性和性能。这是一个只读的属性集合,它定义了设备的主要和次要的设备类以及它的服务。但是,它没有描述所有的蓝牙配置和设备支持的服务,它只是暗示了设备的类型。
BluetoothProfile.ServiceListener 一个接口,当BluetoothProfile IPC客户端从服务器上建立连接或者断开连接时,负责通知他们(也就是,运行在特性配置的内务服务)
BluetoothProfile 描述一个蓝牙配置文件的接口,一个Bluetooth profile是一个基于蓝牙的通信无线接口定义。
BluetoothGattCallback 一个抽象类,你可以使用它来实现BluetoothGatt的回调函数,包括读写、连接状态改变等
其他的官方文档上有,没有怎么接触过也不是很理解就暂时不进行介绍。接下来介绍一些方法。
1.权限设置
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
2.判断蓝牙是否可用而且是否开启
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null)
{
connectStatetx.setText("设备不支持蓝牙");
}
//如果没有启用蓝牙设备,然后启用设备的可发现性将自动启用蓝牙。所以如果要启动设备的可发现性,可以不定义启动蓝牙的代码
//如下是启动蓝牙的代码
else if(!mBluetoothAdapter.isEnabled())
{
//蓝牙没有开启时,开启蓝牙
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent,REQUEST_ENABLE_BT);
}
else
{
Intent intent1 = new Intent(MainActivity.this,BluetoothActivity.class);
startActivity(intent1);
}
如果开启蓝牙成功,你的activity在onActivityResult()回调函数中接受一个RESULT_OK结果代码。如果蓝牙因为一个错误(或者用户相应”否“)没有开启,那么结果代码就是RESULT_CANCELED。
可选的,你的应用也可以监听ACTION_STATE_CHANGED广播Intent,任何时候蓝牙状态改变了系统将会广播该Intent。这个广播包含了额外的变量EXTRA_STATE 和EXTRA_PREVIOUS_STATE,分别包含了新的和老的蓝牙状态。这些额外的变量可能值是STATE_TURNING_ON,STATE_ON,STATE_TURNING_OFF和STATE_OFF。监听这个广播对于检测你的应用运行时蓝牙状态的变化是非常有用的
3.查找设备
- 查找可发现设备
mBluetoothAdapter.startDiscovery();
查找到设备后,系统会发送一个ACTION为ACTION_FOUND的intent。intent包含EXTRA_DEVICE和EXTAR_CLASS,里面包含的的对象分别是BluetoothDevice和BluetoothClass.可通过一下方法获得远程设备
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- 查找已配对的设备
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
//如果有配对的设备
if(pairedDevices.size()>0) {
//依次操作配对设备
for (BluetoothDevice device : pairedDevices)
{
//添加配对设备的名字和地址到一个数组适
配器且显示在一个ListView里
Intent deviceIntent = new Intent();
deviceIntent.setAction(DEVICE_MASSAGE);
Bundle bundle = new Bundle();
bundle.putString("name", device.getName());
bundle.putString("address", device.getAddress());
deviceIntent.putExtras(bundle);
sendBroadcast(deviceIntent);
}
}
- 使设备可被其他设备发现
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
enableBtIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,90);
startActivityForResult(enableBtIntent,REQUEST_DISCOVERABLE);
开始这个方法后,系统会跳出一个询问你是否允许使设备可发现的对话框。如果用户点击“NO”或者有错误产生,返回的结果码是RESULT_CANCELED.如果用户点击的是“YES”,返回的结果码是你设置的的可发现的时间。(官网上说是设置可发现时间为0时,则可以使设备一直处于可发现状态。注意RESULT_CANCELED的值也为0,所以如果你希望你的设备处于一直处于可发现的状态,最好不要用onActivityResult()的方法。可以注册一个ACTION为ACTION_SCAN_MODE_CHANGED的监听器,它可以监听可发现状态是否改变)ACTION为ACTION_SCAN_MODE_CHANGED的监听器包含EXTRA_SCAN_MODE和EXTRA_PREVIOUS_SCAN_MODE.对应的值包括 SCAN_MODE_CONNECTABLE_DISCOVERABLE(设备处于可发现模式), SCAN_MODE_CONNECTABLE(设备不不可发现,但是仍然一直接受连接), or SCAN_MODE_NONE(设备不可发现,也不接受连接)。
4.配对已发现的设备
String address = intent.getStringExtra("address");
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
try
{
Method createBondMethod = BluetoothDevice.class.getMethod("createBond");
createBondMethod.invoke(device);
}catch (Exception e)
{
e.printStackTrace();
}
注册ACTION为BluetoothDevice.ACTION_BOND_STATE_CHANGED的BroadcastReceiver可以监听配对状态。当连配对状态改变时,intent里的BluetoothDevice.EXTRA_DEVICE对应的一个BluetoothDevice.
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
int bondState = device.getBondState();
switch(bondState)
{
case BluetoothDevice.BOND_NONE:
...
break;
case BluetoothDevice.BOND_BONDING:
...
break;
case BluetoothDevice.BOND_BONDED;
...
break;
}
5.连接设备
- 作为服务器端连接
- 1.用 listenUsingRfcommWithServiceRecord(String, UUID)得 到一个BluetoothServerSocket。
UUID uuid = UUID.fromString(MY_uuid);
BluetoothServerSocket tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, uuid);
2.通过调用accept()开始监听连接请求。
注意当accept()返回一个 BluetoothSocket是,该socket已经被连接了,因此你不应该调用connect();
这一个阻塞调用。在一个连接被接受或一个异常出现时,它将会返回。只有当一个远程设备使用一个UUID发送了一个连接请求,并且该UUID和正在监听的服务器socket注册的UUID相匹配时,一个连接才会被接受。成功后,accept() 将会返回一个已连接的 BluetoothSocket。3.调用close(),除非你想要接受更多的连接。
这将释放服务器socket和它所有的资源,但是不会关闭 accept()返回的已连接的 BluetoothSocket。不同于TCP/IP,RFCOMM仅仅允许每一个通道上在某一时刻只有一个已连接的客户端,因此在大多数情况下在接受一个已连接的socket后,在BluetoothServerSocket上调用 close() 是非常必要的。
accept() 不应该再主活动UI线程上执行,因为它是一个阻塞调用,并且将会阻止任何与应用的交互行为。它通常在你的应用管理的一个新的线程中使用一个BluetoothServerSocket 或 BluetoothSocket 来完成所有工作。为了中止一个阻塞调用,例如accept(),从你的其他线程里在BluetoothServerSocket (或 BluetoothSocket) 上调用 close() ,然后阻塞调用就会立即返回。注意在 BluetoothServerSocket 或 BluetoothSocket 上所有的方法都是线程安全的。
- 作为客户端的连接
- 1.使用 BluetoothDevice,通过调用createRfcommSocketToServiceRecord(UUID)来得到一个 BluetoothSocket 。这将初始化一个BluetoothSocket,它连接到该BluetoothDevice。
这里传递的UUID必须和服务器设备开启它的 BluetoothServerSocket时使用的UUID相匹配。 2.通过调用connect()初始化一个连接。
执行这个调用时,系统将会在远程设备上执行一个SDP查找工作,来匹配UUID。如果查找成功,并且远程设备接受了连接,它将会在连接过程中分享RFCOMM通道,而 connect()将会返回。这个方法是阻塞的。如果,处于任何原因,该连接失败了或者connect()超时了(大约12秒以后),那么它将会抛出一个异常。
因为connect()是一个阻塞调用,这个连接过程应该总是在一个单独的线程中执行。
注意:你应该总是确保在你调用connect()时设备没有执行设备查找工作。如果正在查找设备,那么连接尝试将会很大程度的减缓,并且很有可能会失败。注意到在创建一个连接之前调用了cancelDiscovery()。你应该在连接前总是这样做,而不需要考虑是否真的有在执行查询任务(但是如果你想要检查,调用 isDiscovering())。当你使用完你的 BluetoothSocket后,总是调用close()来清除资源。这样做将会立即关闭已连接的socket,然后清除所有的内部资源。
- 用BluetoothGatt连接
//定义一个线程,执行连接设备的程序。因为connect()方法是一个阻塞调用
public class ConnectThread extends Thread
{
private final BluetoothSocket mSocket;
private final BluetoothDevice mDevice;
public ConnectThread(BluetoothDevice device)
{
mDevice = device;
BluetoothSocket tmp = null;
mBlutoothGatt = mDevice.connectGatt(BTService.this,false,mGattCallback);
}
public void run()
{
//取消发现。因为这个方法会减慢连接的速度
if(mBluetoothAdapter.isDiscovering())
{
mBluetoothAdapter.cancelDiscovery();
//通知Activity已经取消了发现
Intent cancelDiscover = new Intent();
cancelDiscover.setAction(Cancel_Discover);
sendBroadcast(cancelDiscover);
}
int connectState = mDevice.getBondState();
switch(connectState)
{
case BluetoothDevice.BOND_NONE:
try
{
Method createBondMethod = BluetoothDevice.class.getMethod("createBond") ;
createBondMethod.invoke(mDevice);
}catch (Exception e){e.printStackTrace();}
break;
case BluetoothDevice.BOND_BONDING:
break;
case BluetoothDevice.BOND_BONDED:
try
{
mBlutoothGatt.connect();
}catch (Exception e)
{
e.printStackTrace();
//连接失败
return;
}
System.out.println("正在连接");
break;
}
}
public void cancle()
{
mBlutoothGatt.disconnect();
}
}
其中mGattCallback是一个在主程序实现的BluetoothGattCallback对象。可以实现BluetoothGatt的回调函数,监听连接时各种状态的改变。
6.传输数据
- 使用已连接的BluetoothSocket
- 1.分别通过getInputStream() 和 getOutputStream()来得到 InputStream 和 OutputStream 来控制socket之间的传输。
- 2.Read and write data to the streams with read(byte[]) and write(byte[]). 使用 read(byte[]) 和 write(byte[])来向数据流中读取和写入数据。
首先并且最重要的是,你应该为所有输入和输出的数据流使用一个专属的线程。这是十分重要的,因为read(byte[]) 和
write(byte[])方法都是阻塞调用。 read(byte[])将会发生阻塞知道送数据流中读取到了一些东西。
write(byte[])不经常发生阻塞,但是当远程设备没有足够迅速地调用
read(byte[])而中间缓冲区已经负载时可以阻塞。因此,你的线程中的主要循环应该是专门从InputStream中读取数据的。一个单独的公共方法可以被用于初始化向OutputStream 中写入数据。
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) { }
}
}
构造器需要必须的数据流,并且一旦执行,线程将会等待InputStream 中来的数据。当 read(byte[])
返回一些数值时,数据将会使用一个父类的一个成员变量句柄发送给主活动。然后它返回并继续等待更多的数据。
发送数据只需要在主活动中调用线程的 write()方法,并将需要发送数据传递给它即可。这个方法然后调用
write(byte[])来向远程设备发送数据。
线程的cancel()
方法是重要的,以便连接可以在任何时间被中断(通过关闭BluetoothSocket)。这个方法应该在你完成蓝牙连接后总是被调用。
向characteristic直接写入数值
一个蓝牙设备包括多个Service,每个Service包含多个characteristic,每个characteristic又包括多个description和一个value。每个Service和characteristic都有他们各自的uuid,都可以通过他们各自的getUuid()方法获得。
//定义一个新的线程用于读写
private class ConnectedThread extends Thread
{
public ConnectedThread()
{
}
public void run()
{
mBlutoothGatt.readCharacteristic(characteristicALL);
}
public void writeCharacteristic
(BluetoothGattCharacteristic characteristic,byte[] bytes)
{
if (mBluetoothAdapter == null || mBlutoothGatt == null)
{
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
characteristic.setValue(bytes);
boolean s = mBlutoothGatt.writeCharacteristic(characteristic);
}
}
当调用BluetoothGatt的mBlutoothGatt.readCharacteristic(characteristicALL);和
mBlutoothGatt.writeCharacteristic(characteristic)时会回调BluetoothGattCallback的特征读写方法
下面是我实现的部分BluetoothGattCallback
// Implements callback methods for GATT events that the app //cares about. For example,
// connection change and services discovered.
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback()
{
@Override
public void onCharacteristicRead (BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status)
{
String value = null;
String valueW = null;
String valueR= null;
String valueG = null;
String valueB = null;
if(UUID_HEART_RATE_MEASUREMENT.equals
(characteristic.getUuid()))
{
int flag = characteristic.getProperties();
int format = -1;
if ((flag & 0x01) != 0)
{
format = BluetoothGattCharacteristic.FORMAT_UINT16;
} else
{ format= BluetoothGattCharacteristic.FORMAT_UINT8;
}
final int heartRate = characteristic.getIntValue(format, 1);
value = String.valueOf(heartRate);
} else
{
final byte[] data = characteristic.getValue();
if (data != null && data.length > 3) {
valueW = String.format("%d", data[0]);
valueR = String.format("%d", data[1]);
valueG = String.format("%d", data[2]);
valueB = String.format("%d", data[3]);
Intent intent = new Intent(DATA_READ_OVER);
Bundle bundleX = new Bundle();
bundleX.putString(WvalueX,valueW);
bundleX.putString(RvalueX,valueR);
bundleX.putString(GvalueX,valueG);
bundleX.putString(BvalueX,valueB);
intent.putExtras(bundleX);
sendBroadcast(intent);
} else {
new ConnectedThread().run();
}
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt,int status)
{
for(BluetoothGattService gattService : mBlutoothGatt.getServices())
{
if(gattService.getUuid().equals(device_uuid))
{
Log.e(TAG,"获得了所需要的服务");
while(characteristicALL == null)
{
characteristicALL = gattService.getCharacteristic(characteristic_all_uuid);
}
}
}
}
};