蓝牙这个功能,搞了我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