文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android蓝牙BLE开发

2023-09-01 19:29

关注

最近正在研究Android的蓝牙BLE开发学习,以下是自己做的个人总结

1.1何为BLE?

首先得说明什么是低功耗蓝牙BLE,BLE的全称为Bluetooth low energy(或称Blooth LE,BLE),从英文全称便可以知晓其是一种低功耗的蓝牙技术,是蓝牙技术联盟设计和销售的一种个人局域网技术,旨在用于医疗保健、运动健身、信标、安防、家庭娱乐等领域的新兴应用。相较经典蓝牙,低功耗蓝牙旨在保持同等通信范围的同时显著降低功耗和成本。而正因为其低功耗的优点,可以让Android APP可以具有与低功耗要求的BLE设备通信,如近距离传感器、心脏速率监视器、健身设备等

1.2基础术语和概念

在正式开发前,要对基本的蓝牙的术语和概念要有个大致的认识,因为我本人学习的也不长,就是个简单的总结先:

Generic Attribute Profile:简称为GATT,现在的低功耗BLE的连接都是建立在GATT协议之上实现的,蓝牙技术联盟规定了许多低功耗设备的配置文件,配置文件是设备如何在特定的应用程序在工作的规格,而一个设备中可以有多个配置文件。
Generic Access Profile:Profile可以视为一种规范,一个标准的通信协议,它存在于从机中,蓝牙技术联盟规定了一些标准的profile,例如防丢器 ,心率计等。每个profile中会包含多个service,每个service代表从机的一种能力。
Service:服务,在BLE从机中,可以有多个服务,例如:电量信息服务,而Service中又有多个Characteristic特征值,而每个具体的特征值才是BLE通信的重点,例如在1电量信息服务中,当前的电量为80%,所以会通过电量的特征值存在从机的profile里,这样主机就可以通过这个特征值来读取80%这个数据
Characteristic:特征值,ble主从机通信都是通过特征值实现的,类似于标签key,可以通过这个key值来获取信息和数据
UUID:统一识别码,服务和特征值都需要一个唯一的UUID来标识整理,而每个从机都会有一个叫做profile的东西存在,不管是上面的自定义的simpleprofile,还是标准的防丢器profile,他们都是由一些列service组成,然后每个service又包含了多个characteristic,主机和从机之间的通信,均是通过characteristic来实现。

1.3初始化配置

讲完了大概的概念之后便是基本的操作了,以下内容会结合代码和流程图进行展示

在这里插入图片描述

1.3.1权限

想要使用BLE开发,就得先获得蓝牙必要的权限,需要先在AndroidManifest.xml中设置权限

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

如果想声明你的app只为具有BLE的设备提供,在manifest文件中包括:

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

除此之外,如果是Android 6.0以上的手机仅仅是添加以上的蓝牙权限是不足的,这样会造成无法扫描到其他设备,因而还需要添加位置权限:

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />    <uses-feature android:name="android.hardware.location.gps" />

1.3.2是否支持蓝牙BLE

required=true只能是让支持BLE的Android设备上安装运行,不支持的则不行,如果想在Java实现上述功能,可以通过下述代码:

// 手机硬件支持蓝牙if(!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {    Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();    finish();}

1.3.3初始化蓝牙适配器

所有的蓝牙活动都需要蓝牙适配器,BluetoothAdapter代表设备本身的蓝牙适配器。整个系统只有一个蓝牙适配器,而且app需要蓝牙适配器与系统交互。下面的代码片段显示了如何得到适配器。

注意该方法使用getSystemService()返回BluetoothManager,然后将其用于获取适配器的一个实例。Android 4.3(API 18)引入BluetoothManager

final BluetoothManager bluetoothManager =(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.LOLLIPOP)    mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();else    mBluetoothAdapter = bluetoothManager.getAdapter();if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)    mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();

1.3.4开启蓝牙

要操作蓝牙,必须先在设备中开启蓝牙。如果当前未启用蓝牙,则可以通过触发一个Intent调用系统显示一个对话框来要求用户启用蓝牙权限

// 打开蓝牙权限if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {    Intent enableBtIntent = new Intent(        BluetoothAdapter.ACTION_REQUEST_ENABLE);    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);}

1.3.5初始化ListView列表适配器

private class LeDeviceListAdapter extends BaseAdapter {    private ArrayList<BluetoothMessage> mLeDevices;        private LayoutInflater mInflator;        public LeDeviceListAdapter()    {        super();        //rssis = new ArrayList();        mLeDevices = new ArrayList<BluetoothMessage>();        mInflator = getLayoutInflater();     }    public void addDevice(BluetoothMessage device)    {        for (BluetoothMessage mLeDevice : mLeDevices) {                        if(mLeDevice.getDevice().getAddress().equals(device.getDevice().getAddress())){                return;                }            }            mLeDevices.add(device);        //rssis.add(rssi);    }    public BluetoothMessage getDevice(int position)    {    return mLeDevices.get(position);    }    public void clear()    {        mLeDevices.clear();        //rssis.clear();    }    @Override    public int getCount()    {    return mLeDevices.size();    }@Override    public Object getItem(int i)    {        return mLeDevices.get(i);    }    @Override    public long getItemId(int i)    {        return i;    }       @Override    public View getView(int i, View view, ViewGroup viewGroup)    {        // General ListView optimization code.        // 加载listview每一项的视图        BluetoothMessage bluetoothMessage = mLeDevices.get(i);        return view;    }}

1.3.6发现BLE设备(扫描设备)

如果要发现BLE设备,使用startLeScan()方法进行扫描,扫描的话就要传入true执行scanLeDvice(true)方法,然后蓝牙适配器就调用startLeScan()方法进行扫描,LeScanCallback是扫描回调,也就是返回扫描结果。

1.3.6.1扫描结果:

private void scanLeDevice(final boolean enable) {    if (mBluetoothAdapter == null){        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)            mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();        else {            BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);            mBluetoothAdapter = bluetoothManager.getAdapter();        }  }    if (mBluetoothLeScanner == null){        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)            mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();        }    if (enable) {        // Stops scanning after a pre-defined scan period.        mHandler.postDelayed(new Runnable()        {            @Override            public void run()            {                mScanning = false;                scan_flag = true;                scan_btn.setText("扫描设备");                Log.i("SCAN", "stop.....................");                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)                    mBluetoothLeScanner.stopScan(mScanCallback);                else                    mBluetoothAdapter.stopLeScan(mLeScanCallback);             }         }, SCAN_PERIOD);                     Log.i("SCAN", "begin.....................");            mScanning = true;            scan_flag = false;            scan_btn.setText("停止扫描");            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)                mBluetoothLeScanner.startScan(mScanCallback);            else                mBluetoothAdapter.startLeScan(mLeScanCallback);     } else {            Log.i("Stop", "stoping................");            mScanning = false;            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)                mBluetoothLeScanner.stopScan(mScanCallback);            else                mBluetoothAdapter.stopLeScan(mLeScanCallback);            scan_flag = true;      }}

1.3.6.2回调方法:

在这里回家上面的扫描方法的扫描结果回调,也就是传回来。其中在onLeScan方法是重点,蓝牙扫描成功后的结果会返回此方法中,然后就可以处理BluetoothDevice拿到设备信息,最后展示到前面初始化的ListView列表中:

// 这个是官方demo的源码// 是扫描的Callback的回调,其中的onLeScan方法,蓝牙扫描成功之后会将结果会返回此方法中// 然后就可以处理BluetoothDevice拿到设备信息 最后展示到前面初始化的listview列表中private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback(){    @Override    public void onLeScan(final BluetoothDevice device, final int rssi, byte[] scanRecord)    {        // TODO Auto-generated method stub    runOnUiThread(new Runnable()        {            @Override            public void run()            {                // 讲扫描到设备的信息输出到listview的适配器                BluetoothMessage bluetoothMessage = new BluetoothMessage(device);                mleDeviceListAdapter.addDevice(bluetoothMessage);                mleDeviceListAdapter.notifyDataSetChanged();             }         });    }};

至此已经完成初始化配置、一些设备的判断逻辑和扫描操作了,如果能成功地扫描到设备并展示到界面上的话,下一步如果用户点击了列表,将进行蓝牙连接和相关的读写操作!

1.4创建BluetoothLeService服务类并初始化蓝牙连接

创建了一个BluetoothLeService服务类并继承了Service,用来完成蓝牙设备的初始化、连接、断开连接、读取特征值、写入特征值、设置特征值变化通知以及获取已连接蓝牙的所有服务等操作

1.4.1创建服务

首先,我们得进行第一步,在onCreate()方法中,执行bindService开启一个服务

这里因为是项目需要,使用了一个虚拟按钮来进行初始化的连接

//蓝牙service,负责后台的蓝牙服务private static BluetoothLeService mBluetoothLeService;Intent gattServiceIntent;@Overridepublic void onCreate(Bundle savedInstanceState){    ...                gattServiceIntent = new Intent(this, BluetoothLeService.class);    //模拟按键点击事件触发蓝牙连接    rev_tv.post(new Runnable() {        @Override        public void run() {            scan_btn.performClick();        }    });    //监听scan_btn    scan_btn.setOnClickListener(new View.OnClickListener(){        @Override        public void onClick(View v){            if (scan_flag)            {                mleDeviceListAdapter = new LeDeviceListAdapter();                //lv.setAdapter(mleDeviceListAdapter);                scanLeDevice(true);            } else {                scanLeDevice(false);                scan_btn.setText("扫描设备");            }            if (mScanning) {                                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)                    mBluetoothLeScanner.stopScan(mScanCallback);                else                    mBluetoothAdapter.stopLeScan(mLeScanCallback);                mScanning = false;            }            bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE);        }    });    ...}

开启服务成功后,便会一样进行服务回调,当服务回调已经成功连接时,便会获取一个BlueToohtLeService的实例,接着就执行蓝牙连接操作:

// 获取BluetoothLeService的实例,进行蓝牙连接操作// 以下都是Android官方的demo源码private final ServiceConnection mServiceConnection = new ServiceConnection(){    @Override    public void onServiceConnected(ComponentName componentName,       IBinder service)    {        mBluetoothLeService = ((BluetoothLeService.LocalBinder) service)            .getService();        if (!mBluetoothLeService.initialize())        {            //Log.e(TAG, "Unable to initialize Bluetooth");            finish();        }        mBluetoothLeService.connect(mDeviceAddress);    }    @Override    public void onServiceDisconnected(ComponentName componentName)    {        mBluetoothLeService = null;    }};

这个时候,需要单独创建一个BlueToothService类,因为BlueToohtLeService类既然是服务类,那它父类肯定是继承于Service

public class BluetoothLeService extends Service {...}

这里的BlueToothService类是BindService服务,用于绑定一个服务。这样当bindService(intent,conn,flags)后,就会绑定一个服务。这样做可以获得这个服务对象本身,而用StartService(intent)的方法只能启动服务。

BindService方法的一般过程:

1.4.1.1开启BindService服务

// bindService区别于startService,用于绑定服务,可以获得这个服务对象本身public class LocalBinder extends Binder {    public BluetoothLeService getService()    {        return BluetoothLeService.this;    }}// onBind()是使用bindService开启的服务才会有回调的一个方法// onBind()方法给MainActivity返回了BluetoothLeService实例// 用于方便MainActivity后续的连接和读写操作@Overridepublic IBinder onBind(Intent intent){    return mBinder;}

1.4.1.2关闭BindService,关闭蓝牙

当服务调用unbindService时,服务的生命周期将会进入onUnbind()方法,接着执行了关闭蓝牙的方法

@Overridepublic boolean onUnbind(Intent intent){    close();    return super.onUnbind(intent);}private final IBinder mBinder = new LocalBinder();

1.4.2初始化蓝牙

这个方法是BlueToohtLeService服务类创建之后在MainActivity通过拿到BlueToohtLeService实例调用的,也是官方的源码

public boolean initialize(){    // For API level 18 and above, get a reference to BluetoothAdapter    // through    // BluetoothManager.    if (mBluetoothManager == null)    {   //获取系统的蓝牙管理器        //使用 getSystemService(java.lang.String)与 BLUETOOTH_SERVICE创建一个 BluetoothManager        // 然后调用 getAdapter()以获得 BluetoothAdapter        mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);        if (mBluetoothManager == null)        {            Log.e(TAG, "Unable to initialize BluetoothManager.");            return false;        }    }    //BluetoothManager 的变量调用 getAdapter()以获得 BluetoothAdapter 进而对整体蓝牙进行管理    mBluetoothAdapter = mBluetoothManager.getAdapter();    if (mBluetoothAdapter == null)    {        Log.e(TAG, "Unable to obtain a BluetoothAdapter.");        return false;    }    return true;}

1.4.3执行connect()和connectGatt

connect()和connectGatt都是连接BLE设备的方法,但二者用法不同

connectGatt是BluetoothDevice类下的方法,功能是向BLE设备发起连接,然后得到一个BluetoothGatt类型的返回值,利用这个返回值可以进行下一步操作。

connect是BluetoothGatt类下的方法,功能是重新连接。如果BLE设备和APP已经连接过,但是因为设备超出了蓝牙的连接范围而断掉,那么当设备重新回到连接范围内时,可以通过connect()重新连接

// 连接远程蓝牙    public boolean connect(final String address)    {        // 适配器为空或者地址为空就会提示        if (mBluetoothAdapter == null || address == null)        {            Log.w(TAG,                    "BluetoothAdapter not initialized or unspecified address.");            return false;        }        // Previously connected device. Try to reconnect.        if (mBluetoothDeviceAddress != null                && address.equals(mBluetoothDeviceAddress)                && mBluetoothGatt != null)        {            Log.d(TAG,                    "Trying to use an existing mBluetoothGatt for connection.");            //mBluetoothGatt.connect()表示连接回远程设备            //在连接断开后,此方法的功能在于“重新连接到远程设备”            //如果设备曾经连接过,但目前不在范围内            //则一旦设备回到范围内,则可以通过connect重新连接。            if (mBluetoothGatt.connect())//连接蓝牙,其实就是调用BluetoothGatt的连接方法            {                mConnectionState = STATE_CONNECTING;                return true;            } else            {                return false;            }        }                //mBluetoothAdapter.getRemoteDevice        //蓝牙适配器通过调用getRemoteDevice()方法获取给定的蓝牙硬件地址的BluetoothDevice对象        //有效的蓝牙地址必须是6个字节,即使没有找到设备,也得返回有效的6个字节,当然,估计就是6个0        final BluetoothDevice device = mBluetoothAdapter                .getRemoteDevice(address);        if (device == null)        {            Log.w(TAG, "Device not found.  Unable to connect.");            return false;        }        // We want to directly connect to the device, so we are setting the        // autoConnect        // parameter to false.                //connectGatt是BluetoothDevice类下的方法        //功能是向BLE设备发起连接,然后得到一个BluetoothGatt类型的返回值,利用这个返回值可以进行下一步操作        //connectGatt方法往往是和BluetoothGatt类的connect方法一起使用        //两个方法的运行逻辑是:        //先使用connectGatt方法发起连接,连接状态的改变会回调callback对象中的onConnectionStateChange        // (需要自己定义一个BluetoothGattCallBack对象并重写onConnectionStateChange)        // 并返回一个BluetoothGatt对象,这时BluetoothGatt已经实例化,下一次连接可以调用connect重新连接。        mBluetoothGatt = device.connectGatt(this, false, mGattCallback);        Log.d(TAG, "Trying to create a new connection.");        mBluetoothDeviceAddress = address;        mConnectionState = STATE_CONNECTING;        System.out.println("device.getBondState==" + device.getBondState());        return true;    }

取消连接

public void disconnect(){    if (mBluetoothAdapter == null || mBluetoothGatt == null)    {        Log.w(TAG, "BluetoothAdapter not initialized");        return;    }    mBluetoothGatt.disconnect();}

1.4.4 BluetoothGattCallback 回调

这个回调十分重要,主要对BluetoothGatt的蓝牙连接、断开、读、写、特征值变化等的回调监听,然后我们可以将这些回调信息通过广播机制传播回给广播监听器

// BluetoothGattCallback是一个抽象类,目的是用于实现 BluetoothGatt的回调// 用于将结果传递给用户,例如连接状态等,以及任何进一步对GATT客户端的操作// 因为BluetoothGattCallback是一个抽象类,因此需要对里面的方法进行重写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;   // 连接外设成功(GATT服务端)            mConnectionState = STATE_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;// 连接外设失败(GATT服务端)            mConnectionState = STATE_DISCONNECTED;  // 设备无法连接            Log.i(TAG, "Disconnected from GATT server.");            broadcastUpdate(intentAction);        }    }        @Override    public void onServicesDiscovered(BluetoothGatt gatt, int status)    {   // GATT_SUCCESS表示GATT操作完成        if (status == BluetoothGatt.GATT_SUCCESS)//发现蓝牙服务成功        {            broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);            Log.i(TAG, "--onServicesDiscovered called--");        } else        {            Log.w(TAG, "onServicesDiscovered received: " + status);            System.out.println("onServicesDiscovered received: " + status);        }    }        @Override    public void onCharacteristicRead(BluetoothGatt gatt,         BluetoothGattCharacteristic characteristic, int status)    {        if (status == BluetoothGatt.GATT_SUCCESS)        {            Log.i(TAG, "--onCharacteristicRead called--");            //从特征值读取数据            // characteristic是特征值,而特征值是用16bit或者128bit,16bit是官方认证过的,128bit是可以自定义的            // 这里的两步操作第一步获得二进制的特征值,第二步将其变成字符串            byte[] sucString = characteristic.getValue();            String string = new String(sucString);            //将数据通过广播到Ble_Activity            //broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);        }    }        @Override    public void onCharacteristicChanged(BluetoothGatt gatt,            BluetoothGattCharacteristic characteristic)    {        System.out.println("++++++++++++++++");        broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);        // ACTION_DATA_AVAILABLE: 接受来自设备的数据,可以通过读或通知操作获得    }        @Override    public void onCharacteristicWrite(BluetoothGatt gatt,          BluetoothGattCharacteristic characteristic, int status) {        super.onCharacteristicWrite(gatt, characteristic, status);        if (status == BluetoothGatt.GATT_SUCCESS) {//发送完成            mSendState = true;            Log.d("AppRun"+getClass().getSimpleName(),"发送完成");        }    }        @Override    public void onDescriptorRead(BluetoothGatt gatt,     BluetoothGattDescriptor descriptor, int status)    {        // TODO Auto-generated method stub        // super.onDescriptorRead(gatt, descriptor, status);        Log.w(TAG, "----onDescriptorRead status: " + status);        byte[] desc = descriptor.getValue();        if (desc != null)        {            Log.w(TAG, "----onDescriptorRead value: " + new String(desc));        }    }        @Override    public void onDescriptorWrite(BluetoothGatt gatt,      BluetoothGattDescriptor descriptor, int status)    {        // TODO Auto-generated method stub        // super.onDescriptorWrite(gatt, descriptor, status);        Log.w(TAG, "--onDescriptorWrite--: " + status);    }        // Rssi是蓝牙的接受信号强度    @Override    public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status)    {        // TODO Auto-generated method stub        // super.onReadRemoteRssi(gatt, rssi, status);        Log.w(TAG, "--onReadRemoteRssi--: " + status);        broadcastUpdate(ACTION_DATA_AVAILABLE, rssi);    }    @Override    public void onReliableWriteCompleted(BluetoothGatt gatt, int status)    {        // TODO Auto-generated method stub        // super.onReliableWriteCompleted(gatt, status);        Log.w(TAG, "--onReliableWriteCompleted--: " + status);    }};

1.4.5设置特征值变化通知

为了让手机APP接收蓝牙设备发送的数据,必须要设置这个setCharacteristicNotification()方法,这个十分重要。否则,手机APP将无法接受蓝牙设备的数据

1.4.6读取特征值

开启对特征值的读

在蓝牙设备连接成功后自动读一次特征值,如果读成功,将返回BluetoothLeService类中的OnDataAvailableListener接口,并进入如下的方法

public void onCharacteristicRead(BluetoothGatt gatt,     BluetoothGattCharacteristic characteristic, int status);

1.4.7写入特征值

开启对特征值的写,也就是向蓝牙外设写入数据

这是完成手机APP向蓝牙设备写数据的操作

1.4.8获取已连接蓝牙的所有服务

public List<BluetoothGattService> getSupportedGattServices(){    if (mBluetoothGatt == null)        return null;    return mBluetoothGatt.getServices();}

返回已经连接蓝牙设备的所有服务

1.4.9读取蓝牙设备的RSSI值

该方法返回的是已连接的蓝牙设备的信号值(RSSI),而RSSI值是蓝牙的信号值,离得越远信号越小,反之亦然

// 读取RSSipublic void readRssi(){    if (mBluetoothAdapter == null || mBluetoothGatt == null)    {        Log.w(TAG, "BluetoothAdapter not initialized");        return;    }    mBluetoothGatt.readRemoteRssi();}

该方法返回的是已连接的蓝牙设备的信号值(RSSI

1.4.10发送广播

通过广播的形式将数据发出去,在MainActivity中通过设置过滤器接收对应的广播

//广播意图private void broadcastUpdate(final String action, int rssi){    final Intent intent = new Intent(action);    intent.putExtra(EXTRA_DATA, String.valueOf(rssi));    sendBroadcast(intent);}//广播意图private void broadcastUpdate(final String action){    final Intent intent = new Intent(action);    sendBroadcast(intent);}public void broadcastUpdate(final String action,final BluetoothGattCharacteristic characteristic){    final Intent intent = new Intent(action);    //从特征值获取数据    final byte[] data = characteristic.getValue();    MainActivity.revDataForCharacteristic =data;    if (data != null && data.length > 0)    {        final StringBuilder stringBuilder = new StringBuilder(data.length);        for (byte byteChar : data)        {            stringBuilder.append(String.format("%02X ", byteChar));            Log.i(TAG, "***broadcastUpdate: byteChar = " + byteChar);        }        Log.e("AppRunTime","测试一");        intent.putExtra("BLE_BYTE_DATA", data);        intent.putExtra(EXTRA_DATA, new String(data));        System.out.println("broadcastUpdate for  read data:"                           + new String(data));    }    sendBroadcast(intent);}

1.5广播监听器:

广播的目的:

扫描者只有在收到广播数据后,才能去与广播者建立连接。广播是周期性的将广播数据从广播通道上发送出去

意图过滤器:

IntentFilter翻译成中文就是“意图过滤器”,主要用来过滤隐式意图。当用户进行一项操作的时候,Android系统会根据配置的 “意图过滤器” 来寻找可以响应该操作的组件,服务。这里的意图过滤器就是让用户对服务端也就是硬件外设进行操作

1.5.1注册/取消注册广播监听

在官方Demo中,便用了广播来作为activity和service之间的数据传递;MainActivity开启了前面的服务之后,就在MainActivity中注册了这个mGattUpdateReceiver广播,以下代码是MainActivity中的

// 取消注册广播和IntentFilter@Overrideprotected void onDestroy(){    super.onDestroy();    //解除广播接收器    unregisterReceiver(mGattUpdateReceiver);    mBluetoothLeService = null;}// Activity出来时候,绑定广播接收器,监听蓝牙连接服务传过来的事件// 在官方demo中,广播接收器也叫广播监听器,用广播实现activity和service的数据传递// 在MainActivity执行了bindService,开启了蓝牙服务// 而在这里,就通过registerReceiver注册了mGattUpdateReceiver广播和IntentFilter@Overrideprotected void onResume(){    super.onResume();    //绑定广播接收器    registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter());    if (mBluetoothLeService != null)    {        //根据蓝牙地址,建立连接        final boolean result = mBluetoothLeService.connect(mDeviceAddress);    }}
private static IntentFilter makeGattUpdateIntentFilter(){    final IntentFilter intentFilter = new IntentFilter();    intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED);    intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED);    intentFilter        .addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED);    intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE);    return intentFilter;}

上述代码利用registerReceiver()和unregisterReceiver()方法完成注册和取消注册广播,下面的代码是设置广播接收和过滤器

1.5.2广播回调监听

广播回调监听,便是MainActivity接收从Service发送过来的信息,上面说到的上文有说到BluetoothService类的方法BluetoothGattCallback,就是从这里发送广播的

// 下面是对前面注册的广播的回调监听,作用是接受从Service发送回来的信息// 从BluetoothGattCallback中发送广播,这里接受信息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))//Gatt连接成功        {            mConnected = true;            //status = "connected";            //更新连接状态            //updateConnectionState(status);            System.out.println("BroadcastReceiver :" + "device connected");        } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED//Gatt连接失败                   .equals(action))        {            mConnected = false;            //status = "disconnected";            //更新连接状态            //updateConnectionState(status);            System.out.println("BroadcastReceiver :"   + "device disconnected");        } else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED//发现GATT服务器                   .equals(action))        {            // Show all the supported services and characteristics on the            // user interface.            //获取设备的所有蓝牙服务            //这里留意一下:当连接成功后,首先service那边会发现服务特征值,通过广播传输回来,然后执行下面的方法            displayGattServices(mBluetoothLeService    .getSupportedGattServices());            System.out.println("BroadcastReceiver :"   + "device SERVICES_DISCOVERED");        } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action))//有效数据        {            //处理发送过来的数据            try {                if (intent.getExtras().getString(                    BluetoothLeService.EXTRA_DATA)!=null) {                    displayData(intent.getExtras().getString(                        BluetoothLeService.EXTRA_DATA), intent);                    System.out.println("BroadcastReceiver onData:"           + intent.getStringExtra(BluetoothLeService.EXTRA_DATA));                }            }catch (Exception e){                e.printStackTrace();            }        }    }};

在接收广播的代码中,displayGattServices()是进一步的完成发现服务,而displayData()则是进一步的完成数据接收处理

1.5.3处理数据的输入和获取

private static BluetoothGattCharacteristic target_chara = null;//蓝牙service,负责后台的蓝牙服务private static BluetoothLeService mBluetoothLeService;private Handler mhandler = new Handler();// 处理数据的输入和获取private void displayGattServices(List<BluetoothGattService> gattServices){    if (gattServices == null)        return;    String uuid = null;    String unknownServiceString = "unknown_service";    String unknownCharaString = "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();        // 查表,根据该uuid获取对应的服务名称。SampleGattAttributes这个表需要自定义。        gattServiceData.add(currentServiceData);        System.out.println("Service uuid:" + uuid);        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 (final BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics)        {            charas.add(gattCharacteristic);            HashMap<String, String> currentCharaData = new HashMap<String, String>();            uuid = gattCharacteristic.getUuid().toString();            if (gattCharacteristic.getUuid().toString()                .equals(HEART_RATE_MEASUREMENT))            {                // 测试读取当前Characteristic数据,会触发mOnDataAvailable.onCharacteristicRead()                mhandler.postDelayed(new Runnable()         {             @Override             public void run()             {                 // TODO Auto-generated method stub                 mBluetoothLeService                     .readCharacteristic(gattCharacteristic);             }         }, 200);                // 接受Characteristic被写的通知,收到蓝牙模块的数据后会触发mOnDataAvailable.onCharacteristicWrite()                mBluetoothLeService.setCharacteristicNotification(                    gattCharacteristic, true);                target_chara = gattCharacteristic;                // 设置数据内容                // 往蓝牙模块写入数据                // mBluetoothLeService.writeCharacteristic(gattCharacteristic);            }            List<BluetoothGattDescriptor> descriptors = gattCharacteristic                .getDescriptors();            for (BluetoothGattDescriptor descriptor : descriptors)            {                System.out.println("---descriptor UUID:"       + descriptor.getUuid());                // 获取特征值的描述                mBluetoothLeService.getCharacteristicDescriptor(descriptor);                // mBluetoothLeService.setCharacteristicNotification(gattCharacteristic,                // true);            }            gattCharacteristicGroupData.add(currentCharaData);        }        // 按先后顺序,分层次放入特征值集合中,只有特征值        mGattCharacteristics.add(charas);        // 构件第二级扩展列表(服务下面的特征值)        gattCharacteristicData.add(gattCharacteristicGroupData);    }}

1.5.4数据的接收

下面的代码完成数据的接收,将其显示到scrollview中

private void displayData(String rev_string, Intent intent){    try {        byte[] data = intent.getByteArrayExtra("BLE_BYTE_DATA");        if(data==null)            System.out.println("data is null!!!!!!");        if (receptionHex)            rev_string = bytesToHexString(data);        else            rev_string = new String(data, 0, data.length, "GB2312");//GB2312编码    } catch (UnsupportedEncodingException e) {        // TODO Auto-generated catch block        e.printStackTrace();    }    bluedata = rev_string;    //更新UI    runOnUiThread(new Runnable()                  {                      @Override                      public void run()                      {                          //                rev_tv.setText(bluedata);                          if(bluedata.length() == RFID )                          {  rfid = bluedata;  rev_tv.setText(rfid);                          }                          else if(bluedata.length() == DISTANCE )                          {  Log.d("data","*"+bluedata+"*");  int a = bluedata.charAt(1) - '0';  int b = bluedata.charAt(4) - '0';  int c = bluedata.charAt(7) - '0';  distance = a * 100 + b * 10 + c;  if(distance>100 && distance < 500 )takephoto = false;  rev_tv.setText(new Integer(distance).toString());                          }                      }                  });}

1.5.5发送数据

下面的代码是在BluetoothLeService类中,用于给外设的蓝牙模块写入数据的方法

// 发送数据public void startSend(final BluetoothGattCharacteristic characteristic){    if (mBluetoothAdapter == null || mBluetoothGatt == null) {        Log.w(TAG, "BluetoothAdapter not initialized");        return;    }    Log.d("AppRun"+getClass().getSimpleName(),"添加完成,开始发送");    if (mList.size() != 0){        new Thread(new Runnable() {            @Override            public void run() {                for (int i = 0;i<mList.size();) {                    try {                        if (mSendState) {Thread.sleep(5);characteristic.setValue(mList.get(i));mSendState = false;mBluetoothGatt.writeCharacteristic(characteristic);++i;                        } else {Log.d("AppRun"+getClass().getSimpleName(),"等待中..");Thread.sleep(20);                        }                    }catch (Exception e){                        e.printStackTrace();                    }                }                Log.d("AppRun"+getClass().getSimpleName(),"发送完毕..");                mList.clear();            }        }).start();    }}

下面是将数据进行分包,因为每次蓝牙传输数据只能是20个字节,如果一个数据超过20个字节,必须要分成n个存放20字节的包

public int[] dataSeparate(int len){    int[] lens = new int[2];    lens[0]=len/20;    lens[1]=len%20;    return lens;}public static byte[] hexString2ByteArray(String bs) {    if (bs == null) {        return null;    }    int bsLength = bs.length();    if (bsLength % 2 != 0) {        bs = "0"+bs;        bsLength = bs.length();    }    byte[] cs = new byte[bsLength / 2];    String st;    for (int i = 0; i < bsLength; i = i + 2) {        st = bs.substring(i, i + 2);        cs[i / 2] = (byte) Integer.parseInt(st, 16);    }    return cs;}//byte数组转Stringpublic static String bytesToHexString(byte[] bArray) {    StringBuffer sb = new StringBuffer(bArray.length);    String sTemp;    for (int i = 0; i < bArray.length; i++) {        sTemp = Integer.toHexString(0xFF & bArray[i]);        if (sTemp.length() < 2)            sb.append(0);        sb.append(sTemp.toUpperCase());    }    int length = sb.length();    if (length == 1||length == 0){        return sb.toString();    }    if (length%2==1){        sb.insert(length-1," ");        length= length-1;    }    for (int i = length;i>0;i=i-2){        sb.insert(i," ");    }    return sb.toString();}

来源地址:https://blog.csdn.net/weixin_51890646/article/details/128607608

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯