Android 蓝牙五子棋[可人机对战] —— 蓝牙通信篇

前言

我之前做了个人机对战的五子棋, AI算法很垃圾, 然后各种逻辑很糟糕, 已经很久没有维护了, 今天看了篇文章, 是用了蓝牙和Wifi的五子棋对战, 我觉得很有意思, 毕竟自己没做过蓝牙连接这方面的项目, 然后我就把以前做的五子棋搬了出来.


蓝牙基础知识

  • 官方知识:

    设置蓝牙、查找局部区域内的配对设备或可用设备、连接设备,以及在设备之间传输数据。


  • 设置权限

    权限是一定要设置的,不然根本无法使用,在Manifest设置以下权限:

    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

    关于6.0以上的蓝牙权限问题

    我是基于6.0开发的, 然而我开启了蓝牙却搜索不到其他设备, 感觉很奇怪,搜索了一下,原来:还要添加一个权限(其中一个):

    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

  • 设置蓝牙

    1. 获取BluetoothAdapter

      所有蓝牙Activity都需要BluetoothAdapter,获取这个Adapter很简单, 只需要调用BluetoothAdapter.getDefaultAdapter()这个静态方法即可,它会返回本机的蓝牙适配器.

    2. 启用蓝牙可见性

      首先检测是否已开启蓝牙可见性, 然后使用

      if(!mBluetoothAdapter.isEnabled())
      {
          Intent openIntent=new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
          openIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 500); //设置可见性的时间500s, 0为一直可见
          startActivity(openIntent);
      }

      提示:启用可见性就会启动蓝牙.


  • 查找设备

    • 监听广播:

      当我们调用BluetoothAdapter.startDiscovery()扫描的时候, 会发出有ACTION_FOUND的广播, 并且Intent还带有EXTRA_DEVICE(包含BluetoothDevice),请看下面代码:

      public class BluetoothReceiver extends BroadcastReceiver {
      
          private List mDevices;
          private List mBluetoothList;
          public OnReceiverListener mOnReceiverListener;
      
          public BluetoothReceiver(List devices, List bluetooths, OnReceiverListener onReceiverListener) {
              mOnReceiverListener = onReceiverListener;
              mDevices = devices;
              mBluetoothList = bluetooths;
          }
      
          @Override
          public void onReceive(Context context, Intent intent) {
              String action = intent.getAction();
              if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                  ///获取扫描到的Device信息
                  BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                  if (!mDevices.contains(device)) {
                      Bluetooth bluetooth = new Bluetooth(device.getName(), device.getAddress());
                      mBluetoothList.add(bluetooth);
                      mDevices.add(device);
                  }
              } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
                  mOnReceiverListener.showText();
              }
      
              if (mOnReceiverListener != null) {
                  mOnReceiverListener.setBluetoothList(mBluetoothList, mDevices);
              }
          }
      
          //设置给BluetoothActivity的回调接口
          public interface OnReceiverListener {
              void setBluetoothList(List bluetooths, List devices);
              void showText();
          }
      }

      注意: 别忘了再Activity里注册和销毁Receiver

    • BluetoothActivity里的扫描

      直接调用startDiscovery()就可以了,很简单.

      //初始化
      private void init() {
          mBluetooths = new ArrayList<>();
          mDevices = new ArrayList<>();
      
          //初始化BluetoothAdpter
          mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
          if (!mBluetoothAdapter.isEnabled()) {
              openBluetooth();
          }
          mName = mBluetoothAdapter.getName();
          mAdress = mBluetoothAdapter.getAddress();
      
          //初始化ListView
          mAdapter = new BluetoothDevicesAdapter(mBluetooths, this);
          mLvBluetooth.setAdapter(mAdapter);
      
          //实例化广播
          mReceiver = new BluetoothReceiver(mDevices, mBluetooths, new BluetoothReceiver.OnReceiverListener() {
              @Override
              public void setBluetoothList(List bluetooths, List devices) {
                  mBluetooths = bluetooths;
                  mDevices = devices;
                  mAdapter.setDevices(mBluetooths);
                  mAdapter.notifyDataSetChanged();
              }
      
              @Override
              public void showText() {
                  Toast.makeText(BluetoothActivity.this, "搜索完成!", Toast.LENGTH_SHORT).show();
              }
          });
      
          //注册广播
          IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
          registerReceiver(mReceiver, filter);
          filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
          registerReceiver(mReceiver, filter);
      
          mLvBluetooth.setOnItemClickListener(new AdapterView.OnItemClickListener() {
              @Override
              public void onItemClick(AdapterView parent, View view, int position, long id) {
                  connectBluetooth(position);
              }
          });
      }
      
      //查找设备
      private void findBluetooth() {
          if (mBluetoothAdapter.isDiscovering()) {    //中断搜索
              mBluetoothAdapter.cancelDiscovery();
          }
          mBluetooths.clear();
          mDevices.clear();
      
          //获取已配对的设备
          Set devices = mBluetoothAdapter.getBondedDevices();
          if (devices.size() > 0) {
              for (BluetoothDevice device : devices) {
                  if (!mDevices.contains(device)) {
                      mBluetooths.add(new Bluetooth(device.getName(), device.getAddress()));
                      mDevices.add(device);
                  }
              }
          }
          mAdapter.setDevices(mBluetooths);
          mAdapter.notifyDataSetChanged();
          mBluetoothAdapter.startDiscovery();
      }

  • 连接设备

    连接设备需要同时实现服务端和客服端, 因为其中一台设备必须开放服务器套接字, 另一台设备发起连接(使用服务器设备MAC地址), 当服务器和客户端在同一RFCOMM通道上分别拥有已连接的BluetoothSocket时, 此时就是彼此连接.

    提示: 若两台设备未配对, 则会先进行配对, 知道配对成功才会进行连接.

    • 建立服务器

      当需要连接时, 其中一台设备需要开放BluetoothServerSocket来充当服务器, 服务器套接字是用来侦听传入的连接请求, 并在接收后提供已连接的BluetoothSocket.

      • 获取BluetoothServerSocket

        通过调用listenUsingRfcommWithServiceRecord(String , UUID)获取BluetoothServerSocket.其中字符串是任意名称, UUID必须唯一标识, 两个UUID必须匹配.

        UUID: 通用唯一标识符, UUID足够庞大,可以使用随机, 可使用UUID.fromString(String)初始化

      • 侦听连接

        调用accept()来侦听, 需要放在子线程中, 只有当发送的请求中的UUID与服务器套接字UUID相匹配时, 才会被接受.返回已连接的BluetoothSocket.

      • 释放Server

        如果不需要与更多的连接,那么可以调用close()来释放所有资源.并不会关闭accept的已连接的BluetoothSocket.

        //初始化Scocket
        private void initSocket() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        while (true) {
                            mServerSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(mName, ConfigData.UUID);
                            mSocket = mServerSocket.accept();
                            if (mSocket.isConnected()) {
                                InputStream is = mSocket.getInputStream();
                                byte[] buffer = new byte[1024];
                                int count = is.read(buffer);
                                final String result = new String(buffer, 0, count, "utf-8");
                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        AlertDialog aDialog = new AlertDialog.Builder(BluetoothActivity.this)
                                                .setTitle("消息")
                                                .setMessage("是否接受" + result + "的挑战?")
                                                .setPositiveButton("是", new DialogInterface.OnClickListener() {
                                                    @Override
                                                    public void onClick(DialogInterface dialog, int which) {
                                                        Toast.makeText(BluetoothActivity.this, "连接成功!", Toast.LENGTH_SHORT).show();
                                                    }
                                                })
                                                .setNegativeButton("否", null)
                                                .show();
                                    }
                                });
                                if(is!=null){
                                    is.close();
                                }
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                    }
                }
            }).start();
        }

    • 建立客户端

      主要是为了能然上面的ServerSocket接收到请求.

      • 获取BluetoothSocket

        先获取需要连接设备的Device, 然后调用 device.createRfcommSocketToServiceRecord(ConfigData.UUID);就能够获取然后再调用bluetoothSocket.connect()就可以发起连接了

        注: 调用connect()时, 最好不要进行其他的蓝牙操作, 不然连接速度很慢, 也有可能会失败.

        // 开始连接对方
        private void connectBluetooth(int position) {
            BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(mBluetooths.get(position).getAdress());
            try {
                mThisSocket = device.createRfcommSocketToServiceRecord(ConfigData.UUID);
                //弹出对话框
                AlertDialog dialog = new AlertDialog.Builder(BluetoothActivity.this)
                        .setTitle("发起挑战")
                        .setMessage("确认挑战玩家: " + mBluetooths.get(position).getName())
                        .setPositiveButton("确定", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                new Thread(new Runnable() {
                                    @Override
                                    public void run() {
                                        OutputStream os = null;
                                        if (mBluetoothAdapter.isDiscovering()) {
                                            mBluetoothAdapter.cancelDiscovery();
                                        }
                                        try {
                                            mThisSocket.connect();                  //发起连接
                                            if (!mBluetoothAdapter.isEnabled()) {   //若未开启蓝牙,则打开
                                                mBluetoothAdapter.enable();
                                            }
                                            if (mThisSocket.isConnected()) {        //若已连接成功
                                                os = mThisSocket.getOutputStream(); //获得输出流
                                                os.write(mName.getBytes("utf-8"));  //向服务端发送本机蓝牙名称
                                                runOnUiThread(new Runnable() {      //在UI线程发送提示.
                                                    @Override
                                                    public void run() {
                                                        Toast.makeText(BluetoothActivity.this, "连接成功!!!", Toast.LENGTH_SHORT).show();
                                                    }
                                                });
                                            }
                                            if (os != null) {
                                                os.close();
                                            }
                                        } catch (Exception e) {
                                            e.printStackTrace();
                                        }
        
                                    }
                                }).start();
                            }
                        })
                        .setNegativeButton("取消", null)
                        .show();
            } catch (Exception e) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(BluetoothActivity.this, "连接失败!", Toast.LENGTH_SHORT).show();
                    }
                });
                e.printStackTrace();
            }
        }

总结

  • 这里简单的说了下蓝牙的连接和管理,其实差不多都是官网的教程,百度到的教程十分的少,都是去官网看的,官网也有中文版,所以担心自己英语不好的兄弟们不要怕,就是干。

  • 完整版的篇章:http://blog.csdn.net/williamchew/article/details/69566878

  • 完整版对蓝牙连接的Socket线程进行了优化, 并使用了管理类,可能看起来会比较难懂,其实很简单,也有写的不好的地方,请原谅。

你可能感兴趣的:(Android-实战演练)