Android IOT 蓝牙(二)

2023-02-28  本文已影响0人  古早味蛋糕

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

(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();
}
上一篇 下一篇

猜你喜欢

热点阅读