澳门新葡萄京官网首页Android 蓝牙4.0低功耗(BLE)官方详解(译)

前段时间项目选拔到了蓝牙( Bluetooth® 卡塔尔才具,生手一枚,英特网各样找材质,开掘不是不全正是不适那时候候宜,要么正是抄袭转发,真实醉了,今后将这一块的东西整理出来,供大家参照他事他说加以考察。

扬言:转发请表明出处http://www.jianshu.com/p/54bc88207050

正文将开展对蓝牙( Bluetooth® State of Qatar低功耗从围观蓝牙( Bluetooth® 卡塔尔设备,建设结构连接到蓝牙( Bluetooth® 卡塔尔(قطر‎数据通讯的亲力亲为介绍,以致详细介绍GATT
Profile(Generic
Attribute Profile,通用属性左券State of Qatar的重新组合布局。

基本概念

Android中的蓝牙( Bluetooth® 卡塔尔分三种:卓绝蓝牙5.0、低功耗蓝牙( Bluetooth® 卡塔尔国。

  1. 互相本质上还未太多分歧,能够知晓为后人是前者的升迁优化版本。对于API上的落到实处差异依旧相当的大的。
  2. 办事流程:开采设备->配成对/绑定设备->创立连接->数据通讯。
    至于底层怎么样专门的职业,本人不驾驭,亦非本文关切的机要。
  3. 合英文档:

Android 4.3(API level
18)引进了Bluetooth低功耗(BLE卡塔尔国核心功效,并提供了API可供app来寻觅设备,查询服务以致传输音讯。

权限和feature

和优异蓝牙5.0同样,使用低耗电蓝牙( Bluetooth® 卡塔尔,要求证明BLUETOOTH权限,假若需求扫描设备也许操作蓝牙( Bluetooth® 卡塔尔设置,则还亟需BLUETOOTH_ADMIN权限:

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

除此而外Bluetooth权限外,假诺急需BLE feature则还需要注脚uses-feature:

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

当required为true时,则运用只好在帮助BLE的Android设备上安装运营;required为false时,Android设备均可平常安装运维,要求在代码中决断设备是或不是援救BLE
feature:

if(!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
  Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
  finish();
}

第一实例

  1. 非凡Bluetooth闲谈实例:
  2. 低功耗Bluetooth:
  3. 用作外设(API>=21):
  4. 幼功概念讲授:
  5. 深切理论:

Bluetooth低耗电经常用于以下多少个方面:

创建BLE

在运用能够经过 BLE 人机联作从前, 你需求证实设备是还是不是援助 BLE 作用, 假如帮忙,
鲜明它是足以选拔的. 那些检查独有在 上边包车型大巴配备 设置为 false
时才是必得的。

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

注意事项

  1. 低耗电蓝牙5.0(BLE卡塔尔Android 4.3(API 18)以上才支撑
  2. 使用蓝牙供给权限

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    
  3. Android 5.0(API 21)扫描蓝牙( Bluetooth® State of Qatar供给牢固权限,不然扫描不到设备,实际使用时候发掘5.0不须求也得以扫描,Android 6.0(API
    23State of Qatar以上务必(不明白哪些原因,测验机器:MI 2,知道源委的可告知)

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

    合Lithuania语档:

  4. 低功耗Bluetooth要评释特征,或许代码决断

    // 如果为true表示只能在支持低功耗蓝牙的设备上使用,如果不支持的设备也可以使用,采用代码判断
    <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
    // 代码判断
    if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
        Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
        finish();
    }
    
  5. 优质Bluetooth连接成功后获得二个socket连接获得输入输出流进行通讯,低耗电通过特色(具体达成不通晓是如何)

  6. Android 5.0(API
    21)早前不能算作外设(蓝牙5.0耳机、音响等State of Qatar来行使,只可以当作宗旨即主机
  • 在八个八九不离十的设备之间传输少些数量。
  • 与相差传感器举办相互,比如Google
    Beacons。那样能够遵照当前地点来定制客户特性化体验。
赢得Bluetooth适配器(蓝牙( Bluetooth® State of QatarAdapter)

装有的Bluetooth活动都亟待 BluetoothAdapter, BluetoothAdapter
代表了设备自身的蓝牙5.0适配器 (蓝牙5.0有线设备State of Qatar. 整个种类中独有三个蓝牙5.0适配器, 应用能够动用 蓝牙Adapter 对象与 蓝牙5.0适配器硬件举办人机联作.

// Initializes a Bluetooth adapter.  For API level 18 and above, get a reference to
// BluetoothAdapter through BluetoothManager.
final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();

低功耗蓝牙

当今蓝牙( Bluetooth® 卡塔尔支出多数都以低功耗Bluetooth,举个例子心率设备、动圈耳机设备、手环,所以我们先从着重的初始,讲讲低功耗蓝牙( Bluetooth® 卡塔尔(قطر‎的使用,前面在圆满杰出蓝牙( Bluetooth® 卡塔尔国。

声称权限

制造项目在AndroidManifest.xml中宣示权限:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.lowett.android">
  // 定位权限第二个包含第一个,所以这里就声明了一个,两个都声明也可以
  <!-- <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> -->
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> 
  <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="false"/>
   // 是有gps硬件,这个现在的智能手机没有不支持的吧
  <uses-feature android:name="android.hardware.location.gps"/>

  </manifest>

若是manifest中声称的Bluetooth性子为false,那么在代码中监测是或不是扶持BLE性子,

// 使用此检查确定BLE是否支持在设备上,然后你可以有选择性禁用BLE相关的功能
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
    Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
    finish();
}

BluetoothAdapter

获取BluetoothManager获得蓝牙5.0适配器蓝牙5.0Adapter,注意那七个类皆以系统等第,独有四个,代表了你手提式无线电话机上的Bluetooth模块

final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();

假使获得到的Adapter为空表明不扶助Bluetooth,只怕未有蓝牙5.0模块

翻开蓝牙5.0

前方获取到了BluetoothAdapter,能够透过调用isEnabled(卡塔尔国函数去检查评定是不是开启了蓝牙5.0,false表示尚未张开,若无展开需求去启用,

Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
activity.startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);

澳门新葡萄京官网首页,代码施行完后,会有弹框提醒客户是不是启用,我们须求在onActivityResult(State of Qatar中推断再次来到

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == BluetoothLeManager.REQUEST_ENABLE_BT) {
        if (resultCode == Activity.RESULT_OK) {
           // something, 去扫描设备
            startScan();
        } else {
            new AlertDialog.Builder(this)
                    .setMessage("请开启蓝牙,连接设备")
                    .setPositiveButton(R.string.confirm, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                           // something
                        }
                    })
                    .create()
                    .show();
        }
    }
}

环顾设备

低耗电Bluetooth和经文蓝牙5.0的扫描情势各异,假使熟练精华蓝牙( Bluetooth® 卡塔尔(قطر‎,那将要只怕掉进坑了起不来了,哈哈。蓝牙5.0扫描功耗相比较严重,所以那边必供给记得在适当的莫过于甘休扫描,比方依期结束、扫描到对象设备就停下(诡异的是API为啥不提供扫描时间长度的接口呢?)扫描时调用Adapter的startLeScan(State of Qatar方法,然则那些被标志为过时,API>=21被另三个代表。然后通过回掉获得扫描结果(精华蓝牙( Bluetooth® 卡塔尔是广播)

public void startScan() {
    // 初始化一个handler
        initHandler();
        if (!mScanning) {
            if (scanRunnable == null) {
                scanRunnable = new Runnable() {
                    @Override
                    public void run() {
                        stopScan();
                    }
                };
            }

          // SCAN_PERIOD = 3 * 10 * 1000, 30s后停止扫面
            mHandler.postDelayed(scanRunnable, SCAN_PERIOD);
// 新API
//            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//                mBluetoothAdapter.getBluetoothLeScanner().startScan(this);
//            }else {
          // this 实现了BluetoothAdapter.LeScanCallback,即扫描结果回掉
                mBluetoothAdapter.startLeScan(this);
//            }
            mScanning = true;
            Log.i(TAG, "开始扫描,蓝牙设备");
        }
    }

回调函数

@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
 // 得到扫描结果
    Log.i(TAG, "扫描到的设备, name=" + device.getName() + ",address=" + device.toString());
}

留意:device:代表外设即指标设备
rssi:设三个强度值,不过时负值,利用这么些值通过公式能够算出离你的间隔

scanRecord:广播数据,附加的多寡,没用到

停下扫描

环视完结必需停止,因为扫描不唯有耗能,还影响连接速度,所以当要一连的时候,先结束扫描时必须的

public void stopScan() {
    initHandler();
    Log.i(TAG, "停止扫描,蓝牙设备");
    if (mScanning) {
        mScanning = false;
        // 开始扫描的接口,要一样的不然停止不了
        mBluetoothAdapter.stopLeScan(this);
    }

    if (scanRunnable != null) {
        mHandler.removeCallbacks(scanRunnable);
        scanRunnable = null;
    }
}

老是装置

平常来说连接装置速度依然火速的,连接理论上的话也是无状态的,所以也急需二个定式职分来,保险超时结束。

public boolean connect(Context context, String address) {
    if (mConnectionState == STATE_CONNECTED) {
        return false;
    }
    if (mBluetoothAdapter == null || TextUtils.isEmpty(address)) {
        return false;
    }
    initHandler();
    if (connectRunnable == null) {
        connectRunnable = new Runnable() {
            @Override
            public void run() {
                mConnectionState = STATE_DISCONNECTING;
                disconnect();
            }
        };
    }
  // 30s没反应停止连接
    mHandler.postDelayed(connectRunnable, 30 * 1000);
    stopScan();
// 获取到远程设备,
    final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
    if (device == null) {
        return false;
    }
    // 开始连接,第二个参数表示是否需要自动连接,true设备靠近自动连接,第三个表示连接回调
    mBluetoothGatt = device.connectGatt(context, false, mGattCallback);
    mBluetoothDeviceAddress = address;
    mConnectionState = STATE_CONNECTING;
    return true;
}

监听连接回调

private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
// 连接状态变化
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        if (newState == BluetoothProfile.STATE_CONNECTED) { // 连接上
            mConnectionState = STATE_CONNECTED;
            boolean success = mBluetoothGatt.discoverServices(); // 去发现服务
            Log.i(TAG, "Attempting to start service discovery:" +
                    success);
        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { // 连接断开
            mConnectionState = STATE_DISCONNECTED;       
        }
    }

// 发现服务
    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        if (status == BluetoothGatt.GATT_SUCCESS) {
            Log.i(TAG,"发现服务");
            // 解析服务
            discoverService();
        }
    }

// 特征读取变化
    @Override
    public void onCharacteristicRead(BluetoothGatt gatt,
                                     BluetoothGattCharacteristic characteristic,
                                     int status) {
        if (status == BluetoothGatt.GATT_SUCCESS) {

        }
    }

// 收到数据
    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt,
                                        BluetoothGattCharacteristic characteristic) {
        mConnectionState = STATE_CONNECTED;

    }
};

断开连接

public void disconnect() {
    if (mBluetoothAdapter == null || mBluetoothGatt == null) {
        mConnectionState = STATE_DISCONNECTED;
        return;
    }
    // 连接成功的GATT
    mBluetoothGatt.disconnect();
    mBluetoothGatt.close();
}

相比卓绝蓝牙5.0,Bluetooth低耗电(BLE)能够明显滑坡电量消耗。那使得Android应用能够与对电量有着更严酷界定的配备通讯,例如地点传感器,心率监视器,以致健美设备。

开垦Bluetooth功效

为了保险 蓝牙( Bluetooth® 卡塔尔成效是开荒的, 调用 Bluetooth( Bluetooth® 卡塔尔(قطر‎艾达pter 的 isEnable(State of Qatar 方法,
检查蓝牙5.0在脚下是否可用. 假使回去 false, 表达当前蓝牙( Bluetooth® 卡塔尔国不可用.

// 确认当前设备的蓝牙是否可用,   
// 如果不可用, 弹出一个对话框, 请求打开设备的蓝牙模块  
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {  
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);  
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);  
}  

多配备连接

蓝牙5.0适配器未有听过连年七个道具的接口,必要我们和煦实现,即获得到指标设备的address后调用连接方式,自个儿维护多少个蓝牙( Bluetooth® 卡塔尔国Gatt就可以(代码稍后放出卡塔尔国。

重大术语和定义

以下是对BLE关键术语和定义的下结论:

  • Generic Attribute Profile (GATT卡塔尔国–GATT
    profile是发送和收取一些些数额的一种通用规范,正如大家精晓的在BLE连接上的“attributes”。当前享有的低功耗应用profile都以根据GATT。

    • Bluetooth技巧联盟为低功耗设备定义了多数profile。profile正是内定设备在特定的利用中怎么着工作的一种标准。比方,八个配备得以饱含叁个心率监视器和一个电量状态监测器。
  • Attribute Protocol
    (ATT卡塔尔(قطر‎–GATT是确立在ATT的底工之上。所以也叫做GATT/ATT。ATT在BLE设备上运转经过了优化,由此,它会尽量使用更加少的字节数组。每一个属性通过UUID来独一确定,UUID是一种1二十10个人的口径格式字符串ID来标志新闻的独一性。属性会被格式化为特征(characteristics
    )与劳动(services)后经过ATT来传输。

  • 特色(Characteristic)–叁个风味包罗了贰个单身的值和0-n个描述符(descriptors
    ,用来描述特征的值)。多少个特征能够作为是一种等级次序,相似一个class。

  • 陈诉符(Descriptor)–描述符定义了属性用来陈述特征值。举个例子,描述符能够是人人可读的叙说语句,可以是特点值得可选拔范围,或然是特征值的度量单位。

  • 服务(Service)–服务是特色的汇集。例如,你有四个称为‘心率监视器’的劳动,在这里个服务里带有三个“心率度量”的性状。你能够在bluetooth.org上查看一比比皆已经基于GATT的profile与劳务。

研究设备

查找 BLE 设备, 调用 BluetoothAdapter 的 startLeScan(卡塔尔 方法,
该办法必要二个 Bluetooth( Bluetooth® 卡塔尔(قطر‎Adapter.LeScanCallback 类型的参数.
你必须兑现这些 LeScanCallback 接口, 因为 BLE
蓝牙5.0设备扫描结果在这里个接口中再次来到.蓝牙5.0找寻是三个功耗的操作,由此蓝牙5.0找寻提供了三种检索形式:
暂停寻觅:一物色到器械就浅尝辄止寻觅
不循环找出:给搜索设置多个确切的扫视周期

扫描设备:

private void scanLeDevice(final boolean enable) {
        if (enable) {
            // Stops scanning after a pre-defined scan period.
            mHandler.postDelayed(new Runnable() {

                public void run() {
                    isScanned = false;
                    refreshLayout.setRefreshing(false);
                    mBluetoothAdapter.stopLeScan(mLeScanCallback);
                }
            }, SCAN_PERIOD);

            isScanned = true;
            mBluetoothAdapter.startLeScan(mLeScanCallback);
        } else {
            if (refreshLayout.isRefreshing()){
                refreshLayout.setRefreshing(false);
            }
            isScanned = false;
            mBluetoothAdapter.stopLeScan(mLeScanCallback);
        }
    }

回调代码:

 private final LeScanCallback mLeScanCallback = new LeScanCallback() {

        public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
            runOnUiThread(new Runnable() {
                public void run() {
                    if (!devices.contains(device)){
                        devices.add(device);
                        adapter.notifyDataSetChanged();
                    }

                }
            });
        }
    };

全体代码示例

正值做心率有关的Bluetooth设备,此处代码发出来。

代码还在宏观中,仅供参照他事他说加以考察,稳定代码将会发在Github中

package com.lowett.android.ble;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.IntDef;
import android.text.TextUtils;

import com.fit.android.utils.Logger;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.Locale;
import java.util.UUID;

/**
 * Email: fvaryu@qq.com
 */
public class BluetoothLeManager implements BluetoothAdapter.LeScanCallback {
    public static final int REQUEST_ENABLE_BT = 1;
    private static final int SCAN_PERIOD = 3 * 10 * 1000;

    static final int STATE_DISCONNECTED = 1;
    public static final int STATE_CONNECTING = 2;
    public static final int STATE_DISCONNECTING = 3;
    public static final int STATE_CONNECTED = 4;
    public static final int STATE_DISCOVER_SERVICES = 5;

    @IntDef(value = {STATE_DISCONNECTED, STATE_CONNECTING, STATE_CONNECTED, STATE_DISCONNECTING, STATE_DISCOVER_SERVICES})
    @Retention(RetentionPolicy.SOURCE)
    public @interface State {
    }

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

    private static BluetoothLeManager ourInstance = new BluetoothLeManager();

    //    private Context mContext;
    private boolean is_inited = false;

    private android.bluetooth.BluetoothManager mBluetoothManager;
    private BluetoothAdapter mBluetoothAdapter;
    private String mBluetoothDeviceAddress;
    private int mConnectionState;
    private BluetoothGatt mBluetoothGatt;

    private boolean mScanning;
    private Runnable scanRunnable;
    private Handler mHandler;

    private Runnable connectRunnable;

    private OnDataReceivedListener mOnDataReceivedListener;
    // 记得清掉监听 泄漏
    private OnLeScanListener mOnLeScanListener;
    private OnConnectionStateChangeListener mOnConnectionStateChangeListener;

    private int retryCount;

    public static BluetoothLeManager getInstance() {
        return ourInstance;
    }

    private BluetoothLeManager() {
    }

    public void setOnDataReceivedListener(OnDataReceivedListener onDataReceivedListener) {
        mOnDataReceivedListener = onDataReceivedListener;
    }

    public interface OnConnectionStateChangeListener {
        void onConnectionStateChange(BluetoothGatt gatt, int status, int newState);

        void onConnectTimeout();
    }

    public void setOnConnectionStateChangeListener(OnConnectionStateChangeListener onConnectionStateChangeListener) {
        mOnConnectionStateChangeListener = onConnectionStateChangeListener;
    }

    public interface OnLeScanListener {
        void onLeScan(BluetoothDevice device);
    }

    public interface OnDataReceivedListener {
        void onDataReceived(int heart);
    }

    private void init() {
        if (!is_inited) {
            is_inited = true;
        }
    }

    private boolean initialize(Context context) {
        init();
        if (!is_inited) {
            throw new RuntimeException("请先调用init");
        }

        if (mBluetoothAdapter != null) {
            return true;
        }
        // For API level 18 and above, get a reference to BluetoothAdapter through
        // BluetoothLeManager.
        if (mBluetoothManager == null) {
            mBluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
            if (mBluetoothManager == null) {
                return false;
            }
        }

        mBluetoothAdapter = mBluetoothManager.getAdapter();
        return mBluetoothAdapter != null;
    }

    public boolean isSupportBluetoothLe(Activity activity) {
        return activity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
    }

    public boolean isSupportBluetooth(Context context) {
        return initialize(context);
    }

    public void enableBluetooth(Activity activity) {
        if (!initialize(activity)) {
            return;
        }
        if (!mBluetoothAdapter.isEnabled()) {
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            activity.startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
        }
    }

    public boolean isEnabled(Context context) {
        return initialize(context) && mBluetoothAdapter.isEnabled();
    }

    private void initHandler() {
        if (mHandler == null) {
            mHandler = new Handler(Looper.getMainLooper());
        }
    }

    public void startScan(OnLeScanListener onLeScanListener) {
        initHandler();
        if (!mScanning) {
            if (scanRunnable == null) {
                scanRunnable = new Runnable() {
                    @Override
                    public void run() {
                        stopScan();
                    }
                };
            }

            mHandler.postDelayed(scanRunnable, SCAN_PERIOD);

//            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//                mBluetoothAdapter.getBluetoothLeScanner().startScan(this);
//            }else {
            mBluetoothAdapter.startLeScan(this);
//            }

            mScanning = true;

            this.mOnLeScanListener = onLeScanListener;

            Logger.i("开始扫描,蓝牙设备");
        }
    }

    @Override
    public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
        final BluetoothDevice tmp = device;
        Logger.i("扫描到的设备, name=" + device.getName() + ",address=" + device.toString());
        if (mOnLeScanListener != null) {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    mOnLeScanListener.onLeScan(tmp);
                }
            });
        }
    }

    public void stopScan() {
        initHandler();
        mOnLeScanListener = null;
        Logger.i("停止扫描,蓝牙设备");
        if (mScanning) {
            mScanning = false;
            mBluetoothAdapter.stopLeScan(this);
        }

        if (scanRunnable != null) {
            mHandler.removeCallbacks(scanRunnable);
            scanRunnable = null;
        }
    }

    private void removeConnectRunnable() {
        if (connectRunnable != null) {
            mHandler.removeCallbacks(connectRunnable);
            connectRunnable = null;
        }
    }

    private void retry() {
        if (TextUtils.isEmpty(mBluetoothDeviceAddress)) {
            return;
        }
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                if (++retryCount < 11 && mConnectionState < STATE_CONNECTED) {
                    reconnect(retryCount);
                    mHandler.postDelayed(this, retryCount * 5 * 1000);
                    Logger.i("蓝牙重试次数=" + retryCount);
                }

            }
        }, 2000);
    }

    private void reconnect(int count) {
        if ((mConnectionState >= STATE_CONNECTING)) {
            return;
        }

        if (connectRunnable == null) {
            connectRunnable = new Runnable() {
                @Override
                public void run() {
                    mConnectionState = STATE_DISCONNECTING;
                    disconnect();
                }
            };
        }
        mHandler.postDelayed(connectRunnable, count * 3 * 1000);

        if (mBluetoothDeviceAddress != null
                && mBluetoothGatt != null) {
            mBluetoothGatt.connect();
            mConnectionState = STATE_CONNECTING;
        }
    }

    public boolean connect(Context context, String address) {
        if (mConnectionState == STATE_CONNECTED) {
            return false;
        }
        if (mBluetoothAdapter == null || TextUtils.isEmpty(address)) {
            return false;
        }
        initHandler();
        if (connectRunnable == null) {
            connectRunnable = new Runnable() {
                @Override
                public void run() {
                    mConnectionState = STATE_DISCONNECTING;
                    disconnect();

                    if (mOnConnectionStateChangeListener != null) {
                        mOnConnectionStateChangeListener.onConnectTimeout();
                    }
                }
            };
        }
        mHandler.postDelayed(connectRunnable, 30 * 1000);

        stopScan();

        // Previously connected device.  Try to reconnect.
        if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress)
                && mBluetoothGatt != null) {
            if (mBluetoothGatt.connect()) {
                mConnectionState = STATE_CONNECTING;
                return true;
            } else {
                return false;
            }
        }

        final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
        if (device == null) {
            return false;
        }
        // We want to directly connect to the device, so we are setting the autoConnect
        // parameter to false.
        mBluetoothGatt = device.connectGatt(context, false, mGattCallback);
        mBluetoothDeviceAddress = address;
        mConnectionState = STATE_CONNECTING;
        return true;
    }

    public void disconnect() {
        if (mBluetoothAdapter == null || mBluetoothGatt == null) {
            Logger.i("BluetoothAdapter not initialized");
            mConnectionState = STATE_DISCONNECTED;
            return;
        }

        mBluetoothGatt.disconnect();

    }

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

    public void disconnectNoRetry() {
        mBluetoothDeviceAddress = null;
        close();
    }

    private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {

        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                mConnectionState = STATE_CONNECTED;
                boolean success = mBluetoothGatt.discoverServices();
                Logger.i("Attempting to start service discovery:" +
                        success);
                removeConnectRunnable();

                Logger.i("链接上");
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                mConnectionState = STATE_DISCONNECTED;
                Logger.i("断开链接");

                retry();
            }

            if (mOnConnectionStateChangeListener != null) {
                mOnConnectionStateChangeListener.onConnectionStateChange(gatt, status, newState);
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                Logger.i("发现服务");
                discoverService();
            }

        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt,
                                         BluetoothGattCharacteristic characteristic,
                                         int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(characteristic);
            }
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt,
                                            BluetoothGattCharacteristic characteristic) {
            mConnectionState = STATE_CONNECTED;
            broadcastUpdate(characteristic);
        }
    };

    /**
     * Retrieves a list of supported GATT services on the connected device. This should be
     * invoked only after {@code BluetoothGatt#discoverServices()} completes successfully.
     *
     * @return A {@code List} of supported services.
     */
    private List<BluetoothGattService> getSupportedGattServices() {
        if (mBluetoothGatt == null) return null;
        return mBluetoothGatt.getServices();
    }

    private void discoverService() {
        if (mConnectionState == STATE_DISCOVER_SERVICES) {
            return;
        }
        mConnectionState = STATE_DISCOVER_SERVICES;
        List<BluetoothGattService> list = getSupportedGattServices();
        /**
         *  BluetoothGattService = 00001800-0000-1000-8000-00805f9b34fb
         BluetoothGattCharacteristic = 00002a00-0000-1000-8000-00805f9b34fb
         BluetoothGattCharacteristic = 00002a01-0000-1000-8000-00805f9b34fb
         BluetoothGattCharacteristic = 00002a04-0000-1000-8000-00805f9b34fb

         BluetoothGattService = 00001801-0000-1000-8000-00805f9b34fb
         BluetoothGattCharacteristic = 00002a05-0000-1000-8000-00805f9b34fb

         心跳服务
         BluetoothGattService = 0000180d-0000-1000-8000-00805f9b34fb
         心跳特征
         BluetoothGattCharacteristic = 00002a37-0000-1000-8000-00805f9b34fb
         BluetoothGattCharacteristic = 00002a38-0000-1000-8000-00805f9b34fb

         BluetoothGattService = 0000180f-0000-1000-8000-00805f9b34fb
         BluetoothGattCharacteristic = 00002a19-0000-1000-8000-00805f9b34fb

         // 设备名字
         BluetoothGattService = 0000180a-0000-1000-8000-00805f9b34fb
         BluetoothGattCharacteristic = 00002a28-0000-1000-8000-00805f9b34fb
         */
        for (BluetoothGattService s : list) {
            if (!SampleGattAttributes.HEART_RATE_SERVICES.equals(s.getUuid().toString())) {
                continue;
            }
            final List<BluetoothGattCharacteristic> l = s.getCharacteristics();
            for (final BluetoothGattCharacteristic bc : l) {
                if (!SampleGattAttributes.HEART_RATE_MEASUREMENT.equals(bc.getUuid().toString())) {
                    continue;
                }
                Logger.i("连接蓝牙 服务成功");
                setCharacteristicNotification(bc, true);
                return;
            }
        }
    }

    private void broadcastUpdate(final BluetoothGattCharacteristic characteristic) {

        // This is special handling for the Heart Rate Measurement profile.  Data parsing is
        // carried out as per profile specifications:
        // http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_measurement.xml
        if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
            int flag = characteristic.getProperties();
            int format = -1;
            if ((flag & 0x01) != 0) {
                format = BluetoothGattCharacteristic.FORMAT_UINT16;
            } else {
                format = BluetoothGattCharacteristic.FORMAT_UINT8;
            }
            int heartRate = characteristic.getIntValue(format, 1);
            Logger.i(String.format(Locale.getDefault(), "Received heart rate: %d", heartRate));
            if (mOnDataReceivedListener != null) {
                mOnDataReceivedListener.onDataReceived(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());
//            }
//        }
        /**
         * 2、
         */
//        sendBroadcast(intent);
    }

    public boolean isConnected() {
        return mConnectionState == STATE_CONNECTED;
    }

    public String getConnectedAddress() {
        if (!isConnected()) {
            return null;
        }
        return mBluetoothDeviceAddress;
    }

    public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
        if (mBluetoothAdapter == null || mBluetoothGatt == null) {
            Logger.i("BluetoothAdapter not initialized");
            return;
        }
        mBluetoothGatt.readCharacteristic(characteristic);

    }

    private void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
                                               boolean enabled) {
        if (mBluetoothAdapter == null || mBluetoothGatt == null) {
            Logger.i("BluetoothAdapter not initialized");
            return;
        }
        mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);

        // This is specific to Heart Rate Measurement.
        if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
            BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
                    UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
            descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
            mBluetoothGatt.writeDescriptor(descriptor);
        }
    }

    public void clear() {
        mOnLeScanListener = null;
        mOnConnectionStateChangeListener = null;
    }

    public void release() {
//        connectRunnable = null;
//        mHandler = null;
//         ourInstance = null;
    }
}

剧中人物与任务

以下是Android设备与BLE设备交互作用时的剧中人物和义务:

  • 大旨 vs
    外设。那适用于BLE自个儿的连天。主旨配备扫描,寻觅广告,外设发送广告。
  • GATT服务 vs
    GATT顾客端。那决定了多个器具之间怎样通讯一旦他们树立了连年。

为了越来越好的接头不一样,想象下假设你有一个Android手机与七个负有蓝牙( Bluetooth® 卡塔尔低耗能的移动跟踪器设备。手提式有线电话机支持宗旨剧中人物;活动追踪器帮助外设剧中人物(为了树立连接四个设施必需是分化的角色,不能够况兼为同一个剧中人物)。
若是手提式有线电话机与蓝牙( Bluetooth® State of Qatar设备创设了连年。他们就能够传导GATT媒体数据到另一配备。依照传输的数量,它们中间叁个亟待作为服务端。例如,假诺运动追踪设备想要上传传感数据到手提式有线电电话机,那么该运动追踪器就充作服务端。假若移动追踪设备想要接纳手提式有线电话机传来的多少,那么手提式有线电话机正是服务端。

在小说中举的例子中,Android应用是GATT顾客端。app从GATT服务端获取数据,这么些服务也正是永葆
心率Profile的心率监视器。当然你也可以是你的Android
app扮演服务端剧中人物。能够查看BluetoothGattServer赢得越多新闻。

招来特定设备

找出特定项目标外围设备, 能够调用上边包车型大巴点子, 那个办法要求提供一个 UUID
对象数组, 那些 UUID 数组是 应用软件 扶植的 GATT 服务的超过常规规标志.

startLeScan(UUID[], BluetoothAdapter.LeScanCallback)

总结

项目还在开展中,碰到的标题将会持续抛出,完备此文书档案。

BLE权限

为了在您的采纳中运用Bluetooth,你须要表达蓝牙5.0权限BLUETOOTH。你须要那几个权力来兑现别的的Bluetooth通讯,譬如诉求连接,接受一连以至传输数据。

万一你想是您的app开启设备找寻甚至管理蓝牙( Bluetooth® 卡塔尔国设置,你必需表明BLUETOOTH_ADMIN权力。注意:假诺您发明了BLUETOOTH_ADMIN权限,那么您不得不同期表明BLUETOOTH权限。

在你的manifest文件里表明权限,如下:

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

假使您想你的利用只可以在援救BLE的设施上应用,你能够加上以下表达:

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

而是,如若您想在不扶植BLE的设备上使用你的app,你也亟需加上上边的阐明,只是供给将required设为false。那样你能够在运行时经过调用
PackageManager.hasSystemFeature()卡塔尔来判别该器械是不是帮衬BLE。

// 用以下方式来判断设备是否支持BLE,从而选择性的禁用BLE相关特性
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
    Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
    finish();
}

在乎:BLE平日与岗位有联系。所认为了不带过滤器的选择
BluetoothLeScanner,你必须要表达ACCESS_COARSE_LOCATION或者ACCESS_FINE_LOCATION权限来走访客户的职位信息。若无这个权限,那么扫描将会并未有别的结果。

连接到GATT服务

调用 蓝牙( Bluetooth® State of Qatar( Bluetooth® 卡塔尔Device 的 connectGatt(卡塔尔国 方法能够接二连三到 BLE 设备的 GATT
服务. 参数一 Context 上下文对象, 参数二 boolean autoConnect
是不是自动连接扫描到的蓝牙( Bluetooth® State of Qatar设备, 参数三 BluetoothGattCallback 接口完结类.

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

设置BLE

在您的运用使用BLE通讯以前,你需求认同你的装置是或不是帮助BLE,假诺补助,那么有限支撑蓝牙5.0是打开的。注意这些决断只有在<uses-feature/>标签设为false的时候才是必需的。

设若设备不帮衬BLE,那么你应当文雅的剥夺任何Bluetooth功效。若是辅助BLE,但还未有拉开的话,你能够让客户在采纳内展开蓝牙( Bluetooth® 卡塔尔(قطر‎。你还行BluetoothAdapter透过以下两步来变成该操作:
1.获取BluetoothAdapter
蓝牙( Bluetooth® 卡塔尔Adapter对于持有Bluetooth活动都以必得。BluetoothAdapter代表设备本身的蓝牙( Bluetooth® State of Qatar适配器。整个设施系统中独有两个Bluetooth适配器,你的运用能够用这几个指标来和它交互作用。以下代码片展示了什么样获得那么些适配器。注意这里运用getSystemService()State of Qatar来回到多少个BluetoothManager实例,那能够用来收获适配器。Android
4.3(API 18)才引进的蓝牙5.0Manager:

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

2.敞开蓝牙( Bluetooth® 卡塔尔国
接下去,你供给断定Bluetooth是否张开。调用
isEnabled()卡塔尔(قطر‎方法来承认当前蓝牙( Bluetooth® State of Qatar是否展开。假诺回去false,则Bluetooth未展开。下边包车型大巴代码片判定蓝牙( Bluetooth® 卡塔尔国是不是开启。如果未有,则提示客商展开Bluetooth:

// 确保设备支持蓝牙,并已开启,如果未开启,则显示一个弹窗来获取用户权限打开蓝牙
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

注意:传入startActivityForResult的常量REQUEST_ENABLE_BT
是概念在本土的int类型,系统会将这些常量作为requestCode参数回传到onActivityResult方法。

GATT数据人机联作

这段代码的面目便是 BLE 设备的 GATT 服务 与 Android 的 BLE API 举行交流.
当叁个特定的回调被触发, 它调用适当的 broadcastUpdate(卡塔尔 帮助方法,
将其充任多个 Action 操作传递出去.

/**
     * Implements callback methods for GATT events that the app cares about.
     * For example,connection change and services discovered.
     */
    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;
                broadcastUpdate(intentAction);
                Log.i(TAG, "Connected to GATT server.");
                // Attempts to discover services after successful connection.
                Log.i(TAG, "Attempting to start service discovery:" + mBluetoothGatt.discoverServices());

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

搜索BLE设备

您能够行使
startLeScan()卡塔尔(قطر‎方法来搜索设备。那一个方法要求传入叁个回调参数BluetoothAdapter.LeScanCallback。你一定要兑现这一个回调接口,因为寻找结果会经过那么些回调重返。由于扫描是耗能的,你须求根据以下法规:

  • 当搜索到您想要的装置就应当结束扫描
  • 不可能最佳的围观下去,需求给扫描做个日子范围。以前能够访谈的装置恐怕不仅仅间距节制,继续扫描只会消耗能量。

以下代码片呈现了什么早先以至截至扫描:

/**
 * 一个用于扫描的Activity并展示搜到的设备
 */
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)卡塔尔方法,须要传入你app协助的一定GATT服务的UUID对象数组。
以下是
BluetoothAdapter.LeScanCallback接口实现,用来回到扫描结果:

private LeDeviceListAdapter mLeDeviceListAdapter;
...
// 设备扫描回调
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();
           }
       });
   }
};

注意:正如在作品
Bluetooth中描述的,你只好扫描BLE设备恐怕杰出Bluetooth设备个中一种,不可能同一时候扫视。

读取 BLE 属性

到了这里自身就要详细的授课一下BLE GATT
PROFILE的组织布局了。每一个Bluetooth设备都有三个Profile(就把这一个Profile想象成是叁个Bluetooth模块),各个Profile有八个service(服务)如电量音讯服务、系统音讯服务等,每种service有多少个Characteristic(特征),每一种特征里面包蕴属性(properties)和值(value)和若干个descriptor(描述符)。我们刚刚提到的service和characteristic,都供给一个独一的uuid来标志,如图:
[图形上传失利…(image-89e748-1514968778904State of Qatar]
Android 应用连接到了 设备中的 GATT 服务, 並且开采了 各个服务 (特征集合State of Qatar,
能够读写个中的性质,遍历服务 (特征群集卡塔尔国 和 特征, 将其出示在 UI 界面中.

 // 遍历 GATT 服务  
        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>();  

           // 循环遍历特征集合  
            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设备通讯的第一步正是连上它–更具象的讲就是接二连三上设备的GATT服务。你能够应用connectGatt(State of Qatar方法来三番若干次装置上的GATT服务。那么些办法需求传入四个参数:贰个Context对象,autoConnect(用来标识是不是当BLE设备被搜届时就马上连接上),还也可能有个BluetoothGattCallback回调参数:

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

上边代码会接连上BLE设备中的GATT服务,并回到三个BluetoothGatt实例,你能够用那么些实例来做GATT客商端的操作。调用者(Android
app)是GATT客商端。
BluetoothGattCallback回调用来向客商端重返结果,譬喻总是情状以至越多的GATT客户端操作。

以此本文的例子中,BLE app提供一个Activity(DeviceControlActivity
)来三回九转,展现数据,以致显示蓝牙5.0设备所帮忙的GATT服务与特点。基于客户的输入,那些活动会和蓝牙( Bluetooth® State of QatarLeService服务通讯,那一个服务通过Android
BLE API来和BLE设备人机联作:

// 通过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);

    // 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
        // 发现新的服务是回调
        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
        // 读取特征回调
        public void onCharacteristicRead(BluetoothGatt gatt,
                BluetoothGattCharacteristic characteristic,
                int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
            }
        }
     ...
    };
...
}

当有些特定的回调触发时,会调用broadcastUpdate(卡塔尔(قطر‎方法,并传播一个action。注意这里的数目深入解析是对应于本例子中的蓝牙( Bluetooth® 卡塔尔心率度量仪:

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);

    // 这是心率测量仪profile特有的数据解析方式
    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 {
        // 对于其他profile,将数据转换为16进制
        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接收到:

// 处理各种事件
// ACTION_GATT_CONNECTED: 连接到设备
// ACTION_GATT_DISCONNECTED:断开连接
// ACTION_GATT_SERVICES_DISCOVERED: 发现GATT服务
// ACTION_DATA_AVAILABLE: 从设备接收数据,这可以是读操作或 通知的结果
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)) {
            //向用户展示所有支持的服务与特征
            displayGattServices(mBluetoothLeService.getSupportedGattServices());
        } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
            displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA));
        }
    }
};
接收GATT通知

当 BLE 设备中的一些分裂常常的天性改动, 供给布告与之连接的 Android BLE
应用,使用 setCharacteristicNotification(State of Qatar 方法为特点设置通告.

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);  

举个例子特征开启了变动通告监听, 假如特性产生了变动, 就可以回调
蓝牙( Bluetooth® State of QatarGattCallback 接口中的 onCharacteristicChanged(State of Qatar 方法.

@Override  
// 特性通知  
public void onCharacteristicChanged(BluetoothGatt gatt,  
        BluetoothGattCharacteristic characteristic) {  
    broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);  
}  

读取BLE属性

假让你的使用连接上GATT服务并搜到服务,那么就足以读写所支撑的性质。例如,以下代码片正是迭代器材的服务与天性并将它们展以往UI上:

public class DeviceControlActivity extends Activity {
    ...
    //演示了如何遍历服务和特征,在这个例子中将数据结构展示到树形列表上
    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>>();

        // 循环遍历服务
        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>();
           // 循环遍历特征
            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);
         }
    ...
    }
...
}
关闭APP中的BLE连接

假使截止了 BLE 设备的采用, 调用 BluetoothGatt 的 close(卡塔尔(قطر‎ 方法, 关闭 BLE
连接, 释放相关的资源.

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

完整项目链接,访问作者的开源中华夏儿女民共和国账号osgit

接收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);

如若特征设置了布告,那么当远程设备的特点产生转移时就能回调
onCharacteristicChanged(State of Qatar 方法:

@Override
// 特征通知
public void onCharacteristicChanged(BluetoothGatt gatt,
        BluetoothGattCharacteristic characteristic) {
    broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}

关闭App客户端

借令你的利用结束使用BLE设备,你就应当调用close方法来释放系统能源:

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

好了上述正是合法关于蓝牙( Bluetooth® 卡塔尔国BLE的介绍,恐怕还是相比较含糊,下一篇笔者会结合一个实际的例证来介绍下。

官方最初的文章:https://developer.android.com/guide/topics/connectivity/bluetooth-le.html#close

.

.

发表评论

电子邮件地址不会被公开。 必填项已用*标注