Android 蓝牙4.0 BLE 开发


    个人认为,Android开发后台部分的技术难题只要把google提供的API看懂了就没什么太多的难点了,关键在于运用,蓝牙4.0也是,琢磨了好久没搞懂,结果下了个doc下来自己翻译一下也就理清了整个过程了,大家可能比较忙,我翻译一下吧,翻译得不好别打脸~网上还有很多人的翻译,我尽量用自己的语言来翻译,稍微带点自己的理解。

低功耗蓝牙(Bluetooth Low Energy

Android 4.3 (API Level 18)介绍了一种以低功耗蓝牙为核心的开发平台,并提供了可以让应用程序来发现设备、查询服务、读写特征值等相关API(关于服务、特征值的概念请参考BLE4.0蓝牙经典问答,要是不知道可以参考我转发的一篇,讲得很好,蓝牙4.0开发人员必看:http://www.cnblogs.com/dbgqp/articles/4103517.html)。 因为低功耗蓝牙作为经典蓝牙的拓展,相比经典蓝牙2.0为了统一通信设备,跟3.0增加数据传输速率,4.0尽可能的在降低功耗方面进行了优化。这就允许了android应用程序和低功耗蓝牙设备进行通讯,如各类传感器,心率仪,健身设备等。

关键术语和概念

下面是一些主要的BLE的概念性知识:

  • Generic Attribute Profile (GATT)—GATT协议是一个通过BLE连接从而发送和接收数据(这种数据格式我们通常称之为“属性”)的通用协议。当前所有的低功耗蓝牙的应用协议都是基于GATT的。
    • 蓝牙技术联盟为低功耗蓝牙设备定义了很多协议(我们直接用就行了,so easy,但很重要,大家开发时找准了协议直接下载参考使用就好了,下载地址:https://www.bluetooth.org/en-us/specification/adopted-specifications)。这些协议其实就是个在应用程序中如何在连接的设备中进行通信使用的说明书。每个设备可以使用多个协议,譬如我做的智能硬件就加载了电池电量检测跟心率监控的协议,用到就加进去就行了,好像我们在Android开发时用的包一样。
  • Attribute Protocol (ATT)—GATT 是ATT的上层架构,GATT把ATT的数据打包成service供我们使用. ATT 运行在我们的蓝牙外设设备上.为了达到最优化的目的,它在运行中使用尽可能少的字节。  每个属性以唯一的UUID(就是把特定的字符串转换成128位长的数字,通常是16进制的)定义,这个属性就有ATT进行传送,我们看到的就是services和characteristics .
  • Characteristic—一个 characteristic 包含了一个值和多个描述符。它就好像一个类(可以实例化出一个对象,里面具体的属性可以由自己定义一样,不知道这么理解对不对)
  • Descriptor—描述符是用来定义属性的,就像上面讲的用来描述characteristic 的(应该想类里面的set 跟get方法一样的吧)。如指定特征值是否可读,可接受的范围或者特征值特定的单位等。
  • Service—service是一些列的characteristic 的总集。如你有个服务叫“心率检测仪”,里面就有个characteristic 叫“心率测量”(应用程序中直接对这个characteristic 的UUID进行数据读取就行了)。 你可以找到很多基于GATT协议的应用协议,就在上面给的那个网址链接中https://www.bluetooth.org/en-us/specification/adopted-specifications

角色和责任

下面是android手机和BLE设备通信时需要我们注意的一些游戏规则,不遵守的就没得玩了(中间一段抄的人家的,感觉讲得很简练,可耻地copy了,第一次写博客,请轻砸):

Central vs. peripheral:

中心设备和外围设备的概念针对的是BLE连接本身。Central角色负责scan advertisement。而peripheral角色负责make advertisement。

GATT server vs. GATT client:

这两种角色取决于BLE连接成功后,两个设备间通信的方式。

举例说明:

现有一个活动追踪的BLE设备和一个支持BLE的Android设备。Android设备支持Central角色,而BLE设备支持peripheral角色。创建一个BLE连接需要这两个角色都存在,都仅支持Central角色或者都仅支持peripheral角色则无法建立连接。

当连接建立后,它们之间就需要传输GATT数据。谁做server,谁做client,则取决于具体数据传输的情况。例如,如果活动追踪的BLE设备需要向Android设备传输sensor数据,则活动追踪器自然成为了server端;而如果活动追踪器需要从Android设备获取更新信息,则Android设备作为server端可能更合适。

在文件包含的例子中,手机端作为client,从GATT server(一个支持心率协议:https://developer.bluetooth.org/TechnologyOverview/Pages/HRP.aspx的BLE硬件智能设备)中获取数据,当然,你也可以手动指定你的手机app作为server端,关于BluetoothGattServer可以了解一下:

BLE 权限

废话不多说,你要想在手机里使用蓝牙的功能你就得申明蓝牙的使用权限:BLUETOOTH,你需要这个权限来完成各种蓝牙通信,如请求连接,接受连接,收发数据。

如果你想要你的APP发起设备发现或者修改蓝牙设置,你必须要申明:BLUETOOTH_ADMIN权限。不过你想要用该权限那必须先得拥有BLUETOOTH的权限。

在manifest 文件中申明权限的方法:

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

如果你想让你的应用程序只在低功耗蓝牙手机上工作,那你可以如下设置:

<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

 

然而,如果你想让你的应用程序同样可以在不支持蓝牙4.0的设备上工作的话,你也应该申明上述权限,但要set required="false". 然后在程序运行过程中你可以通过PackageManager.hasSystemFeature()来申明低功耗蓝牙是否可用。

// Use this check to determine whether BLE is supported on the device. Then
// you can selectively disable BLE-related features.
通过下面代码来检查你手机是否直接低功耗蓝牙,然后你可以有选择的设置是否开启蓝牙服务。
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
    Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
    finish();
}

下面就简单了,主要是低功耗蓝牙的代码开发步骤,大家随便看看,然后把官网提供的范例下载下来学习一下,范例只有读,没有写,大家百度一下就可以找到,。

找不到的再问我要吧:至于我自己开发的项目中的一些功能我到时看看稍微修改一下放出来让大家也帮忙指导一下,这个博客我还不会上传附件。

需要的问我QQ:772026483要吧,QQ不在可以到我微博上留邮箱:微博VIA:逗不过奇葩;

鉴于本人不是专业的android开发工程师,大家太高深的问题就别问了,只是项目中需要才研究做个android APP的。

步骤开始

 

设置蓝牙


在你应用程序通过低功耗蓝牙通讯前,你需要query你的设备是否支持BLE,确保它是可用的。注意:这个检查只有在<uses-feature.../>中set to false的时候才是必须做的。

如果BLE不支持,那你就没办法使用BLE的功能咯。如果支持BLE,但没有开启蓝牙功能,你可以请求使用者在你的应用程序中开启。设置蓝牙可以通过BluetoothAdapter分两步完成:

 

  1. 获取 BluetoothAdapter

几乎所有的蓝牙activity都需要 BluetoothAdapterBluetoothAdapter 代表了设备自身的蓝牙适配器。在整个系统中只有一个蓝牙适配器,你的应用程序可以通过BluetoothAdapter使用它,下面的代码演示了如何获取适配器。注意:该方法是通过getSystemService返回的一个BluetoothManager实例来得到适配器,关于BluetoothManager在Android 4.3 (API Level 18)中有详细介绍。

// Initializes Bluetooth adapter.初始化蓝牙适配器
final BluetoothManager bluetoothManager =
        (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();

  1. 启动蓝牙

下一步,你需要确定蓝牙是可用的,调用isEnabled()来检测蓝牙当前状态是否可用,如果返回false,当前蓝牙不可用。下面的代码演示了如何检测是否可用,如果不可用,则设置开启蓝牙

private BluetoothAdapter mBluetoothAdapter;
...
// Ensures Bluetooth is available on the device and it is enabled. If not,
// displays a dialog requesting user permission to enable Bluetooth.
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

寻找BLE蓝牙设备


你可以使用startLeScan()方法来发现BLE设备,该方法用BluetoothAdapter.LeScanCallback作为参数,你必须继承这个回调来获取扫描结果,由于扫描过程很耗电,你应该遵守以下的引导来降低功耗:

  • 一旦发现目标设备立刻停止扫描.
  • 不要重复扫描,设置一个i额扫描时间限制,以前连接的设备可能已经离开了当前可扫描的范围,持续扫描会增加功耗。

下面代码演示了如何发起和停止扫描:

/**
 * Activity for scanning and displaying available BLE devices.
 */
public class DeviceScanActivity extends ListActivity {

    private BluetoothAdapter mBluetoothAdapter;
    private boolean mScanning;
    private Handler mHandler;

    // Stops scanning after 10 seconds.
    private static final long SCAN_PERIOD = 10000;
    ...
    private void scanLeDevice(final boolean enable) {
        if (enable) {
            // Stops scanning after a pre-defined scan period.
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mScanning = false;
                    mBluetoothAdapter.stopLeScan(mLeScanCallback);
                }
            }, SCAN_PERIOD);

            mScanning = true;
            mBluetoothAdapter.startLeScan(mLeScanCallback);
        } else {
            mScanning = false;
            mBluetoothAdapter.stopLeScan(mLeScanCallback);
        }
        ...
    }
...
}

如果你想扫描特定的外围设备,你可以用startLeScan(UUID[], BluetoothAdapter.LeScanCallback)这个方法来代替上面代码的扫描函数。该方法中的UUID就是你app中支持的服务的UUIP,在搜索过程中就进行了一遍筛选连接。

下面的代码是BluetoothAdapter.LeScanCallback的实现过程,这个接口是用来传递BLE搜索到的结果的。

private LeDeviceListAdapter mLeDeviceListAdapter;
...
// Device scan callback.
private BluetoothAdapter.LeScanCallback mLeScanCallback =
        new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(final BluetoothDevice device, int rssi,
            byte[] scanRecord) {
        runOnUiThread(new Runnable() {
           @Override
           public void run() {
               mLeDeviceListAdapter.addDevice(device);
               mLeDeviceListAdapter.notifyDataSetChanged();
           }
       });
   }
};

注意: 搜索时,你只能搜索传统蓝牙设备或者BLE设备,两者完全独立,不可同时被搜索.

 

连接到一个GATT server


两个设备通过BLE通信,首先需要建立GATT连接。这里我们讲的是Android设备作为client端,连接GATT Server。

连接GATT Server,你需要调用BluetoothDevice的connectGatt()方法。此函数带三个参数:Context、autoConnect(boolean来表明当BLE设备可用时是否主动去连接它)和BluetoothGattCallback对象。调用示例:

mBluetoothGatt = device.connectGatt(this, false, mGattCallback);

函数成功,返回BluetoothGatt对象,它是GATT profile的封装。通过这个对象,我们就能进行GATT Client端(就是我们手机端的APP)的相关操作。BluetoothGattCallback用于传递一些连接状态及结果给client端。

在该实例中,app提供了一个DeviceControlActivity来连接,显示数据,并列出了由设备提供的服务和特征,基于用户的输入,这个activity通过Android BLE API来调用BluetoothLeService和service通讯。

// A service that interacts with the BLE device via the Android BLE API.
public class BluetoothLeService extends Service {
    private final static String TAG = BluetoothLeService.class.getSimpleName();

    private BluetoothManager mBluetoothManager;
    private BluetoothAdapter mBluetoothAdapter;
    private String mBluetoothDeviceAddress;
    private BluetoothGatt mBluetoothGatt;
    private int mConnectionState = STATE_DISCONNECTED;

    private static final int STATE_DISCONNECTED = 0;
    private static final int STATE_CONNECTING = 1;
    private static final int STATE_CONNECTED = 2;

    public final static String ACTION_GATT_CONNECTED =
            "com.example.bluetooth.le.ACTION_GATT_CONNECTED";
    public final static String ACTION_GATT_DISCONNECTED =
            "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";
    public final static String ACTION_GATT_SERVICES_DISCOVERED =
            "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
    public final static String ACTION_DATA_AVAILABLE =
            "com.example.bluetooth.le.ACTION_DATA_AVAILABLE";
    public final static String EXTRA_DATA =
            "com.example.bluetooth.le.EXTRA_DATA";

    public final static UUID UUID_HEART_RATE_MEASUREMENT =
            UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT);

    // Various callback methods defined by the BLE API.
    private final BluetoothGattCallback mGattCallback =
            new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status,
                int newState) {
            String intentAction;
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                intentAction = ACTION_GATT_CONNECTED;
                mConnectionState = STATE_CONNECTED;
                broadcastUpdate(intentAction);
                Log.i(TAG, "Connected to GATT server.");
                Log.i(TAG, "Attempting to start service discovery:" +
                        mBluetoothGatt.discoverServices());

            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                intentAction = ACTION_GATT_DISCONNECTED;
                mConnectionState = STATE_DISCONNECTED;
                Log.i(TAG, "Disconnected from GATT server.");
                broadcastUpdate(intentAction);
            }
        }

        @Override
        // New services discovered
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
            } else {
                Log.w(TAG, "onServicesDiscovered received: " + status);
            }
        }

        @Override
        // Result of a characteristic read operation
        public void onCharacteristicRead(BluetoothGatt gatt,
                BluetoothGattCharacteristic characteristic,
                int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
            }
        }
     ...
    };
...
}

当一个具体的回调被发起时,它会调用合适的broadcastUpdate()方法并给它发一个动作,下面这段是Bluetooth Heart Rate Measurement profile 中的实现代码,可以参考了解一下:

private void broadcastUpdate(final String action) {
    final Intent intent = new Intent(action);
    sendBroadcast(intent);
}

private void broadcastUpdate(final String action,
                             final BluetoothGattCharacteristic characteristic) {
    final Intent intent = new Intent(action);

    // This is special handling for the Heart Rate Measurement profile. Data
    // parsing is carried out as per profile specifications.
    if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
        int flag = characteristic.getProperties();
        int format = -1;
        if ((flag & 0x01) != 0) {
            format = BluetoothGattCharacteristic.FORMAT_UINT16;
            Log.d(TAG, "Heart rate format UINT16.");
        } else {
            format = BluetoothGattCharacteristic.FORMAT_UINT8;
            Log.d(TAG, "Heart rate format UINT8.");
        }
        final int heartRate = characteristic.getIntValue(format, 1);
        Log.d(TAG, String.format("Received heart rate: %d", heartRate));
        intent.putExtra(EXTRA_DATA, String.valueOf(heartRate));
    } else {
        // For all other profiles, writes the data formatted in HEX.
        final byte[] data = characteristic.getValue();
        if (data != null && data.length > 0) {
            final StringBuilder stringBuilder = new StringBuilder(data.length);
            for(byte byteChar : data)
                stringBuilder.append(String.format("%02X ", byteChar));
            intent.putExtra(EXTRA_DATA, new String(data) + "\n" +
                    stringBuilder.toString());
        }
    }
    sendBroadcast(intent);
}

回到DeviceControlActivity中, 这些事件就由 BroadcastReceiver进行处理:

// Handles various events fired by the Service.
// ACTION_GATT_CONNECTED: connected to a GATT server.
// ACTION_GATT_DISCONNECTED: disconnected from a GATT server.
// ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services.
// ACTION_DATA_AVAILABLE: received data from the device. This can be a
// result of read or notification operations.
private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
            mConnected = true;
            updateConnectionState(R.string.connected);
            invalidateOptionsMenu();
        } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
            mConnected = false;
            updateConnectionState(R.string.disconnected);
            invalidateOptionsMenu();
            clearUI();
        } else if (BluetoothLeService.
                ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
            // Show all the supported services and characteristics on the
            // user interface.
            displayGattServices(mBluetoothLeService.getSupportedGattServices());
        } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
            displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA));
        }
    }
};

读取BLE属性(数据)


一旦你的APP连接到了一个GATT server并且发现相关的服务(serices),它就可以根读写服务中提供的属性(参考上面的概念);

下面的代码演示了如何把搜索到的可用的服务和特征遍历显示在UI界面上:

public class DeviceControlActivity extends Activity {
    ...
    // Demonstrates how to iterate through the supported GATT
    // Services/Characteristics.
    // In this sample, we populate the data structure that is bound to the
    // ExpandableListView on the UI.
    private void displayGattServices(List<BluetoothGattService> gattServices) {
        if (gattServices == null) return;
        String uuid = null;
        String unknownServiceString = getResources().
                getString(R.string.unknown_service);
        String unknownCharaString = getResources().
                getString(R.string.unknown_characteristic);
        ArrayList<HashMap<String, String>> gattServiceData =
                new ArrayList<HashMap<String, String>>();
        ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData
                = new ArrayList<ArrayList<HashMap<String, String>>>();
        mGattCharacteristics =
                new ArrayList<ArrayList<BluetoothGattCharacteristic>>();

        // Loops through available GATT Services.
        for (BluetoothGattService gattService : gattServices) {
            HashMap<String, String> currentServiceData =
                    new HashMap<String, String>();
            uuid = gattService.getUuid().toString();
            currentServiceData.put(
                    LIST_NAME, SampleGattAttributes.
                            lookup(uuid, unknownServiceString));
            currentServiceData.put(LIST_UUID, uuid);
            gattServiceData.add(currentServiceData);

            ArrayList<HashMap<String, String>> gattCharacteristicGroupData =
                    new ArrayList<HashMap<String, String>>();
            List<BluetoothGattCharacteristic> gattCharacteristics =
                    gattService.getCharacteristics();
            ArrayList<BluetoothGattCharacteristic> charas =
                    new ArrayList<BluetoothGattCharacteristic>();
           // Loops through available Characteristics.
            for (BluetoothGattCharacteristic gattCharacteristic :
                    gattCharacteristics) {
                charas.add(gattCharacteristic);
                HashMap<String, String> currentCharaData =
                        new HashMap<String, String>();
                uuid = gattCharacteristic.getUuid().toString();
                currentCharaData.put(
                        LIST_NAME, SampleGattAttributes.lookup(uuid,
                                unknownCharaString));
                currentCharaData.put(LIST_UUID, uuid);
                gattCharacteristicGroupData.add(currentCharaData);
            }
            mGattCharacteristics.add(charas);
            gattCharacteristicData.add(gattCharacteristicGroupData);
         }
    ...
    }
...
}

获取GATT的通知


当设备端的某个特定的特征值发生改变时BLE的APP就会收到通知。

下面的代码演示了如何通过setCharacteristicNotification()方法来为一个特征设置通知:

private BluetoothGatt mBluetoothGatt;
BluetoothGattCharacteristic characteristic;
boolean enabled;
...
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
...
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
        UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);

Once notifications are enabled for a characteristic, an onCharacteristicChanged() callback is triggered if the characteristic changes on the remote device:

@Override
// Characteristic notification
public void onCharacteristicChanged(BluetoothGatt gatt,
        BluetoothGattCharacteristic characteristic) {
    broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}

关闭clientAPP


一旦你的APP结束使用BLE设备,它需要调用close()来让系统适当的回收资源和释放内存空间。

示例代码:

public void close() {
    if (mBluetoothGatt == null) {
        return;
    }
    mBluetoothGatt.close();
    mBluetoothGatt = null;
}

摘抄的人家的一段,算是备注一下:

BluetoothGatt常规用到的几个操作示例:

connect() :连接远程设备。

discoverServices() : 搜索连接设备所支持的service。

disconnect():断开与远程设备的GATT连接。

close():关闭GATT Client端。

readCharacteristic(characteristic) :读取指定的characteristic。

setCharacteristicNotification(characteristic, enabled) :设置当指定characteristic值变化时,发出通知。

getServices() :获取远程设备所支持的services。

等等。

注:

1、某些函数调用之间存在先后关系。例如首先需要connect上才能discoverServices。

2、一些函数调用是异步的,需要得到的值不会立即返回,而会在BluetoothGattCallback的回调函数中返回。例如discoverServices与onServicesDiscovered回调,readCharacteristic与onCharacteristicRead回调,setCharacteristicNotification与onCharacteristicChanged回调等。

本文中除了自己理解的部分外,参考的博客地址是:http://www.blogjava.net/zh-weir/archive/2013/12/09/407373.html

自己翻译前大概浏览过《低功耗蓝牙开发权威指南》跟《蓝牙4.0BLE开发完全手册》,这两本书很实用,可惜没时间好好看,推荐给大家!

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM