Android蓝牙开发流程实践

作者: JairusTse | 来源:发表于2018-12-14 18:17 被阅读5次

概述

工作需要用到Android蓝牙开发,所以在这里对Android蓝牙开发做一个整理。

先了解下Android蓝牙开发的基础知识:官方文档戳这里

我们需要知道,现在的蓝牙分为经典蓝牙和BLE(低功耗蓝牙)两种,经典蓝牙和低功耗蓝牙的区别戳这里 ,经典蓝牙适合传输数据量比较大的传输,比如图像、视频、音乐等,耗电也大;BLE适合耗电小,实时性要求高和数据量小的传输,比如智能穿戴设备、遥控类、鼠标、键盘、传感设备如心跳带,血压计,温度传感器等。

对于Android开发者来说,我们要知道 Android 4.3 及以上版本才支持BLE,常说的蓝牙单模和双模指的是仅支持BLE和同时支持BLE和经典蓝牙,经典蓝牙和BLE之间不能通信,Android手机同时支持经典蓝牙和BLE,但是扫描蓝牙的时候,只能扫描其中一种,如果是Android手机跟其他设备通过蓝牙通信,首先要确认设备支持的蓝牙协议。下面记录的是经典蓝牙开发步骤。

蓝牙开发步骤

通常Android蓝牙开发包含以下5个步骤:

  • 开启
  • 扫描
  • 配对
  • 连接
  • 通信

开启蓝牙

  • 权限(需要蓝牙和GPS权限,Android 6.0以上要加上运行时权限授权)

在 AndroidManifest 中声明权限:

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

获取定位授权:

    //要模糊定位权限才能搜索到蓝牙
    PermissionUtil.requestEach(this, new PermissionUtil.OnPermissionListener() {
        @Override
        public void onSucceed() {
            //授权成功后打开蓝牙
            openBlueTooth();
        }
        @Override
        public void onFailed(boolean showAgain) {

        }
    }, PermissionUtil.LOCATION);

  • 判断设备是否支持蓝牙
    BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if (mBluetoothAdapter == null) {
        //如果mBluetoothAdapter为null,该设备不支持蓝牙,不过基本上都支持的
    }

  • 判断蓝牙是否开启,没有就开启
    if (!mBluetoothAdapter.isEnabled()) {
        //若没打开则打开蓝牙
        mBluetoothAdapter.enable();
    }

扫描蓝牙

  • 可以扫描有指定 UUID 服务的蓝牙设备,UUID 不是设备的标识,而是某个服务的标识, 什么是UUID戳这里
  • 可以扫描全部蓝牙设备
  • 注意:蓝牙设备被某设备(包括当前的设备)配对/连接后,可能不会再被扫描到
    //扫描:经典蓝牙
    mBluetoothAdapter.startDiscovery();

    //扫描:低功耗蓝牙,需要加上停止扫描规则,扫描到指定设备或者一段时间后,这里设置10秒后停止扫描
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            mBluetoothAdapter.stopLeScan(MainActivity.this);
            Log.i(TAG, "=== 停止扫描了 === ");
        }
    }, SCAN_PERIOD);

    mBluetoothAdapter.startLeScan(this);

通过广播来监听扫描结果:

    //广播接收器
    BroadcastReceiver receiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            switch (action) {
                case BluetoothAdapter.ACTION_DISCOVERY_STARTED:
                    //扫描开始
                    break;
                case BluetoothDevice.ACTION_FOUND:
                    //发现蓝牙
                    BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                    break;
                case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
                    //扫描结束
                    break;
            }
        }
    };

配对蓝牙

  • 配对与连接之间的区别:配对意味着两个设备之间知道彼此的存在,通过配对密钥,建立一个加密的连接。而连接意味着设备之间共享一个通信通道(UUID),能够彼此传输数据
    /**
     * 蓝牙配对,配对结果通过广播返回
     * @param device
     */
    public void pin(BluetoothDevice device) {
        if (device == null || !mBluetoothAdapter.isEnabled()) {
            return;
        }

        //配对之前把扫描关闭
        if (mBluetoothAdapter.isDiscovering()) {
            mBluetoothAdapter.cancelDiscovery();
        }

        //判断设备是否配对,没有就进行配对
        if (device.getBondState() == BluetoothDevice.BOND_NONE) {
            try {
                Method createBondMethod = device.getClass().getMethod("createBond");
                Boolean returnValue = (Boolean) createBondMethod.invoke(device);
                returnValue.booleanValue();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

广播监听配对结果:

    case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
        //配对状态变化广播
        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice
                .EXTRA_DEVICE);
        switch (device.getBondState()) {
            case BluetoothDevice.BOND_NONE:
                Log.i(TAG, "--- 配对失败 ---");
                break;
            case BluetoothDevice.BOND_BONDING:
                Log.i(TAG, "--- 配对中... ---");
                break;
            case BluetoothDevice.BOND_BONDED:
                Log.i(TAG, "--- 配对成功 ---");
                break;
        }
        break;

连接蓝牙

  • 配对成功之后,两台设备之间建立连接,一台充当client的角色,另一台充当server的角色,由client发起连接;
  • 通过 UUID 创建 BluetoothSocket 进行连接,两个端的UUID要一致,client和server都自己开发的话,可以由服务端创建一个UUID
  • 发起连接和监听连接都要在子线程中执行

在client端的子线程中发起连接:

/**
 *
 * 发起蓝牙连接的线程
 * 作者: 代码来自于Google官方 -> API指南 -> 蓝牙模块
 * 日期: 18/12/14
 */

public class ConnectThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final BluetoothDevice mmDevice;
    private final BluetoothAdapter mBluetoothAdapter;
    private ConnectCallBack callBack;

    public ConnectThread(BluetoothDevice device, BluetoothAdapter bluetoothAdapter, ConnectCallBack callBack) {
        // Use a temporary object that is later assigned to mmSocket,
        // because mmSocket is final
        BluetoothSocket tmp = null;
        mmDevice = device;
        mBluetoothAdapter = bluetoothAdapter;
        this.callBack = callBack;

        // 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(UUID.fromString(ServerActivity.uuidStr));
        } 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); //启动数据传输的线程
        if(callBack != null) {
            callBack.onConnectSucceed(mmSocket);
        }

    }

    /** Will cancel an in-progress connection, and close the socket */
    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) { }
    }

    public interface ConnectCallBack {
        public void onConnectSucceed(BluetoothSocket serverSocket);
    }
}

在server端的子线程中监听连接:

/**
 *
 * 监听蓝牙连接线程
 * 作者: 代码来自于Google官方 -> API指南 -> 蓝牙模块
 * 日期: 18/12/14
 */

public class AcceptThread extends Thread {

    private static final String TAG = "BluetoothDemo";

    private final BluetoothServerSocket mmServerSocket;

    private AcceptCallBack callBack;

    public AcceptThread(BluetoothAdapter bluetoothAdapter, AcceptCallBack callBack) {

        this.callBack = callBack;

        // 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 = bluetoothAdapter.listenUsingRfcommWithServiceRecord("bluetoothdemo", UUID
                    .fromString(ServerActivity.uuidStr));
        } catch (IOException e) {
        }
        mmServerSocket = tmp;
    }

    public void run() {
        BluetoothSocket socket = null;
        // Keep listening until exception occurs or a socket is returned
        while (true) {
            Log.i(TAG, "AcceptThread监听中...");

            try {
                socket = mmServerSocket.accept();
            } catch (IOException e) {
                break;
            }
            // If a connection was accepted
            if (socket != null) {

                try {
                    // Do work to manage the connection (in a separate thread)
//                manageConnectedSocket(socket); //启动数据传输的线程
                    if(callBack != null) {
                        callBack.onAcceptSucceed(socket);
                    }


                    Log.i(TAG, "AcceptThread连接成功");
                    mmServerSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                break;
            }
        }
    }

    /**
     * Will cancel the listening socket, and cause the thread to finish
     */
    public void cancel() {
        try {
            mmServerSocket.close();
        } catch (IOException e) {
        }
    }

    public interface AcceptCallBack {
        public void onAcceptSucceed(BluetoothSocket serverSocket);
    }

}

通信

  • 连接成功后,通过socket得到I/O流,读取数据和写入数据,在两个设备之间传输数据。
/**
 * 发送和接收数据
 * 作者: 代码来自于Google官方 -> API指南 -> 蓝牙模块
 * 日期: 18/12/14
 */

public class ConnectedThread extends Thread {
    private static final String TAG = "ConnectedThread";
    private final BluetoothSocket mmSocket;
    private final InputStream mmInStream;
    private final OutputStream mmOutStream;
    private Handler mHandler;

    public ConnectedThread(BluetoothSocket socket, Handler handler) {
        mmSocket = socket;
        InputStream tmpIn = null;
        OutputStream tmpOut = null;
        mHandler = handler;

        // Get the input and output streams, using temp objects because
        // member streams are final
        try {
            tmpIn = socket.getInputStream();
            tmpOut = socket.getOutputStream();
        } catch (IOException e) { }

        mmInStream = tmpIn;
        mmOutStream = tmpOut;
    }

    public void run() {
        byte[] buffer = new byte[1024];  // buffer store for the stream
        int bytes; // bytes returned from read()

        // Keep listening to the InputStream until an exception occurs
        while (true) {
            try {
                // Read from the InputStream
                bytes = mmInStream.read(buffer);
                // Send the obtained bytes to the UI activity
                if(mHandler != null) {
                    mHandler.obtainMessage(ServerActivity.MESSAGE_READ, bytes, -1, buffer)
                            .sendToTarget();
                }

            } catch (IOException e) {
                break;
            }
        }
    }

    /* Call this from the main activity to send data to the remote device */
    public void write(byte[] bytes) {
        try {
            mmOutStream.write(bytes);
        } catch (IOException e) { }
    }

    /* Call this from the main activity to shutdown the connection */
    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) { }
    }
}

ConnectThread、AcceptThread 和 ConnectedThread 都是Google官方文档里面的示例,我自己加了些回调方法而已,如果觉得比较乱,可以直接去看官方文档。官方文档戳这里

代码上传到了GitHub, 功能比较简单,两台手机一个充当client,一个充当server,严格按照 开启 >> 扫描 >> 配对 >> 连接 >> 通信 5个步骤走才能实现通信。仅供参考。

相关文章

  • Android蓝牙开发流程实践

    概述 工作需要用到Android蓝牙开发,所以在这里对Android蓝牙开发做一个整理。 先了解下Android蓝...

  • Android中蓝牙开发步骤 (流程)

    Android中蓝牙开发步骤 (流程) 蓝牙在我们做智能手表中,必须使用到的。即使不同的需求开发,但也可以抽取出下...

  • Android Bluetooth相关操作

    Android Bluetooth 参考 Android 蓝牙开发(1) android蓝牙耳机下的语音(输入/识...

  • Android零散技术点

    Android BLE 蓝牙开发入门 逐步指导新手进行 Android ble 蓝牙的开发,避免踩坑。Androi...

  • Android 蓝牙BLE开发流程

    关键术语和概念 蓝牙有传统蓝牙(3.0以下)和低功耗蓝牙(BLE,又称蓝牙4.0)之分,而蓝牙4.0开发需要and...

  • Android BLE 蓝牙开发流程

    开发流程 1.申请权限 在 Android 日益注重安全性,隐私化的趋势下,我们当然需要先做权限声明 BLUETO...

  • 【七】蓝牙开发系列--蓝牙开发小结

    前言:本篇对蓝牙移动开发做个简略梳理,涉及内容有GATT Profile、Android蓝牙开发、iOS蓝牙开发等...

  • Android 蓝牙开发实践笔记

    本文基于传统蓝牙开发。 先来梳理一下蓝牙开发的逻辑,基本分为以下几步:搜索设备,配对设备,连接设备,传输数据。前三...

  • iOS蓝牙后台保活

    Xcode设置如图: 在实践中,主要的开发流程有以下: 新建Central Manager实例并进行监听蓝牙设备状...

  • Android实践 -- Android蓝牙设置连接

    蓝牙开发相关 使用Android Bluetooth APIs将设备通过蓝牙连接并通信,设置蓝牙,查找蓝牙设备,配...

网友评论

    本文标题:Android蓝牙开发流程实践

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