关于蓝牙通信的开发实践

关于蓝牙通信的开发

  1. 权限问题:

    1. 需要权限如下:

      <uses-permission android:name="android.permission.BLUETOOTH"/>
      <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
      <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
      
    2. 并且需要在代码中动态进行申请(万恶的权限限制a)

    3. 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);    
          }
      }
      
  2. 打开蓝牙

    1. 打开蓝牙主要使用Intent进行申请

      Toast.makeText(context, "蓝牙未打开", Toast.LENGTH_SHORT).show();
      Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);//请求打开蓝牙
      context.startActivity(intent);
      

      当调用上面申请后,系统会弹出是否打开蓝牙。选择是就可以了。

  3. 查找附近已匹配或可用设备

    这一步骤分为两类:一是已匹配的设备,而是可用设备。

    1. 已匹配设备:以前跟当前蓝牙设备已经做过了匹配操作,当前蓝牙设备记住了其蓝牙信息,包括地址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;
      }
      
    2. 附近可用蓝牙设备:发现附近可用蓝牙设备,在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);
          }
      }
      
  4. 连接设备

    1. 概念理解:配对和连接之间有一点是不同的。配对以为着两台设备是知道彼此的存在的,它们有一个共享的链接密匙,可以用于授权以及建立一个加密的连接。而连接意味着设备目前共享一个RFCOMM通道,并且可以彼此传输数据。目前的安卓蓝牙接口要求设备在建立一个RFCOMM连接之前先进行配对。(当你使用蓝牙接口初始化一个加密的连接时,配对是自动执行的)。

    2. 那么我们分为两步,一是配对,二是连接

      1. 配对:使用createBond

        Method createBondMethod = null;
        createBondMethod = BluetoothDevice.class.getMethod("createBond");
        if((boolean)createBondMethod.invoke(device)){
            Toast.makeText(context, "device 配对成功", Toast.LENGTH_SHORT).show();
        }
        
      2. 连接:连接需要分为发起方client以及接受方server,通过上面的发现设备等,能够获取到要连接的蓝牙设备的相应的信息BluetoothDevice,通过在相同的RFCOMM通道上有一个连接的BluetoothSocket时,就是相互连接上了。

        1. 服务端:服务端需要持有一个打开的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) { }
              }
          }
          
        2. 客户端:客户端首先是需要一个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) { }
              }
          }
          
  5. 设备间数据交换

    1. 数据交换就很简单了,分为两部分:1.写入;2.读取

    2. 写入:写入操作只需要对上述拿到的socketOutputStream进行操作,通过write(byte[])方法进行数据的写入即可:

      public void writeMessage(byte[] bytes){
          try {
              output.write(bytes);
          } catch (IOException e) {
              e.printStackTrace();
          }
      }
      
    3. 读取:读取操作只需要对上述拿到的socketInputStream进行操作,通过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-

暂时写的比较粗糙,后面再看看怎么改善一下!

有什么不对的地方,欢迎多多指正!!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值