Android蓝牙开发---站在前辈的肩膀上唠嗑
Posted android阿杜
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android蓝牙开发---站在前辈的肩膀上唠嗑相关的知识,希望对你有一定的参考价值。
描述一段背景:前年我找工作时,总碰到一个问题。
面试官问:“你会蓝牙开发吗?”。
我说:“不会”。
面试官答:“那,很抱歉。我们商量了一下,觉得你不适合这个岗位。”
于是我就走了,心里想:“就应为一个蓝牙通讯技术不会,就把我给cut了,这面试官好有想象力。”
我一个同学,都没做过编程,我半年时间都带到android开发道上了。我仅仅蓝牙没做过,研究蓝牙无非就是三两天的时间,难吗?
于是,我周末窝在家里,查阅了大量资料,实践和总结,研究透了蓝牙技术。
有些同学可能会说,蓝牙简单,无非就是扫描设备,配对,和socket通讯。
没错,是这些,但是还有很多坑你不知道,还有很多奇葩代码。废话少说,一起分享吧。
想使用蓝牙呢,首先得看手机是否支持,有些低配手机,可能就没有内置蓝牙模块。当然,一般都会有,我们可以得到唯一的蓝牙适配器,进行其他操作。
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
然后,我们可做n多的事情。不要着急,后面会附上我的项目源码,大牛见笑了。
/**
* 开启蓝牙
*
* @param activity 上下文
* @return 是否开启成功
*/
public static boolean openBluetooth(Activity activity) {
//确认开启蓝牙
if (!getInstance().isEnabled()) {
//=默认120秒==============================================================
//使蓝牙设备可见,方便配对
//Intent in = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
//in.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
//activity.startActivityForResult(in,Activity.RESULT_OK);
//=1=============================================================
//请求用户开启,需要提示
//Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
//startActivityForResult(intent, RESULT_FIRST_USER);
//=2=============================================================
//程序直接开启,不经过提示
getInstance().enable();
}
//T.showLong(context, "已经开启蓝牙");
return getInstance().isEnabled();
}
//关闭蓝牙
public static boolean closeBluetooth() {
return getInstance().disable();
}
/**
* 扫描已经配对的设备
*
* @return
*/
public static ArrayList<BluetoothDevice> scanPairs() {
ArrayList<BluetoothDevice> list = null;
Set<BluetoothDevice> deviceSet = getInstance().getBondedDevices();
if (deviceSet.size() > 0) {
//存在已经配对过的蓝牙设备
list = new ArrayList<>();
list.addAll(deviceSet);
}
return list;
}
//开始扫描
public static void scan() {
getInstance().startDiscovery();
}
//取消扫描
public static void cancelScan() {
if (getInstance().isDiscovering())
getInstance().cancelDiscovery();
}
//蓝牙配对
@TargetApi(Build.VERSION_CODES.KITKAT)
public static boolean createBond(BluetoothDevice device) {
return bond(device, "createBond");
/*if (device.createBond()) {
return device.setPairingConfirmation(true);
}
return false;*/
}
//解除配对
public static boolean removeBond(BluetoothDevice device) {
return bond(device, "removeBond");
}
@TargetApi(Build.VERSION_CODES.KITKAT)
private static boolean bond(BluetoothDevice device, String methodName) {
Boolean returnValue = false;
if (device != null && device.getBondState() == BluetoothDevice.BOND_NONE) {
try {
device.setPairingConfirmation(false);
cancelPairingUserInput(device);
Method removeBondMethod = BluetoothDevice.class.getMethod(methodName);
returnValue = (Boolean) removeBondMethod.invoke(device);
} catch (Exception e) {
e.printStackTrace();
}
}
return returnValue;
}
//取消配对
public static boolean cancelBondProcess(BluetoothDevice device) {
try {
Method cancelBondMethod = BluetoothDevice.class.getMethod("cancelBondProcess");
Boolean returnValue = (Boolean) cancelBondMethod.invoke(device);
return returnValue.booleanValue();
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
//取消用户输入
public static boolean cancelPairingUserInput(BluetoothDevice device) {
try {
Method cancelPairingUserInputMethod = BluetoothDevice.class.getMethod("cancelPairingUserInput");
Boolean returnValue = (Boolean) cancelPairingUserInputMethod.invoke(device);
return returnValue.booleanValue();
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
通过上面这段代码,应道知道了吧,获取到适配器后,可以得到当前手机已经配对的设备。同时可以开启扫描(这个时间大概是12秒,异步的),扫描到设备和扫描完成系统会发广播。故广播接收代码:
//注册蓝牙接收广播
if (!hasRegister) {
hasRegister = true;
//扫描结束广播
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
//找到设备广播
filter.addAction(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
filter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST);
registerReceiver(mMyReceiver, filter);
}
private class MyReceiver extends 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);
//搜索没有配过对的蓝牙设备
if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
mListData.add(device);
dataAdapter.refreshData(mListData);
} else {
T.showLong(TwoActivity.this, device.getName() + '\\n' + device.getAddress() + " > 已发现");
}
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { //搜索结束
if (mListData.size() == 0) {
T.showLong(TwoActivity.this, "没有发现任何蓝牙设备");
}
progressDialog.dismiss();
scan.setText("重新扫描");
} else if (BluetoothDevice.ACTION_PAIRING_REQUEST.equals(action)) {
if (TwoActivity.this.position != -1) {
final BluetoothDevice device = mListData.get(TwoActivity.this.position);
com.dk.bluetooth.tools.T.showLong(TwoActivity.this, device + " 配对成功");
EventBus.getDefault().post(new com.dk.bluetooth.tools.MyEvent());
}
}
}
}
以上都不是重点,核心在线程通讯。首先得了解socket,java有这个,蓝牙里面也有,是BluetoothServerSocket和BluetoothSocket两个,一个是服务器端的,一个是客户端的。不了解的朋友建议先去百度java socket用法,超级简单。
通讯需要建立信道,BluetoothServerSocket需要先启动,监听当前设备上的某UUID位置上的设备(阻塞到在此处),就跟windows的端口意思是一样的。然后BluetoothSocket再启动,根据对方的mac地址和对方监听的UUID位置,启动连接(也阻塞了),直到连上服务器了,就返回。服务器也一样,直到有人来连接了,就返回。都会返回一个BluetoothSocket,然后从这个socket里面获取input和output流。服务器端了input流是客户端的output流,另外一半也一样。剩下的收发消息就是流的读写了,简单吧。
下面贴出我的代码,应为服务器端和客户端启动的方法不一样,我分成了两个线程,由于读写的功能一样,我就共用了一套读写线程。根据这个思路看我的代码。
/**
* 初始化及启动蓝牙socket
*
* @param handler UI消息传递对象
* @param securityType 连接的安全模式
* @param serverOrClient 客户端或服务端
* @param bluetoothDevice 服务器端设备
*/
public BluetoothChatService(Handler handler, SecurityType securityType, ServerOrClient serverOrClient,
BluetoothDevice bluetoothDevice) {
if (securityType != null)
this.mSecurityType = securityType;
if (serverOrClient != null)
this.mServerOrClient = serverOrClient;
if (bluetoothDevice != null)
this.mBluetoothDevice = bluetoothDevice;
mAdapter = BluetoothAdapter.getDefaultAdapter();
mHandler = handler;
start();
}
/**
* 多线程同步修改状态标识
*
* @param state
*/
private synchronized void setState(int state) {
mState = state;
mHandler.obtainMessage(MESSAGE_TOAST_STATE_CHANGE, state, -1, null).sendToTarget();
}
/**
* 多线程同步读取状态标识
*/
public synchronized int getState() {
return mState;
}
/**
* 启动服务
*/
public void start() {
start(null, null, null);
}
/**
* 启动服务
*
* @param securityType 连接的安全模式
* @param serverOrClient 客户端或服务端
* @param bluetoothDevice 服务器端设备
*/
public void start(SecurityType securityType, ServerOrClient serverOrClient, BluetoothDevice bluetoothDevice) {
if (securityType != null)
this.mSecurityType = securityType;
if (this.mSecurityType == null) {
if (DEBUG)
Log.e(TAG, "mSecurityType cannot be null");
mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "mSecurityType cannot be null").sendToTarget();
return;
}
if (serverOrClient != null)
this.mServerOrClient = serverOrClient;
if (this.mServerOrClient == null) {
if (DEBUG)
Log.e(TAG, "mServerOrClient cannot be null");
mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "mServerOrClient cannot be null").sendToTarget();
return;
}
if (bluetoothDevice != null)
this.mBluetoothDevice = bluetoothDevice;
if (this.mBluetoothDevice == null) {
if (DEBUG)
Log.e(TAG, "mBluetoothDevice cannot be null");
mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "mBluetoothDevice cannot be null").sendToTarget();
return;
}
if (mState == STATE_NONE) {
stop();
if (this.mServerOrClient == ServerOrClient.SERVER) {
if (mServerConnectThread == null) {
mServerConnectThread = new ServerConnectThread(this.mSecurityType);
mServerConnectThread.start();
}
} else if (this.mServerOrClient == ServerOrClient.CLIENT) {
if (mClientConnectThread == null) {
mClientConnectThread = new ClientConnectThread(this.mSecurityType);
mClientConnectThread.start();
}
}
setState(STATE_LISTEN);
}
}
/**
* 停止服务
*/
public synchronized void stop() {
try {
if (mReadWriteThread != null) {
mReadWriteThread.cancel();
mReadWriteThread = null;
}
if (mServerConnectThread != null) {
mServerConnectThread.cancel();
mServerConnectThread = null;
}
if (mClientConnectThread != null) {
mClientConnectThread.cancel();
mClientConnectThread = null;
}
} catch (Exception e) {
e.printStackTrace();
if (DEBUG)
Log.e(TAG, "BluetoothChatService -> stop() -> :failed " + e.getMessage());
mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "BluetoothChatService -> stop() -> :failed").sendToTarget();
mReadWriteThread = null;
mServerConnectThread = null;
mClientConnectThread = null;
} finally {
setState(STATE_NONE);
System.gc();
}
}
/**
* 发送消息
*
* @param out 数据参数
*/
public void write(String out) {
if (TextUtils.isEmpty(out)) {
if (DEBUG)
Log.e(TAG, "please write something now");
mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "BluetoothChatService -> write() -> :failed").sendToTarget();
return;
}
ReadWriteThread r;
synchronized (this) {
if (mState != STATE_CONNECTED)
return;
r = mReadWriteThread;
}
r.write(out);
}
/**
* 服务器端连接线程
*/
@SuppressLint("NewApi")
private class ServerConnectThread extends Thread {
private BluetoothServerSocket mmServerSocket;
private BluetoothSocket mmSocket = null;
public ServerConnectThread(SecurityType securityType) {
setName("ServerConnectionThread:" + securityType.getValue());
BluetoothServerSocket tmp = null;
try {
if (securityType == SecurityType.SECURE) {
tmp = mAdapter.listenUsingRfcommWithServiceRecord(SecurityType.SECURE.getValue(), MY_UUID_SECURE);
} else if (securityType == SecurityType.INSECURE) {
tmp = mAdapter.listenUsingInsecureRfcommWithServiceRecord(SecurityType.INSECURE.getValue(),
MY_UUID_INSECURE);
}
if (tmp != null)
mmServerSocket = tmp;
} catch (IOException e) {
e.printStackTrace();
if (DEBUG)
Log.e(TAG, "ServerConnectThread -> ServerConnectThread() -> :failed " + e.getMessage());
mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "ServerConnectThread -> ServerConnectThread() -> :failed").sendToTarget();
mmServerSocket = null;
BluetoothChatService.this.stop();
}
}
public void run() {
try {
// 正在连接
setState(STATE_CONNECTING);
//accept() 阻塞式的方法,群聊时,需要循环accept接收客户端
mmSocket = mmServerSocket.accept();
connected(mmSocket);
} catch (Exception e) {
e.printStackTrace();
if (DEBUG)
Log.e(TAG, "ServerConnectThread -> run() -> :failed " + e.getMessage());
mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "ServerConnectThread -> run() -> :failed").sendToTarget();
BluetoothChatService.this.stop();
}
}
public void cancel() {
try {
if (mmSocket != null) {
mmSocket.close();
mmSocket = null;
}
if (mmServerSocket != null) {
mmServerSocket.close();
mmServerSocket = null;
}
} catch (IOException e) {
e.printStackTrace();
if (DEBUG)
Log.e(TAG, "ServerConnectThread -> cancel() -> :failed " + e.getMessage());
mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "ServerConnectThread -> cancel() -> :failed").sendToTarget();
mmSocket = null;
mmServerSocket = null;
BluetoothChatService.this.stop();
}
}
}
// 客户端连接线程
private class ClientConnectThread extends Thread {
private BluetoothSocket mmSocket;
public ClientConnectThread(SecurityType securityType) {
setName("ClientConnectThread:" + securityType.getValue());
BluetoothSocket tmp = null;
try {
if (securityType == SecurityType.SECURE) {
tmp = mBluetoothDevice.createRfcommSocketToServiceRecord(MY_UUID_SECURE);
//Method m = mBluetoothDevice.getClass().getMethod("createRfcommSocket", int.class);
//tmp = (BluetoothSocket) m.invoke(mBluetoothDevice, 1);
} else if (securityType == SecurityType.INSECURE) {
tmp = mBluetoothDevice.createInsecureRfcommSocketToServiceRecord(MY_UUID_INSECURE);
//Method m = mBluetoothDevice.getClass().getMethod("createRfcommSocket", int.class);
//tmp = (BluetoothSocket) m.invoke(mBluetoothDevice, 1);
}
if (tmp != null)
mmSocket = tmp;
} catch (Exception e) {
e.printStackTrace();
if (DEBUG)
Log.e(TAG, "ClientConnectThread -> ClientConnectThread() -> :failed " + e.getMessage());
mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "ClientConnectThread -> ClientConnectThread() -> :failed").sendToTarget();
mmSocket = null;
BluetoothChatService.this.stop();
}
}
public void run() {
try {
setState(STATE_CONNECTING);
mmSocket.connect();
connected(mmSocket);
} catch (IOException e) {
e.printStackTrace();
if (DEBUG)
Log.e(TAG, "ClientConnectThread -> run() -> :failed " + e.getMessage());
mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "ClientConnectThread -> run() -> :failed").sendToTarget();
BluetoothChatService.this.stop();
}
}
public void cancel() {
try {
if (mmSocket != null && mmSocket.isConnected()) {
mmSocket.close();
}
mmSocket = null;
} catch (IOException e) {
e.printStackTrace();
if (DEBUG)
Log.e(TAG, "ClientConnectThread -> cancel() -> :failed " + e.getMessage());
mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "ClientConnectThread -> cancel() -> :failed").sendToTarget();
mmSocket = null;
BluetoothChatService.this.stop();
}
}
}
/**
* 以获取socket,建立数据流线程
*
* @param socket
*/
private synchronized void connected(BluetoothSocket socket) {
if (mReadWriteThread != null) {
mReadWriteThread.cancel();
mReadWriteThread = null;
}
mReadWriteThread = new ReadWriteThread(socket);
mReadWriteThread.start();
}
/**
* 连接成功线程,可进行读写操作
*/
private class ReadWriteThread extends Thread {
private BluetoothSocket mmSocket;
private DataInputStream mmInStream;
private DataOutputStream mmOutStream;
private boolean isRunning = true;
public ReadWriteThread(BluetoothSocket socket) {
mmSocket = socket;
try {
mmInStream = new DataInputStream(mmSocket.getInputStream());
mmOutStream = new DataOutputStream(mmSocket.getOutputStream());
// 连接建立成功
setState(STATE_CONNECTED);
} catch (IOException e) {
e.printStackTrace();
if (DEBUG)
Log.e(TAG, "ReadWriteThread -> ReadWriteThread() -> :failed " + e.getMessage());
mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "ReadWriteThread -> ReadWriteThread() -> :failed").sendToTarget();
mmOutStream = null;
mmInStream = null;
BluetoothChatService.this.stop();
}
}
public void run() {
byte[] buffer = new byte[1024];
int len;
while (isRunning) {
try {
//readUTF(),read(buffer) 都是阻塞式的方法
//如果这儿用readUTF,那么写的地方得用writeUTF。对应
String receive_str = mmInStream.readUTF();
if (!TextUtils.isEmpty(receive_str))
mHandler.obtainMessage(MESSAGE_RECEIVE, -1, -1, receive_str).sendToTarget();
// len = mmInStream.read(buffer);
// if(len > 0){
// String receive_str = new String(buffer,0,len);
// if (!TextUtils.isEmpty(receive_str))
// mHandler.obtainMessage(MESSAGE_RECEIVE, -1, -1, receive_str).sendToTarget();
// }
} catch (IOException e) {
e.printStackTrace();
if (DEBUG)
Log.e(TAG, "ReadWriteThread -> run() -> :failed " + e.getMessage());
mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "ReadWriteThread -> run() -> :failed").sendToTarget();
BluetoothChatService.this.stop();
}
}
}
public void write(String str) {
try {
mmOutStream.writeUTF(str);
mmOutStream.flush();
mHandler.obtainMessage(MESSAGE_TOAST_SEND, -1, -1, str).sendToTarget();
} catch (IOException e) {
e.printStackTrace();
if (DEBUG)
Log.e(TAG, "ReadWriteThread -> write() -> :failed " + e.getMessage());
mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "ReadWriteThread -> write() -> :failed").sendToTarget();
BluetoothChatService.this.stop();
}
}
public void cancel() {
try {
isRunning = false;
if (mmInStream != null) {
mmInStream.close();
mmInStream = null;
}
if (mmOutStream != null) {
mmOutStream.close();
mmOutStream = null;
}
} catch (IOException e) {
e.printStackTrace();
if (DEBUG)
Log.e(TAG, "ReadWriteThread -> cancel() -> :failed " + e.getMessage());
mHandler.obtainMessage(MESSAGE_TOAST_ERROR, -1, -1, "ReadWriteThread -> cancel() -> :failed").sendToTarget();
mmInStream = null;
mmOutStream = null;
BluetoothChatService.this.stop();
}
}
}
sorry,不要蛋疼,不要骂娘,代码确实有这么多。以上代码使我在前辈的基础上优化改进了的。可以作为一个公共的蓝牙通讯工具类使用。
下面贴出我的项目功能,是一个聊天程序,只能单聊。我不明太网上有很多demo声称能群聊怎么实现的,据目前分析,服务器端和客户端的管道流是一一对应的,不是广播模式。如果能群聊,会在服务器端创建一个输入输出流管理的集合吧,服务器端没收到一条消息,在=再循环输出流集合,往各个客户端都发送消息。这样一来,我上面的这段代码不够用了。懒得改,故没有做群聊。
先贴图:
程序中,socket的连接方式有安全连接和不安全连接,我一直没有搞懂区别
在两个手机都连接了wifi的情况加,再使用我这种蓝牙通讯方式通讯时,io流连接上后会自动断开,很奇怪。查资料说蓝牙通讯的波段频率与路由器的冲突了。没辙,故在启动程序的时候关闭了wifi,下下策,望大家提供思路。
最后附上源码。有遗漏和错误,望大家指点。
以上是关于Android蓝牙开发---站在前辈的肩膀上唠嗑的主要内容,如果未能解决你的问题,请参考以下文章