蓝牙这个功能,搞了我3-4天的时间,网上的资料到时蛮多,但就是没有很全的。今天终于是把它搞定了,记录下。
蓝牙功能需要两个服务:
1、服务端
2、客户端
这个两个服务,与通常的说的C/S的意义是一致的,而且也是用的socket通信。
下面的代码以最简开发蓝牙为实例,来说明蓝牙的数据收发,所以有些代码不是很严谨和合理的。
服务端
1、开启蓝牙
在使用蓝牙前,需要先打开蓝牙设备
private BluetoothAdapter mBluetoothAdapter;
private boolean isEnableBluetooth = false;
public void openBl(View view) {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if(mBluetoothAdapter == null) {
isEnableBluetooth = false;
Toast.makeText(this, "前蓝牙设备驱动有问题", Toast.LENGTH_LONG).show();
log("当前蓝牙设备驱动有问题");
return;
}
if(!mBluetoothAdapter.isEnabled()) {
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent, EnableBluetoothRequestCode);
return;
}
isEnableBluetooth = true;
}
mBluetoothAdapter.isEnabled() 获取蓝牙的开启状态,如果蓝牙已经开启,返回true,否则返回false
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(intent, EnableBluetoothRequestCode); 异步提示用户打开蓝牙,即,弹出允许打开蓝牙框, 因为是异步等待用户打开蓝牙,所以这里重写 onActivityResult 去获取用户操作的结果, EnableBluetoothRequestCode 这个参数为用户自定义,但必须要大于0,主要用于在异步接收操作结果时的判断
重写 onActivityResult ,目的是接收用户打开蓝牙的操作结果
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == EnableBluetoothRequestCode) {
if(Activity.RESULT_OK == resultCode) {
Toast.makeText(this, "蓝牙成功打开", Toast.LENGTH_LONG).show();
isEnableBluetooth = true;
} else {
Toast.makeText(this, "蓝牙打开失败", Toast.LENGTH_LONG).show();
isEnableBluetooth = false;
}
}
}
2、开启Server
因为 server 端是阻塞式的等待用户连接,即,如果用户没有连接,则我什么都不做,一直等待用户来连接。
所以,这个服务就不能与主线程的 Activity 在同一个进程里面了,所以要新起一个 server thread:
private class ServerThread extends Thread {
private BluetoothServerSocket serverSocket;
@Override
public void run() {
try {
//serverSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(blName, UUID.fromString(MY_UUID));
serverSocket = mBluetoothAdapter.listenUsingInsecureRfcommWithServiceRecord(blName, UUID.fromString(MY_UUID));
if(serverSocket == null) {
log("server socket创建失败");
return;
}
} catch (Exception e) {
e.printStackTrace();
log("server socket创建失败, 异常发生");
}
log("等待客户端连接...");
while (true) {
try {
log("开始 accept");
BluetoothSocket socket = serverSocket.accept();
log("接受到了客户端");
BluetoothDevice device = socket.getRemoteDevice();
log("接受客户连接, name:"+device.getName());
if(socket.isConnected()) {
log("已建立连接");
InputStream tmpIn = socket.getInputStream();
byte[] buffer = new byte[1024];
int bytes = tmpIn.read(buffer);
log("收到数据:");
log(new String(buffer));
break;
}
} catch (Exception e) {
e.printStackTrace();
log("accept 发生异常");
break;
}
}
}
}
private BluetoothServerSocket serverSocket; 定义服务端 socket
serverSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(blName, UUID.fromString(MY_UUID)); serverSocket = mBluetoothAdapter.listenUsingInsecureRfcommWithServiceRecord(blName, UUID.fromString(MY_UUID)); 获取服务端 socket
这里有几个东西要讲下,当时是费了很多时间去理解和查资料的
(1) listenUsingRfcommWithServiceRecord 与 listenUsingInsecureRfcommWithServiceRecord 经测试,这两者的主要区别是:
-
listenUsingRfcommWithServiceRecord 在客户端与服务器端连接时需要双方验证匹配, 即,所谓的安全连接
-
listenUsingInsecureRfcommWithServiceRecord 在客户端与服务器端连接时不需要双方验证匹配, 即,所谓的不安全连接。客户端 connect 之后,两者就可以互发数据了,这个应该应用在无线设备上比较多,比如蓝牙耳机等
-
在测试过程中发现,服务器端和客户端的连接方式要一致,即,要么都是“安全连接”,要么都是“非安全连接”, 如果有一方为非安全连接的话,程序会出异常,具体的异常原因,目前还没有去研究
(2)listenUsingRfcommWithServiceRecord 参数的意义
public BluetoothServerSocket listenUsingRfcommWithServiceRecord(String name, UUID uuid) 定义服务端 socket
-
@param name service name for SDP record 这个参数最好与当前设备的蓝牙名称一致(还没研究透这个字段的具体意义)
-
@param uuid uuid for SDP record 一开始在网上搜索到蓝牙相关的代码,里面有这个字段,但是没说这个字段是怎么来的,还一直以为是读取蓝牙设备的值,一致出错;后来单独搜索了UUID类型的含义,原来是标识设备/服务的唯一标识,具体的可以参考这几篇文章:
http://blog.csdn.net/buaaroid/article/details/39372187
http://blog.csdn.net/wletv/article/details/8957612
(3) 接下来就是死循环的接受客户端的连接了。
在这里只是为了说明原理,这个 while 循环只会运行一次就跳出
BluetoothSocket socket = serverSocket.accept(); 服务器端在这里阻塞地等待客户端连连接, socket 为客户端连接后,服务器端与客户端绑定的 socket
如果程序能继续往下运行,则说明已经有客户端来连接了
if(socket.isConnected()) {
log("已建立连接");
InputStream tmpIn = socket.getInputStream(); // 获取输入流 准备读取数据
byte[] buffer = new byte[1024];
int bytes = tmpIn.read(buffer); // 读取数据
log("收到数据:");
log(new String(buffer)); // 打印数据
break;
}
最后就是使用这个 server thread 进行开启 server 了
public void openService(View view) {
if(!isEnableBluetooth) {
log("蓝牙未开启");
return;
}
new Thread(new ServerThread()).start();
}
客户端
1、开启蓝牙
(代码同上)
2、扫描服务端蓝牙设备
private BluetoothAdapter mBluetoothAdapter;
public void scanBl(View view) {
if(!isEnableBluetooth) {
Toast.makeText(this, "请先打开蓝牙", Toast.LENGTH_LONG).show();
return;
}
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter);
mBluetoothAdapter.startDiscovery();
log("启动蓝牙扫描");
}
这里调用 mBluetoothAdapter.startDiscovery(); 进行搜索
如果发现了蓝牙设备,则进行广播,所以这里注册了一个 BroadcastReceiver:
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(BluetoothDevice.ACTION_FOUND.equals(action)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
String name = device.getName();
log("广播收到消息 name:" + name);
if(name != null && name.equals(blName)) { // 判断是否是目标设备 private final String blName = "Honor 6X";
log("发现目标蓝牙服务,开始连接");
mBluetoothAdapter.cancelDiscovery(); // 停止搜索 否则耗资源
new Thread(new ClientThread(device)).start(); // 启动连接线程
}
}
}
};
3、连接服务端
如果已经扫描到了目标设备,则新启动一个线程去连接
private class ClientThread extends Thread {
private BluetoothDevice device;
private BluetoothSocket socket;
public ClientThread(BluetoothDevice device) {
this.device = device;
}
@Override
public void run() {
try {
//socket = device.createRfcommSocketToServiceRecord(UUID.fromString(MY_UUID));
socket = device.createInsecureRfcommSocketToServiceRecord(UUID.fromString(MY_UUID));
log("连接服务器端");
socket.connect(); // 去连接服务器端 阻塞
log("建立连接");
OutputStream outputStream = socket.getOutputStream();
String sndMsg = "hello Bluetooth";
byte[] buffer = sndMsg.getBytes();
log("发送数据");
outputStream.write(buffer); // 向服务端发送数据
log("发送完数据");
} catch (Exception e) {
e.printStackTrace();
}
}
}
创建 socket
createRfcommSocketToServiceRecord createInsecureRfcommSocketToServiceRecord
这两个方法与服务器端的 listenUsingRfcommWithServiceRecord 和 listenUsingInsecureRfcommWithServiceRecord 类似。
要注意的是这里的 MY_UUID 与服务器端的 MY_UUID 一定要一致
以上就是客户端和服务器端的核心代码了,完整代码如下:
服务器端:
package com.example.xxh.bluetoothserver;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import java.io.InputStream;
import java.util.UUID;
public class MainActivity extends AppCompatActivity {
private TextView log_view;
private BluetoothAdapter mBluetoothAdapter;
private boolean isEnableBluetooth = false;
private int EnableBluetoothRequestCode = 9;
private final String blName = "Honor 6X";
private final String MY_UUID = "00001101-0000-1000-8000-00805F9B34FB";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
log_view = (TextView)findViewById(R.id.log_view);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == EnableBluetoothRequestCode) {
if(Activity.RESULT_OK == resultCode) {
Toast.makeText(this, "蓝牙成功打开", Toast.LENGTH_LONG).show();
isEnableBluetooth = true;
} else {
Toast.makeText(this, "蓝牙打开失败", Toast.LENGTH_LONG).show();
isEnableBluetooth = false;
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mBluetoothAdapter.disable();
}
private void log(String msg) {
Log.i("BLUETOOTH", msg);
//log_view.append(msg + "\n");
}
public void openBl(View view) {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if(mBluetoothAdapter == null) {
isEnableBluetooth = false;
Toast.makeText(this, "前蓝牙设备驱动有问题", Toast.LENGTH_LONG).show();
log("当前蓝牙设备驱动有问题");
return;
}
if(!mBluetoothAdapter.isEnabled()) {
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent, EnableBluetoothRequestCode);
return;
}
isEnableBluetooth = true;
}
public void openService(View view) {
if(!isEnableBluetooth) {
log("蓝牙未开启");
return;
}
new Thread(new ServerThread()).start();
}
private class ServerThread extends Thread {
private BluetoothServerSocket serverSocket;
@Override
public void run() {
try {
//serverSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(blName, UUID.fromString(MY_UUID));
serverSocket = mBluetoothAdapter.listenUsingInsecureRfcommWithServiceRecord(blName, UUID.fromString(MY_UUID));
if(serverSocket == null) {
log("server socket创建失败");
return;
}
} catch (Exception e) {
e.printStackTrace();
log("server socket创建失败, 异常发生");
}
log("等待客户端连接...");
while (true) {
try {
log("开始 accept");
BluetoothSocket socket = serverSocket.accept();
log("接受到了客户端");
BluetoothDevice device = socket.getRemoteDevice();
log("接受客户连接, name:"+device.getName());
if(socket.isConnected()) {
log("已建立连接");
InputStream tmpIn = socket.getInputStream();
byte[] buffer = new byte[1024];
int bytes = tmpIn.read(buffer);
log("收到数据:");
log(new String(buffer));
break;
}
} catch (Exception e) {
e.printStackTrace();
log("accept 发生异常");
break;
}
}
}
}
}
客户端
package com.example.xxh.bluetoothclient;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import java.io.OutputStream;
import java.util.UUID;
public class MainActivity extends AppCompatActivity {
private BluetoothAdapter mBluetoothAdapter;
private boolean isEnableBluetooth = false;
private int EnableBluetoothRequestCode = 9;
private final String blName = "Honor 6X";
private final String MY_UUID = "00001101-0000-1000-8000-00805F9B34FB";
private TextView log_view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
log_view = (TextView)findViewById(R.id.log_view);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == EnableBluetoothRequestCode) {
if(Activity.RESULT_OK == resultCode) {
Toast.makeText(this, "蓝牙成功打开", Toast.LENGTH_LONG).show();
isEnableBluetooth = true;
} else {
Toast.makeText(this, "蓝牙打开失败", Toast.LENGTH_LONG).show();
isEnableBluetooth = false;
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mBluetoothAdapter.disable();
unregisterReceiver(mReceiver);
}
private void log(String msg) {
Log.i("BLUETOOTH", msg);
//log_view.append(msg + "\n");
}
public void openBl(View view) {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if(mBluetoothAdapter == null) {
Toast.makeText(this, "当前蓝牙设备驱动有问题", Toast.LENGTH_LONG).show();
isEnableBluetooth = false;
return;
}
if(mBluetoothAdapter.isEnabled()) {
isEnableBluetooth = true;
return;
}
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent, EnableBluetoothRequestCode);
}
public void scanBl(View view) {
if(!isEnableBluetooth) {
Toast.makeText(this, "请先打开蓝牙", Toast.LENGTH_LONG).show();
return;
}
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter);
mBluetoothAdapter.startDiscovery();
log("启动蓝牙扫描");
}
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(BluetoothDevice.ACTION_FOUND.equals(action)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
String name = device.getName();
log("广播收到消息 name:" + name);
if(name != null && name.equals(blName)) {
log("发现目标蓝牙服务,开始连接");
mBluetoothAdapter.cancelDiscovery(); // 停止搜索
new Thread(new ClientThread(device)).start();
}
}
}
};
private class ClientThread extends Thread {
private BluetoothDevice device;
private BluetoothSocket socket;
public ClientThread(BluetoothDevice device) {
this.device = device;
}
@Override
public void run() {
try {
//socket = device.createRfcommSocketToServiceRecord(UUID.fromString(MY_UUID));
socket = device.createInsecureRfcommSocketToServiceRecord(UUID.fromString(MY_UUID));
log("连接服务器端");
socket.connect();
log("建立连接");
OutputStream outputStream = socket.getOutputStream();
String sndMsg = "hello Bluetooth";
byte[] buffer = sndMsg.getBytes();
log("发送数据");
outputStream.write(buffer);
log("发送完数据");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
【参考】
UUID:
http://blog.csdn.net/buaaroid/article/details/39372187
http://blog.csdn.net/wletv/article/details/8957612
文章:
http://blog.csdn.net/zhangphil/article/details/50554415
http://www.cnblogs.com/CharlesGrant/p/4924169.html
http://www.jianshu.com/p/fc46c154eb77