自2.0之后,Android平台就开始支持蓝牙通信,它允许一个设备通过无线网络与其他蓝牙设备交换数据。应用程序框架提供通过蓝牙的api访问蓝牙功能。这些api允许应用程序通过无线连接到其他蓝牙设备,使用点对点或者多点无线功能。
使用蓝牙的api,一个Android的应用程序可以进行如下的操作:
扫描其他的蓝牙设备
通过当地的蓝牙适配器查询已配对的设备
建立RFCOMM渠道
通过搜索服务连接到其他的设备
与其他设备传输数据
管理多个连接
代表当地的蓝牙适配器(无线蓝牙),它是所有蓝牙功能的入口,使用它,可以去查询你的蓝牙的开启状态,搜索状态,连接状态。可以去搜索和连接其他的蓝牙设备。可以用一个已知的MAC地址去实例化一个BluetoothDevice,并创建一个BluetoothServerSocket监听通信。
代表一个远程的蓝牙设备。通过它可以去请求连接一个远程的蓝牙设备。也可以查新一个远程设备的信心,如名字,mac地址,类型和连接状态。
代表一个蓝牙socket接口(类似于TCPsocket)。这是一个运行设备之间使用InputStream和OutputStream进行数据交换的连接点。
代表一个开放的服务socket,监听接入的请求(类似于TCP的ServerSocket)。为了连接两个Android蓝牙设备,其中的一个设备必须用这个类打开一个服务socket。当一个远程设备请求一个连接的时候并且连接被接受的时候,BluetoothServerSocket将返回一个BluetoothSocket。
描述一个蓝牙设备的基本特征和功能。这是一个只读属性集,定义了设备的主要和次要设备类型和它的服务。虽然它可能并不能完全描述一个设备支持的服务和蓝牙配置文件,但是对设备类型的提示还是有一定的作用。
这些api位于android.bluetooth包下,它只是基础的部分,但是已经可以完成基本的蓝牙功能。
一、(Bluetooth Permissions)权限
为了能在应用程序中使用蓝牙功能,你至少要在manifest文件中声明下面两个权限中的一个:蓝牙权限和蓝牙管理权限。
<uses-permission android:name="android.permission.BLUETOOTH" /> <!-- 蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <!-- 蓝牙管理权限 -->
蓝牙权限:为了执行任何蓝牙通信,如请求连接,接受一个连接,传输数据。
蓝牙管理权限:为了启动搜索服务和设置蓝牙。大部分蓝牙程序都需要使用这个权限。并且如果有这个权限,那么必须要有第一个蓝牙权限。
在自己的蓝牙应用程序中,最好还是将这两个权限都加上。
二、(Setting Up Bluetooth)蓝牙设置
在使用蓝牙进行通信前,你需要检测你的手机是否支持蓝牙,并且蓝牙是否可用。
如果你的手机不支持蓝牙,那么你就不能去使用蓝牙的功能。如果支持但是没有开启,那么你可以直接去请求用户开启蓝牙,而不用离开你的程序去系统设置里面设置。
该过程需要使用BluetoothAdapter
类来完成:
1、获得BluetoothAdapter
,判断蓝牙是否可用
任何蓝牙的活动都离不开BluetoothAdapter。调用BluetoothAdapter的静态方法getDefaultAdapter()
。将返回一个代表你自己的设备蓝牙适配器(无线蓝牙)。
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
// 该设备不支持蓝牙,那么你就不可以继续执行其他的蓝牙操作
}else{
//执行其他的蓝牙操作
}
2、如果确定支持蓝牙,在判断蓝牙当前的状态。
①、判断蓝牙的状态
蓝牙的状态分为四种:(STATE_OFF
)已关闭,(STATE_TURNING_ON
)正在开启,(STATE_ON
)已开启,(STATE_TURNING_OFF
)正在关闭。
在BluetoothAdapter中有着四种状态的声明:
public static final int STATE_OFF = 10;
public static final int STATE_TURNING_ON = 11;
public static final int STATE_ON = 12;
public static final int STATE_TURNING_OFF = 13;
我们可以通过调用getState()方法来获取设备蓝牙当前的状态。
//因为查看BluetoothAdapter类,四个状态的声明都是连续的int值,所以用state-10就是数组中对应的字符串
String[] stateStr=new String[]{
"已关闭","正在开启","已开启","正在关闭"
};
// 获取当前的蓝牙状态
int currentState = mBluetoothAdapter.getState();
System.out.println(stateStr[currentState-10]);
也可以直接调用isEnabled()
方法来判断蓝牙是否已经启用。该方法返回值为boolean,true代表可用,false为不可用。
②、开启和关闭蓝牙
根据上面得到的蓝牙状态,我们可以选择开启或者去关闭蓝牙。在一个用户体验良好的程序中,显然如果直接给出个Toast叫用户自己去系统设置里面进行操作,真的很戳。所以我们可以使用下面的方法,在不离开你的应用的情况下,开启或者关闭蓝牙。
可以使用startActivityForResult()并给出Action为BluetoothAdapter.ACTION_REQUEST_ENABLE的Intent,来请求启动蓝牙。
if (!mBluetoothAdapter.isEnabled()) {// 蓝牙未开启,请求开启蓝牙
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
这段代码将会弹出一个对话框来向用户请求开启蓝牙,REQUEST_ENABLE_BT(要大于0)是请求的状态码,我们需要重写onActivityResult()
方法来获取开启蓝牙是否成功。如果成功将返回RESULT_OK
,如果开启失败或者是用户不同意开启蓝牙,那么将返回RESULT_CANCELED。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_BT) {
if (resultCode == Activity.RESULT_OK) {
Toast.makeText(this, "蓝牙开启成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "蓝牙开启失败", Toast.LENGTH_SHORT).show();
}
}
}
还可以在不给予用户提示的情况下,偷偷的开启:不过这种情况下,也要去判断时候开启成功,enable()方法的返回值为boolean,true表示已经成功的从关闭状态到正在开启状态,过段时间就会从正在开启状态转换到开启状态。用这种方法,我们应该注册一个BrodcastReceiver来监听蓝牙状态的改变(后面提到)。不过不提倡这种方式,应该用第一种让用户选择的方式,而不是自己偷偷的区开启。
if (!mBluetoothAdapter.isEnabled()) {// 蓝牙未开启,请求开启蓝牙
mBluetoothAdapter.enable();
}
关闭只提供了一种方式:
if (mBluetoothAdapter.isEnabled()) {
mBluetoothAdapter.disable();
}
同样在api上也提到,不提倡这种方式,但是貌似没找到别的方式关闭蓝牙。
③、蓝牙状态的监听
蓝牙的状态的改变,会发送BluetoothAdapter.ACTION_STATE_CHANGED
="android.bluetooth.adapter.action.STATE_CHANGED"的广播,可以注册一个BroadcastReceiver来监听蓝牙状态的改变。并且改广播的intent会带两个参数,如下:
private class BtStateChangeReceiver extends BroadcastReceiver {
String[] stateStr=new String[]{
"已关闭","正在开启","已开启","正在关闭"
};
@Override
public void onReceive(Context context, Intent intent) {
if (!intent.getAction().equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
return;
}
int nowState=intent.getExtras().getInt(BluetoothAdapter.EXTRA_STATE);//当前的状态
int oldState=intent.getExtras().getInt(BluetoothAdapter.EXTRA_PREVIOUS_STATE);//之前的状态
String nowStateStr=stateStr[nowState-10];
String oldStateStr=stateStr[oldState-10];
System.out.println("蓝牙状态从 "+oldStateStr+"----------->"+nowStateStr);
}
}
然后注册该广播接受者,就可以监听蓝牙状态的改变:
IntentFilter intentFilter=new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(new BtStateChangeReceiver(), intentFilter);
三、(Finding Devices)查找设备
使用BlueAdapter,可以去搜索远程的设备和查询已经配对的设备。
搜索设备是一个扫描的程序,它可以去寻找附近已经开启了蓝牙的设备,并且请求每一个设备的信息。这些信息包括设备名称,类型,和唯一的MAC地址。
根据搜索的得到的BluetoothDevice,我们可以去请求配对或者连接。
1、查询已经配对的设备
在开始执行搜索之前,去查询一下已经配对的设备列表时相当有意义的。可以调用getBondedDevices()
方法,该方法将返回一个BluetoothDevice
的集合。如下:
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、搜索设备
调用startDiscovery()
方法就可以开始搜索设备。这是一个异步的方法,这个方法会直接返回一个boolean值,true代表开始搜索成功,false代表失败。这个搜索的过程大概会持续12秒。
要想得到搜索的结果,需要设置一个广播接受者去接受搜索结果的广播。该广播会带两个参数进来,一个是代表蓝牙设备的BluetoothDevice,一个是代表蓝牙类型的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);
System.out.println("设备名称:" + device.getName() + "\n" + "设备地址:"
+ device.getAddress());
// Get the BluetoothClass object from the Intent
BluetoothClass btClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS);
// the method .getDeviceClass() while return a int value that represent the device class which is define in the BluetoothClass.Device
System.out.println("设备类型:"+btClass.getDeviceClass());
} else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_STARTED)) {
System.out.println("开始搜索");
} else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
System.out.println("搜索完毕或被终止");
}
}
};
然后注册这个广播,并且开始搜索:
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy
boolean startSuc = mBluetoothAdapter.startDiscovery();
if (startSuc) {
Toast.makeText(this, "开始搜索成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "开始搜索失败", Toast.LENGTH_SHORT).show();
}
注意:执行搜索设备对于蓝牙适配器是一个沉重的过程,会消耗大量的资源。在你执行连接之前,记得要调用cancelDiscovery()
去关闭搜索。并且,如果你已经连接了一个设备,并且还去执行搜索设备,那么就会明显的减少连接的带宽。所以要随时记得关闭搜索设备。如下:
if(mBluetoothAdapter.isDiscovering()){
boolean endSuc = mBluetoothAdapter.cancelDiscovery();
if (endSuc) {
Toast.makeText(this, "停止搜索成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "停止搜索失败", Toast.LENGTH_SHORT).show();
}
}else {
Toast.makeText(this, "当前没有进行搜索", Toast.LENGTH_SHORT).show();
}
四、(Enabling discoverability)能被其他的设备查找并建立连接
上面提到的是由你去搜索其他的蓝牙设备,这种情况下,你的蓝牙必须要是开启的。
但是蓝牙开启的情况下,你不一定能被其他的蓝牙设备搜索到。
在API中,将蓝牙能被其他设备搜索到,叫做scan。有三种scan模式,定义在API中:
public static final int SCAN_MODE_NONE = 20; //不能被其他设备搜索到
public static final int SCAN_MODE_CONNECTABLE = 21; //不能被其他设备搜索到,但是可以被连接到
public static final int SCAN_MODE_CONNECTABLE_DISCOVERABLE = 23; //可以被搜索到,也可以被连接到
如果你的应用程序只是要去连接一个远程设备,是没必要开启被搜索功能的。只有当你的应用程序需要作为一个蓝牙服务端,去接受其他设备的请求时候才需要开启被搜索功能。
可以使用下面的方式去开启被搜索功能:
BluetoothAdapter mAdapter=BluetoothAdapter.getDefaultAdapter();
int scanMode=mAdapter.getScanMode();
if(scanMode!=BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE){
Intent discoverableIntent = new Intent(
BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(
BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivityForResult(discoverableIntent);
}
开启被搜索有时间的限制问题,默认情况下是120秒,我们可以再Intent带上BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION的参数,来指定一个时间。这个时间值是0到3600秒之间。如果为0的话,就是不限制时间。
执行上面的代码后,会弹出一个提示用户开启被搜索的对话框。同样的,我们可以再onActivityResult来得到用户的选择。
同样的,被搜索模式的改变,也会产生一个系统的广播,我们可以注册下面的广播接受者来监听模式的改变:
private final class ScanModeChangeBroadcastReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
if(!intent.getAction().equals(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED))//模式改变的广播
return;
int nowMode=intent.getExtras().getInt(BluetoothAdapter.EXTRA_SCAN_MODE);//当前的模式
int oldMode=intent.getExtras().getInt(BluetoothAdapter.EXTRA_PREVIOUS_SCAN_MODE);//之前的模式
}
}
五、BluetoothAdapter的其他基本方法
public String getAddress() //获取本地蓝牙设备的mac地址
public String getName() //获取本地蓝牙设备的名称
public boolean setName(String name) //设置本地蓝牙设备的名称
public BluetoothDevice getRemoteDevice(String address) //根据一个mac地址,得到一个代表远程设备的BluetoothDevice
public static boolean checkBluetoothAddress(String address) //检测一个mac地址是否是有效的,即格式是否正确
当更改了蓝牙适配器的名字的时候,会发送一个广播,我们可以注册下面的广播接受者,来监听这个改变:
private final class LocalNameChangeBroadcastReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
if(!intent.getAction().equals(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED))
return;
String name=intent.getExtras().getString(BluetoothAdapter.EXTRA_LOCAL_NAME);//更改的名字
}
}