美文网首页
Android IOT 蓝牙(二)

Android IOT 蓝牙(二)

作者: 古早味蛋糕 | 来源:发表于2023-02-28 19:27 被阅读0次

    一、点对点蓝牙通信
    无论WiFi还是4G/5G网络,建立网络连接后都会访问互联网资源,并不能直接访问局域网资源。比如两个人在一起,甲要把手机上的视频传给乙,通常情况是打开微信,通过微信传文件给对方。不过上传视频很耗流量,如果现场没有可用的WiFi,手机的数据流量又不足,就只能干瞪眼了。为解决这种邻近传输文件的问题,蓝牙技术应运而生。它是一种无线技术标准,可实现设备之间的短距离数据交换。
    1、Android为蓝牙技术提供了4个工具类
    分别是蓝牙适配器BuletoothAdapter、蓝牙设备BluetoothDevice、蓝牙服务端套接字BluetoothServerSocket和蓝牙客户端套接字BluetoothSocket。
    (1)蓝牙适配器BuletoothAdapterBuletoothAdapter的常用方法在上面已经写了一部分。下面补充为剩余的几个常见方法:

    • setName:设置本机的蓝牙名称。
    • getName:获取本机的蓝牙名称。
    • getAddress:获取本机的蓝牙地址。
    • getState:获取本地蓝牙适配器的状态。值为BluetoothAdapter.STATE_ON时表示蓝牙可用。
    • listenUsingRfcommWithServiceRecord:根据名称和UUID创建并返回BluetoothServerSocket。
    • listenUsingRfcommOn:根据信道编号创建并返回BluetoothServerSocket。
      (2)蓝牙设备BluetoothDeviceBluetoothDevice用于指代某个蓝牙设备,通常表示对方设备,相对应的,BuletoothAdapter用于管理本机的蓝牙设备。下面是BluetoothDevice的常用方法说:
    • getName:获得该设备的名称。
    • getAddress:获得该设备的地址。
    • getBondState:获得该设备的绑定状态。
    • createBond:创建配对请求。配对结果通过广播返回。
    • createRfcommSocketToServiceRecord:根据UUID创建并返回一个BluetoothSocket。
      (3)蓝牙服务端套接字BluetoothServerSocketBluetoothServerSocket是蓝牙服务端的Socket(套接字),用来接收蓝牙客户端的Socket连接请求。下面是它的常用方法:
    • accept:监听外部的蓝牙连接请求。一旦有请求接入,就返回一个BluetoothSocket对象。
    • close:关闭服务端的蓝牙监听。
      (4)蓝牙客户端套接字BluetoothSocketBluetoothSocket是蓝牙客户端的Socket,用于与对方设备进行数据通信。下面是它的常用方法:
    • connect:建立蓝牙的Socket连接。
    • close:关闭蓝牙的Socket连接。
    • getInptuStream:获取Socket连接的输入流对象。
    • getOutputStream:获取Socket连接的输出流对象。
    • getRemoteDevice:获取远程设备信息,即与本设备建立Socket连接的远程蓝牙设备。
      2、使用蓝牙建立连接、发送消息的完整流程,完整流程主要分为以下4个步骤:
      (1)开启蓝牙功能
      准备两部手机,各自安装蓝牙演示App。首先打开演示App的蓝牙页面,一开始两部手机的蓝牙功能均为关闭状态,然后分别点击两部手机左上角的开关按钮,准备开启手机的蓝牙功能。两部手机都会弹出一个确认对话框,如下图【蓝牙权限的选择对话框】所示,提醒用户是否允许其他蓝牙设备检测到本手机。


      蓝牙权限的选择对话框.png

      点击“允许”按钮确认开启蓝牙功能。稍等一会儿,两部手机分别检测到了对方设备,并在界面上显示对方设备名称,且状态为“未绑定”。此时A手机的检测界面如图【A手机发现对方】所示,B手机的检测界面如图【B手机发现对方】所示。


      A手机发现对方.png
      B手机发现对方.png

    (2)确认配对并完成绑定
    在任意一部手机上点击对方的设备名称,表示发起配对请求。此时两部手机都会弹出一个确认对话框,提示用户是否将本机与对方设备进行配对如图所示


    A手机的配对弹窗.png
    B手机的配对弹窗.png

    两边分别点击“配对”按钮,确认与对方配对。配对完成后,检测界面将设备状态改为“已绑定”


    A手机完成配对.png
    B手机完成配对.png

    (3)建立蓝牙连接
    在任意一部手机上点击已绑定的设备记录,表示发起连接请求。具体而言,首先是客户端的BluetoothSocket调用connect方法,然后服务端BluetoothServerSocket的accept方法接收连接请求,于是双方成功建立连接。有的手机可能会弹窗提示“应用想与设备进行通信”,点击弹窗的“确定”按钮即可放行。建立蓝牙连接后,设备记录右边的状态值改为“已连接”。

    A手机与对方建立连接.png
    B手机与对方建立连接.png
    (4)通过蓝牙发送消息
    在A手机上点击已连接的设备记录,表示想要发送消息。于是A手机弹出文字输入对话框,提示用户输入待发送的消息文本,文字输入框如图【A手机准备向对方发送消息】所示。点击“确定”按钮发送消息,然后B手机接收到A手机发来的消息,就把该消息文本通过弹窗显示出来,B手机的消息弹窗如图【B手机收到对方发来的消息】所示。
    A手机准备向对方发送消息.png
    B手机收到对方发来的消息.png
    一个完整的蓝牙应用过程全部呈现出来。上面的流程仅实现了简单的字符串传输,真实场景更需要文件传输。当然,使用输入输出流操作文件也不是什么难事。
    两部手机之间通过蓝牙分享数据也要先进行搜索与配对操作,然后才能开展后续的设备连接和数据传输,本节直接进入双方设备连接和数据传输的环节。
    正如网络通信中的Socket通信,蓝牙Socket同样存在服务端与客户端的概念,服务端负责侦听指定端口,客户端只管往该端口发送数据。因此,作为服务端的手机要先开启蓝牙侦听线程,守株待兔。下面是服务端的蓝牙手机处理侦听事务的示例代码:BlueAcceptTask完整代码
        //TAG log的标记符
        private static final String TAG = "BlueAcceptTask";
        private static final String NAME_SECURE = "BluetoothChatSecure";
        private static final String NAME_INSECURE = "BluetoothChatInsecure";
        private static BluetoothServerSocket mServerSocket; // 声明一个蓝牙服务端套接字对象
        private Activity mAct; // 声明一个活动实例
        private BlueAcceptListener mListener; // 声明一个蓝牙侦听的监听器对象
    
        @SuppressLint("MissingPermission")
        public BlueAcceptTask(final Activity act, final boolean secure,
                              final BlueAcceptListener listener) {
            mAct = act;
            mListener = listener;
            Log.d(TAG, "init");
            BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
            // 以下提供了三种侦听方法,使得在不同情况下都能获得服务端的Socket对象
            try {
                if (mServerSocket != null) {
                    mServerSocket.close();
                }
                if (secure) { // 安全连接
                    mServerSocket = adapter.listenUsingRfcommWithServiceRecord(
                            NAME_SECURE, BluetoothConnector.uuid);
                } else { // 不安全连接
                    mServerSocket = adapter.listenUsingInsecureRfcommWithServiceRecord(
                            NAME_INSECURE, BluetoothConnector.uuid);
                }
            } catch (Exception e) { // 遇到异常则尝试第三种侦听方式
                e.printStackTrace();
                mServerSocket = BluetoothUtil.listenServer(adapter);
            }
        }
        private static final int EXCEPTION_TIME = 1000;//定义异常休眠时间
        @Override
        public void run() {
            Log.d(TAG, "run");
            while (true) {
                try {
                    // 如果accept方法有返回,则表示某部设备过来打招呼了
                    BluetoothSocket socket = mServerSocket.accept();
                    if (socket != null) { // socket非空,表示名花有主了,赶紧带去见公婆
                        mAct.runOnUiThread(() -> mListener.onBlueAccept(socket));
                        break;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    try {
                        Thread.sleep(EXCEPTION_TIME);
                    } catch (InterruptedException ex) {
                        ex.printStackTrace();
                    }
                }
            }
        }
    
        // 定义一个蓝牙侦听的监听器接口,在获得响应之后回调onBlueAccept方法
        public interface BlueAcceptListener {
            void onBlueAccept(BluetoothSocket socket);
        }
    

    首先客户端要与服务端建立连接并打通信道,核心是调用对方设备对象的createRfcommSocket相关方法,从而获得该设备的蓝牙Socket实例。建立蓝牙连接的示例代码如下:BlueConnectTask完整代码

    public class BlueConnectTask extends Thread{
        private static final String TAG = "BlueConnectTask";
        private Activity mAct; // 声明一个活动实例
        private BlueConnectListener mListener; // 声明一个蓝牙连接的监听器对象
        private BluetoothDevice mDevice; // 声明一个蓝牙设备对象
    
        public BlueConnectTask(Activity act, BluetoothDevice device, BlueConnectListener listener) {
            mAct = act;
            mListener = listener;
            mDevice = device;
        }
    
        @Override
        public void run() {
            // 创建一个对方设备的蓝牙连接器,第一个输入参数为对方的蓝牙设备对象
            BluetoothConnector connector = new BluetoothConnector(mDevice, true,
                    BluetoothAdapter.getDefaultAdapter(), null);
            Log.d(TAG, "run");
            // 蓝牙连接需要完整的权限,有些机型弹窗提示"***想进行通信",这就不行,日志会报错:
            // read failed, socket might closed or timeout, read ret: -1
            try {
                // 开始连接,并返回对方设备的蓝牙套接字对象BluetoothSocket
                BluetoothSocket socket = connector.connect().getUnderlyingSocket();
                mAct.runOnUiThread(() -> mListener.onBlueConnect(socket));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        // 定义一个蓝牙连接的监听器接口,用于在成功连接之后调用onBlueConnect方法
        public interface BlueConnectListener {
            void onBlueConnect(BluetoothSocket socket);
        }
    
    }
    

    双方建立连接之后,客户端拿到了蓝牙Socket实例,于是调用getOutputStream方法获得输出流对象,然后即可进行数据交互。客户端发送信息的代码如下所示:BluetoothUtil完整代码

    // 向对方设备发送信息
    public static void writeOutputStream(BluetoothSocket socket, String message) {
        Log.d(TAG, "begin writeOutputStream message=" + message);
        try {
            OutputStream os = socket.getOutputStream(); // 获得输出流对象
            os.write(message.getBytes()); // 往输出流写入字节形式的数据
        } catch (Exception e) {
            e.printStackTrace();
        }
        Log.d(TAG, "end writeOutputStream");
    }
    

    服务端也没闲着,早在双方建立连接之时便早早开启了消息接收线程,随时准备倾听客户端的呼声。该线程内部调用蓝牙Socket实例的getInputStream方法获得输入流对象,接着从输入流读取数据并送给主线程处理。接收线程处理代码如下:BlueReceiveTask完整代码

        @Override
        public void run() {
            byte[] buffer = new byte[1024];
            int bytes;
            while (true) {
                try {
                    // 从蓝牙Socket获得输入流,并从中读取输入数据
                    bytes = mSocket.getInputStream().read(buffer);
                    // 把字节数据转换为字符串
                    String message = new String(buffer, 0, bytes);
                    Log.d(TAG, "message=" + message);
                    // 将读到的数据通过处理器送回给UI主线程处理
                    mAct.runOnUiThread(() -> mListener.onBlueReceive(message));
                } catch (Exception e) {
                    e.printStackTrace();
                    break;
                }
            }
        }
    
        // 定义一个蓝牙接收的监听器接口,在获得响应之后回调onBlueAccept方法
        public interface BlueReceiveListener {
            void onBlueReceive(String message);
        }
    

    此时回到蓝牙主页面,得到消息接收线程传来的数据,把字节形式的数据转换为原始字符串,这样便可在另一部手机上看到发出来的消息。主线程收到消息后的操作代码:BluetoothTransActivity完整代码

    // 启动蓝牙消息的接收任务
    private void startReceiveTask(BluetoothSocket socket) {
        if (socket == null) {
            return;
        }
        tv_discovery.setText("连接成功");
        mBlueSocket = socket;
        refreshDevice(mBlueSocket.getRemoteDevice(), BlueListAdapter.CONNECTED);
        // 创建一个蓝牙消息的接收线程
        BlueReceiveTask receiveTask = new BlueReceiveTask(this, mBlueSocket, message -> {
            if (!TextUtils.isEmpty(message)) {
                // 弹出收到消息的提醒对话框
                AlertDialog.Builder builder = new AlertDialog.Builder(this);
                builder.setTitle("我收到消息啦").setMessage(message);
                builder.setPositiveButton("确定", null);
                builder.create().show();
            }
        });
        receiveTask.start();
    }
    

    相关文章

      网友评论

          本文标题:Android IOT 蓝牙(二)

          本文链接:https://www.haomeiwen.com/subject/rqwlldtx.html