关于蓝牙通信的开发
-
权限问题:
-
需要权限如下:
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
-
并且需要在代码中动态进行申请(万恶的权限限制a)
-
if (Build.VERSION.SDK_INT >= 23) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED){ ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 10); } }
-
-
打开蓝牙
-
打开蓝牙主要使用
Intent
进行申请Toast.makeText(context, "蓝牙未打开", Toast.LENGTH_SHORT).show(); Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);//请求打开蓝牙 context.startActivity(intent);
当调用上面申请后,系统会弹出是否打开蓝牙。选择是就可以了。
-
-
查找附近已匹配或可用设备
这一步骤分为两类:一是已匹配的设备,而是可用设备。
-
已匹配设备:以前跟当前蓝牙设备已经做过了匹配操作,当前蓝牙设备记住了其蓝牙信息,包括地址MAC、Name等等
/** * 获取已经配对的蓝牙信息 * adapter是蓝牙的适配器BluetoothAdapter, 代表本地蓝牙适配器(蓝牙无线电)。BluetoothAdapter是所有蓝牙交互的入口。使用这个你可以发现其他蓝牙设备,查询已配对的设备列表,使用一个已知的MAC地址来实例化一个BluetoothDevice,以及创建一个BluetoothServerSocket来为监听与其他设备的通信。 * 获取方式为 BluetoothAdapter.getDefaultAdapter() */ public ArrayList<String> getAdaptedBlueTooth() { Toast.makeText(context, "开始获取", Toast.LENGTH_SHORT).show(); bluetoothList = new ArrayList<>(); if (adapter != null) {//是否有蓝牙设备 if (adapter.isEnabled()) {//蓝牙设备是否可用 Set<BluetoothDevice> devices = adapter.getBondedDevices();//获取到已经匹配的蓝牙对象 if (devices.size() > 0) { for (Iterator iterator = devices.iterator(); iterator.hasNext(); ) { BluetoothDevice device = (BluetoothDevice) iterator.next(); bluetoothList.add(device.getAddress() + "设备名:" + device.getName()); } return bluetoothList; } else { Toast.makeText(context, "没有绑定的蓝牙设备", Toast.LENGTH_SHORT).show(); searchForAvailableBluetooth(); } } else { Toast.makeText(context, "蓝牙未打开", Toast.LENGTH_SHORT).show(); Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);//请求打开蓝牙 context.startActivity(intent); } } else { Toast.makeText(context, "没有发现蓝牙", Toast.LENGTH_SHORT).show(); } return null; }
-
附近可用蓝牙设备:发现附近可用蓝牙设备,在Android中发现设备是一个过程,通过注册receiver对响应的信息进行获取。只有在附近蓝牙设备是可被检测的状态下才会回应发现请求,分享出自身的一些属性值,如:设备名称,类,MAC地址等
//首先第一步是创建一个接收回应信息的广播,并且对信息进行相应的处理 //第二步,注册广播,并且开启扫描,开启扫描只需要调用startDiscovery(),成功返回true,失败返回false。 //第三步,记得关闭扫描后,也要把广播注册掉 /** * 创建广播,接收扫描到的蓝牙信息 */ private class BluetoothReceiver extends BroadcastReceiver { /** * 这个receive每一次扫描到都会被调用 * @param context * @param intent */ @Override public void onReceive(Context context, Intent intent) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); bluetoothList.add(device.getAddress() + "设备名:" + device.getName());//接收获取到的蓝牙地址信息 //这里可以使用handler进行数据的传递,更新UI界面 } } /** * 注册一个广播,并且扫描课配对的蓝牙信息 */ public void searchForAvailableBluetooth(){ //1.注册一个广播,用于接收“发现设备”的广播 intentFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND); receiver = new BluetoothReceiver(); if(context != null){ context.registerReceiver(receiver, intentFilter); isRegisted = true; } //2.创建蓝牙适配器,并开始扫描 //注意这里需要对权限进行动态申请!!!!!!!!!!!!!!!!!!!!!!! BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); if (Build.VERSION.SDK_INT >= 23) { if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions((Activity)context, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 10); } } if(adapter.startDiscovery()){ Toast.makeText(context, "扫描开始",Toast.LENGTH_SHORT).show(); }else{ Toast.makeText(context, "扫描未开始",Toast.LENGTH_SHORT).show(); } } /** * 关闭扫描过程 */ public void tryToCancel(){ //结束扫描过程,并且关闭广播 if (adapter.isDiscovering()) { adapter.cancelDiscovery(); } if(isRegisted){ context.unregisterReceiver(receiver); } }
-
-
连接设备
-
概念理解:配对和连接之间有一点是不同的。配对以为着两台设备是知道彼此的存在的,它们有一个共享的链接密匙,可以用于授权以及建立一个加密的连接。而连接意味着设备目前共享一个RFCOMM通道,并且可以彼此传输数据。目前的安卓蓝牙接口要求设备在建立一个RFCOMM连接之前先进行配对。(当你使用蓝牙接口初始化一个加密的连接时,配对是自动执行的)。
-
那么我们分为两步,一是配对,二是连接
-
配对:使用
createBond
。Method createBondMethod = null; createBondMethod = BluetoothDevice.class.getMethod("createBond"); if((boolean)createBondMethod.invoke(device)){ Toast.makeText(context, "device 配对成功", Toast.LENGTH_SHORT).show(); }
-
连接:连接需要分为发起方
client
以及接受方server
,通过上面的发现设备等,能够获取到要连接的蓝牙设备的相应的信息BluetoothDevice
,通过在相同的RFCOMM
通道上有一个连接的BluetoothSocket
时,就是相互连接上了。-
服务端:服务端需要持有一个打开的
BluetoothServerSocket
来作为服务器端。该socket的目的是为了监听外来的连接请求,当一个请求被接受之后,提供一个连接的BluetoothSocket
.//注意:此处例子使用的Android开发中文api提供的例子,我自己写的也是基于该例子进行一些相应的逻辑变化。 /** * 主要步骤如下: * 1. 通过adapter调用listenUsingRfcommWithServiceRecord(String, UUID)得到一个BluetoothServerSocket。其中,这个String是你的服务的标志名称,系统将会把它写入设备中的一个新的服务发现协议(SDP)数据库条目中(名字是任意的,并且可以只是你应用的名字)。UUID同样被包含在SDP条目中,并且将会成为和客户端设备连接协议的基础。也就是说,当客户端尝试连接这个设备时,它将会携带一个UUID用于唯一指定它想要连接的服务器。这些UUIDs必须匹配以便该连接可以被接受(在下一步中)。 通过调用accept()开始监听连接请求。 * 2. 通过调用accept()开始监听请求。这是一个阻塞调用,只有当一个远程设备使用一个UUID发送了一个连接请求,并且该UUID和正在监听的服务器socket注册的UUID相匹配时,一个连接才会被接受。成功后,accept() 将会返回一个已连接的 BluetoothSocket。(所以这意味着客户端需要知道服务端的UUID!!!!!!!)(因为是阻塞的,所以需要卡一个线程进行等待) * 3. close() */ private class AcceptThread extends Thread { private final BluetoothServerSocket mmServerSocket; public AcceptThread() { // Use a temporary object that is later assigned to mmServerSocket, // because mmServerSocket is final BluetoothServerSocket tmp = null; try { // MY_UUID is the app's UUID string, also used by the client code tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID); } catch (IOException e) { } mmServerSocket = tmp; } public void run() { BluetoothSocket socket = null; // Keep listening until exception occurs or a socket is returned while (true) { try { socket = mmServerSocket.accept(); } catch (IOException e) { break; } // If a connection was accepted if (socket != null) { // Do work to manage the connection (in a separate thread) manageConnectedSocket(socket); //这里就可以对该socket进行操作了,可以获取到他的相应的一些信息,例如:inputstream、outputstream //例如我的操作,直接监听客户端发送的信息(这里已经涉及到了蓝牙数据交换了0-0) inputStream = socket.getInputStream(); byte[] buffer = new byte[1024]; int bytes; while(true){ outputStream= socket.getOutputStream();//判断客户端是否断开 bytes = inputStream.read(buffer); if(bytes == 0 || bytes == -1){ ToastString("数据返回为" + bytes); Message message = handler.obtainMessage(); message.what = MainActivity.CONNECTBREAK; handler.sendMessage(message); break; }//判断客户端是否断开 Message message = handler.obtainMessage(); message.what = MainActivity.GETMESSAGE; Bundle bundle = new Bundle(); bundle.putString(MainActivity.MESSAGEKEY, new String(buffer,0,bytes)); message.setData(bundle); handler.sendMessage(message); } break; } } } /** Will cancel the listening socket, and cause the thread to finish */ public void cancel() { try { mmServerSocket.close(); } catch (IOException e) { } } }
-
客户端:客户端首先是需要一个
BluetoothDevice
(上面的步骤已经获取到了),然后使用它获取到BluetoothSocket
,然后调用connect()
即可初始化一个连接。/** 步骤 1. 使用BluetoothDevice,通过调用createRfcommSocketToServiceRecord(UUID)来得到一个BluetoothSocket. 此处的UUID必须跟服务器使用UUID是相匹配的。 2. 调用connect()初始化一个连接。 执行这个调用时,系统将会在远程设备上执行一个SDP查找工作,来匹配UUID。如果查找成功,并且远程设备接受了连接,它将会在连接过程中分享RFCOMM通道,而 connect() 将会返回。这个方法是阻塞的。如果,处于任何原因,该连接失败了或者connect()超时了(大约12秒以后),那么它将会抛出一个异常。 因为connect()是一个阻塞调用,这个连接过程应该总是在一个单独的线程中执行。 需要注意的是:你应该总是确保在你调用connect()时设备没有执行设备查找工作。如果正在查找设备,那么连接尝试将会很大程度的减缓,并且很有可能会失败。 */ private class ConnectThread extends Thread { private final BluetoothSocket mmSocket; private final BluetoothDevice mmDevice; public ConnectThread(BluetoothDevice device) { // Use a temporary object that is later assigned to mmSocket, // because mmSocket is final BluetoothSocket tmp = null; mmDevice = device; // Get a BluetoothSocket to connect with the given BluetoothDevice try { // MY_UUID is the app's UUID string, also used by the server code tmp = device.createRfcommSocketToServiceRecord(MY_UUID); } catch (IOException e) { } mmSocket = tmp; } public void run() { // Cancel discovery because it will slow down the connection mBluetoothAdapter.cancelDiscovery(); try { // Connect the device through the socket. This will block // until it succeeds or throws an exception mmSocket.connect(); } catch (IOException connectException) { // Unable to connect; close the socket and get out try { mmSocket.close(); } catch (IOException closeException) { } return; } // Do work to manage the connection (in a separate thread) //同样,此处也是可以获取到输入输出流,并且对其进行操作,不过,由于输入流是需要等待对方的输入的,所以是需要在线程中进行,避免阻塞主线程。 manageConnectedSocket(mmSocket); } /** Will cancel an in-progress connection, and close the socket */ public void cancel() { try { mmSocket.close(); } catch (IOException e) { } } }
-
-
-
-
设备间数据交换
-
数据交换就很简单了,分为两部分:1.写入;2.读取
-
写入:写入操作只需要对上述拿到的
socket
的OutputStream
进行操作,通过write(byte[])
方法进行数据的写入即可:public void writeMessage(byte[] bytes){ try { output.write(bytes); } catch (IOException e) { e.printStackTrace(); } }
-
读取:读取操作只需要对上述拿到的
socket
的InputStream
进行操作,通过read(byte[])
方法进行读取即可。需要注意的是,read方法是一个阻塞的方法,所以我们需要在新的线程的循环中进行读取数据。//此处只展示读取代码,上面写服务器端的时候有一个比较完整的实例 inputStream = socket.getInputStream(); byte[] buffer = new byte[1024]; int bytes; while(true){ outputStream= socket.getOutputStream();//判断客户端是否断开 bytes = inputStream.read(buffer); if(bytes == 0 || bytes == -1){ ToastString("数据返回为" + bytes); Message message = handler.obtainMessage(); message.what = MainActivity.CONNECTBREAK; handler.sendMessage(message); break; }//判断客户端是否断开 Message message = handler.obtainMessage(); message.what = MainActivity.GETMESSAGE; Bundle bundle = new Bundle(); bundle.putString(MainActivity.MESSAGEKEY, new String(buffer,0,bytes)); message.setData(bundle); handler.sendMessage(message); }
-
完结!!!
写蓝牙这个小demo,我查了很多文章,但是都感觉晕头转向,很多方面都不是很懂,最后还是去看了Android的文档比较实在!
在这里附送 Android蓝牙文档,写的很仔细,我上面的可能是以一个demo的形式进行展现。
查看完整蓝牙通信demo的代码,可以去我GitHub上面看一下:
GitHub的地址:https://github.com/shuhaoLIN/-Bluetooth-socket-
暂时写的比较粗糙,后面再看看怎么改善一下!