这是我第一次才CSDN上面写博客,不足之处请大家多多谅解。
蓝牙开发在之前的android开发中并不是很常用,尤其是在个人设备上,蓝牙模块甚至从买上就没有打开过。但是随着智能家居、可穿戴设备的普及,android蓝牙的功能也越发显得重要了。
那么首先我们说一下android中蓝牙开发的特点:
1)功耗极低,蓝牙设备耗电量几乎可以忽略不计
2)传输速度更快,只要在范围之内,可以很快的完成
3)不需要连接网络
4)传输稳定
我在工作中使用到的是android蓝牙和硬件设备通讯,接下来我尽可能的详细的把每一步都写下来。
首先是权限问题,两个android蓝牙开发必须的权限
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
因为我的程序运行的环境是在android6.0上,所以除了这两个权限之外,还需要一个权限
//选择其中一个,建议选择第一个
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
这是在android6.0系统上使用蓝牙需要访问手机位置信息,所以必须添加这两个权限之一。但是因为位置权限是敏感权限,我们必须动态申请这两个权限。
//在这里,我用android.permission.ACCESS_COARSE_LOCATION为例
private void requestPermission(){
if(ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)!=
PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
0);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//判断申请码
switch (requestCode){
case 0:
if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
//申请的第一个权限成功后
Toast.makeText(this,"申请权限成功",Toast.LENGTH_SHORT).show();
}else{
//申请的第一个权限失败后
finish();
}
break;
}
}
然后我们需要获取BluetoothAdapter对象,通过这个适配器 来得到我们想要的。
adapter = BluetoothAdapter.getDefaultAdapter();
通过获取这个对象,我们可以判断一下设备是否支持蓝牙功能
if (adapter == null) {
//设备不支持蓝牙
}else{
//设备支持蓝牙
}
然后我们可以通过这个适配器来控制我们自己的蓝牙模块
if (!adapter.isEnabled()) {
//直接打开蓝牙
adapter.enable();
}else{
//关闭蓝牙
adapter.disable();
}
//启动修改蓝牙可见性
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
//设置蓝牙可见性的时间,方法本身规定最多可见300秒 intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(intent);
接下来我们可以通过这个对象获取自己蓝牙的一些信息
String name = adapter.getName();//获取蓝牙的名称
String addre = adapter.getAddress();//获取蓝牙的地址
...
获取蓝牙已配对设备
public void match(){
Set<BluetoothDevice> devices = adapter.getBondedDevices();
if(devices.size() > 0){
for (BluetoothDevice bluetoothDevice : devices){
//判断是否存在
if(match.contains(bluetoothDevice.getName()+":"+ bluetoothDevice.getAddress())){
return;
}else {
match.add(bluetoothDevice.getName()+":"+ bluetoothDevice.getAddress());
}
}
}
}
搜索附近的蓝牙设备,在这里我们需要注册两个广播
//发现设备的广播
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(receiver, filter);
// 搜索完成的广播
IntentFilter filters = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
// 注册广播
registerReceiver(receiver, filters);
然后我们需要在广播中,将发现的广播添加到屏幕上。自定义广播事件就可以了。
定义是否搜索附近设备
//判断是否正在搜索
if (adapter.isDiscovering()){
//正在搜索,就停止
adapter.cancelDiscovery();
}else{
//不是就开始搜索
adapter.startDiscovery();
}
当搜索完附近设备后,点击设备我们需要进行匹配,在匹配之前,我们需要两个参数,一个是BluetoothDevice,一个是UUID。BluetoothDevice是指定你连接的设备,UUID是连接设备时建立通讯通道的id名称,UUID网上有很多生成方法。
//连接最好写到线程中
MY_UUID = UUID.fromString("2da12563-e314-1211-7789-" + device.getAddress().replace(":", ""));//建立UUID
mSocket= device.createRfcommSocketToServiceRecord(MY_UUID);//通过Socket通讯建立连接通道
mmSocket.connect();//请求匹配
如果匹配成功,系统会发送一条BluetoothDevice.ACTION_BOND_STATE_CHANGED广播,你可以通过这条广播将匹配好的设备添加到匹配列表。
匹配设备成功后,需要连接设备。注意匹配设备和连接设备是两回事,不可以混为一谈。连接是在匹配的基础上进行的。连接设备我们需要获取端口号,这时候也需要UUID,但是这个UUID和匹配时用的UUID不是同一个,这个是固定的。就目前我知道的是一个,并不是说没有其他的。
private static UUID UUID_CON = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
//通过地址获取到该设备
selectDevice = adapter.getRemoteDevice(address);
if(selectDevice != null){
//建立连接
try {
if (clientSocket == null) {
// 获取到客户端接口
clientSocket = selectDevice.createRfcommSocketToServiceRecord(UUID_CON);
// 向服务端发送连接
clientSocket.connect();
}
} catch (IOException e) {
e.printStackTrace();
//连接失败
clientSocket = null;
}
}
连接好以后基本代码就算OK了。接下来要做的是通讯,发送消息让对方接收显示。这里就需要开线程执行一个死循环了。当设备连接以后,一直循环接受数据,当发现有新数据发送过来后,解析数据并显示。
首先是建立一个接收信息的线程服务端
// 服务端接收信息线程
private class AcceptThread extends Thread {
private InputStream ins;// 获取到输入流
byte[] buffer = new byte[128]; // buffer store for the stream
int byt = 0;
public void run() {
try {
// 获取到输入流
ins = clientSocket.getInputStream();
// 循环来接收数据
while (true) {
byt = ins.read(buffer);
Thread.sleep(10);
String restr = "";
for (int i = 0; i < byt; i++) {
String hex = Integer.toHexString(buffer[ i ] & 0xFF);
if (hex.length() == 1)
hex = '0' + hex;
restr += hex.toUpperCase();
}
Message message = new Message();
message.obj = restr;
handler.sendMessage(message);
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
在这里我需要解释一下,因为我做的程序是需要和硬件通讯的,她接收和显示的数据都是十六进制的数据,所以我需要将接收到的byte[]转换成为十六进制的String类型的数据。Integer.toHexString(buffer[ i ] & 0xFF);就是将一个byte[]转成成十六进制的。
但是最重要的并不是这个,最值得注意的是在蓝牙通讯过程中,你需要延时发送信息和接收信息,延时发送我在下面会说到,我们先说延时接收消息,当你发送消息到机器时,机器会立刻给你发送一些数据,但是这些数据并不是全部,如果你接收了并且没有延时接收,那么你收到的数据就不会显示全。而第二次再次发送接收信息的时候会吧第一次残留的信息也返回回来。所以我们必须延时接收。
说明一点:如果你是在做手机之间的通讯,那就不用延时了,读和写都不用延时。
下面我们来说发送信息的请求。首先我们发送的是十六进制的字符串,我们必须先将输入的数据转换成为十六进制的byte[]。
如果是手机之间通讯,可以忽略这一步。
public static byte[] getHexBytes(String message) {
message = message.replace(" ","");
int len = message.length() / 2;
char[] chars = message.toCharArray();
String[] hexStr = new String[len];
byte[] bytes = new byte[len];
for (int i = 0, j = 0; j < len; i += 2, j++) {
hexStr[j] = "" + chars[i] + chars[i + 1];
bytes[j] = (byte) Integer.parseInt(hexStr[j], 16);
}
return bytes;
}
然后我们获取输入输出流将数据写出去
public void readAndWrite(String s){
// 这里需要try catch一下,以防异常抛出
try {
// 获取到输出流,向外写数据
os = clientSocket.getOutputStream();
in = clientSocket.getInputStream();
// 判断是否拿到输出流
if (os != null) {
// 需要发送的信息
byte[] bytes = StringTransformation.getHexBytes(s);
for(byte b : bytes){
try {
//延时10毫秒
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
os.write(b);
}
// 以utf-8的格式发送出去
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
// 如果发生异常则告诉用户发送失败
Toast.makeText(MainActivity.this, "发送信息失败", Toast.LENGTH_SHORT).show();
}
}
现在我们在开说延时发送信息。你可以看到上面的代码我每当一个byte就会让他sleep10毫秒,当然 这是因为我数据少。如果数据很多这样就不可以了,会很浪费时间,我们可以一次读多一些,然后再延时。
最后是蓝牙操作的结尾部分断开连接的实现
1)主动断开连接
clientSocket.close();
2)异常断开连接或者对方断开连接
如果是异常断开或者对方断开,你只需接收一个广播就可以了,如果接到这个广播了就说明连接以及断开了。
IntentFilter connectedFilters = new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED);
registerReceiver(receiver, connectedFilters);
好了,写到这里对于蓝牙通讯的内容基本都一步步写了下来,这是我第一次写博客,请大家多多关注,多多提出意见。谢谢。
**Sunanang**