Android BLE4.0 从小白到理解的过程

学习蓝牙低功耗的开发过程,要达到的效果是——利用两台Android手机,通过BLE4.0进行通信,可以发送和接收数据。

  1. 其中一台Android手机T模拟发出广播,作为BLE设备(周边设备),这个BLE设备在生产环境中就是我们用到的气体检测传感器、智能手环、体重秤、血压计等等;
  2. 另一台Android手机B,作为中央设备,搜索手机T发出的广播并连接;
  3. 手机B可以接收手机T的数据,也可以发送数据给手机T;
  4. 当然手机T也可以通过通知发送数据给手机B。

1、先通过下面两篇文章理解下,

https://blog.csdn.net/shunfa888/article/details/80140475

https://www.cnblogs.com/asam/p/8676339.html

BLE是什么?低功耗蓝牙协议栈包括什么?以及蓝牙的基础知识等。

2、然后配合Android提供的官方文档,了解蓝牙相关知识,

https://developer.android.com/guide/topics/connectivity/bluetooth

https://developer.android.com/guide/topics/connectivity/bluetooth-le

这个时候可能已经花了至少2个学时学习BLE4.0,已经有了一定的认识,但是还别急着撸代码,因为我们可能只是知道其中的冰山一角。

3、接下来需要更进一步,了解开发的具体步骤,下面两篇博客可以查阅一下,

https://blog.csdn.net/fu908323236/article/details/76208997

https://blog.csdn.net/u011371324/article/details/80568230

在学习BLE4.0基础知识的基础上,通过代码一步一步的走进去,睁眼看世界。

4、这个时候我们大概已经有60%的知识点了,就可以有选择了。其一,再专研BLE的实现代码,自己写一个模拟发出广播的App给手机T,写一个可以接收发送数据的App给手机B,实现BLE4.0的通信;其二,通过已有开源工具,实现低功耗蓝牙的通信,比如这个开源项目就可使用:https://github.com/xiaoyaoyou1212/BLE

5、我相信大多数人还是会再深研BLE,所以有必要更系统一点学习一下,

https://www.jianshu.com/u/4690d1fc40fe分为四个部分介绍:

Android BLE4.0(基本知识)、Android BLE4.0(设备搜索)、Android BLE4.0(设备连接)、Android BLE4.0(蓝牙通信)。

https://blog.csdn.net/likebamboo分为六个部分:

Bluetooth LE(低功耗蓝牙) - 从第一部分到第六部分

6、通过上面的学习和实践,基本能设计出BLE相关代码了,但是可能还跑不通,比如说扫描不到蓝牙啊?连接蓝牙后通信不成功呀?等等问题。这个时候就需要下面这个博客了,老实说,最后我的代码就很大程度上跟着它走了,并且翻看它的文章次数用双手是数不清的。

好了,不卖关子了,直奔主题。http://a1anwang.com/post-36.html这篇文章开发出一个App装在手机T上使用,模拟发送广播;http://a1anwang.com/post-47.html这篇文章开发出的App装在手机B上作为中央设备使用;当然该博客的其他文章对我帮助也很大,比如http://a1anwang.com/post-17.html等等。

7、开发完了两个App,再回过头来看看之前参阅过的文章,收获又不一样了,于是才有了这篇文章的出现。

在这里,我贴出模拟BLE设备发广播的App源码,而由于中央设备App稍微有点复杂(其实就是分了几个包,逻辑更清楚一些)就不贴出了。感兴趣的小伙伴可以去下载,可以直接运行查看效果的。

CSDN下载:

https://download.csdn.net/download/agg_bin/11045928https://download.csdn.net/download/agg_bin/11045943

GitHub开源项目下载:

https://github.com/swu-agg/BLESendhttps://github.com/swu-agg/BLEReceive

模拟BLE设备发广播的App源码如下:

1、Java文件BLEBroadcastActivity.java:

/**
 * <pre>
 *     author    : Agg
 *     blog      : https://blog.csdn.net/Agg_bin
 *     time      : 2019/03/15
 *     desc      : BLE模拟设备,周边
 *     reference :
 * </pre>
 */
public class BLEBroadcastActivity extends RxAppCompatActivity {

    private static final String TAG = BLEBroadcastActivity.class.getSimpleName();
    private static final ParcelUuid PARCEL_UUID_1 = ParcelUuid.fromString("0000ccc0-0000-1000-8000-00805f9b34fb");
    private static final ParcelUuid PARCEL_UUID_2 = ParcelUuid.fromString("0000bbb0-0000-1000-8000-00805f9b34fb");
    private static final UUID SERVICE_UUID_1 = UUID.fromString("0000ccc0-0000-1000-8000-00805f9b34fb");
    private static final UUID SERVICE_UUID_2 = UUID.fromString("0000bbb0-0000-1000-8000-00805f9b34fb");
    private static final UUID CHARACTERISTIC_UUID_1 = UUID.fromString("0000ccc1-0000-1000-8000-00805f9b34fb");
    private static final UUID CHARACTERISTIC_UUID_2 = UUID.fromString("0000ccc2-0000-1000-8000-00805f9b34fb");
    private static final UUID CHARACTERISTIC_UUID_3 = UUID.fromString("0000bbb1-0000-1000-8000-00805f9b34fb");
    private static final UUID DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
    private static final byte[] BROADCAST_DATA = {0x12, 0x34, 0x56, 0x78};
    private static final int MANUFACTURER_ID = 0xACAC;

    private BluetoothManager bluetoothManager;
    private BluetoothGattServer bluetoothGattServer;
    private BluetoothLeAdvertiser bluetoothLeAdvertiser;
    private List<BluetoothDevice> bluetoothDeviceList = new ArrayList<>(); // 建立通知关系的device队列,当发送通知时,通知所有设备。

    @BindView(R.id.et_info)
    EditText etInfo;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_blebroadcast);
        ButterKnife.bind(this);
        etInfo.setImeOptions(EditorInfo.IME_ACTION_SEND);
        etInfo.setOnEditorActionListener((v, actionId, event) -> {
            if (actionId == EditorInfo.IME_ACTION_SEND || (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) {
                sendInfo(etInfo.getText().toString().trim());
                hideSoftInput();
                etInfo.setText("");
                return true;
            }
            return false;
        });
        askPermission();
    }

    @SuppressLint("CheckResult")
    private void askPermission() {
        if (Build.VERSION.SDK_INT >= 23) {
            new RxPermissions(this).request(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION})
                    .compose(bindUntilEvent(ActivityEvent.DESTROY))
                    .take(1)
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(aBoolean -> {
                        if (aBoolean) {
                            isSupportBluetooth4();
                        } else {
                            Toast.makeText(BLEBroadcastActivity.this, "未授予模糊定位权限", Toast.LENGTH_SHORT).show();
                            finish();
                        }
                    });
        } else {
            isSupportBluetooth4();
        }
    }

    private void isSupportBluetooth4() {
        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            Toast.makeText(BLEBroadcastActivity.this, "蓝牙不支持BLE", Toast.LENGTH_SHORT).show();
            finish();
        } else if (!isOpenBluetooth()) {
            Toast.makeText(BLEBroadcastActivity.this, "此硬件平台不支持蓝牙", Toast.LENGTH_SHORT).show();
            finish();
        }
    }

    private boolean isOpenBluetooth() {
        bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if (bluetoothAdapter == null) {
            return false;
        }
        boolean enable = bluetoothAdapter.enable();// 自动打开蓝牙
        if (!enable) {
            Toast.makeText(this, "请打开蓝牙", Toast.LENGTH_SHORT).show();
            finish();
        } else {
            etInfo.postDelayed(this::setService, 1500); // 等待蓝牙开启后再使用(预计1.5秒以上就可以)
        }
        bluetoothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser();
        return true;
    }

    private void setService() {
        bluetoothGattServer = bluetoothManager.openGattServer(this, bluetoothGattServerCallback);
        // 可写ccc1
        BluetoothGattCharacteristic bluetoothGattCharacteristic1 = new BluetoothGattCharacteristic(CHARACTERISTIC_UUID_1, BluetoothGattCharacteristic.PROPERTY_WRITE, BluetoothGattCharacteristic.PERMISSION_WRITE);
        // 可读ccc2
        BluetoothGattCharacteristic bluetoothGattCharacteristic2 = new BluetoothGattCharacteristic(CHARACTERISTIC_UUID_2, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ);
        // service1ccc0
        BluetoothGattService service1 = new BluetoothGattService(SERVICE_UUID_1, BluetoothGattService.SERVICE_TYPE_PRIMARY);
        service1.addCharacteristic(bluetoothGattCharacteristic1);
        service1.addCharacteristic(bluetoothGattCharacteristic2);
        bluetoothGattServer.addService(service1);
        // 可读可写可通知bbb1
        BluetoothGattCharacteristic characteristic3 = new BluetoothGattCharacteristic(CHARACTERISTIC_UUID_3,
                BluetoothGattCharacteristic.PROPERTY_NOTIFY | BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_READ,
                BluetoothGattCharacteristic.PERMISSION_WRITE | BluetoothGattCharacteristic.PERMISSION_READ);
        characteristic3.addDescriptor(new BluetoothGattDescriptor(DESCRIPTOR_UUID, BluetoothGattDescriptor.PERMISSION_WRITE));
        // service2bbb0
        final BluetoothGattService service2 = new BluetoothGattService(SERVICE_UUID_2, BluetoothGattService.SERVICE_TYPE_PRIMARY);
        service2.addCharacteristic(characteristic3);
        new Thread(() -> {
            try {
                Thread.sleep(1000);
                bluetoothGattServer.addService(service2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }

    private final BluetoothGattServerCallback bluetoothGattServerCallback = new BluetoothGattServerCallback() {

        @Override
        public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
            super.onConnectionStateChange(device, status, newState);
            // 这个device是中央设备, mac地址会 因为 中央(手机)蓝牙重启而变化
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                Log.i(TAG, "连接成功");
                Log.i(TAG, "onConnectionStateChange: " + status + " newState:" + newState + " deviceName:" + device.getName() + " mac:" + device.getAddress());
            }
        }

        @Override
        public void onServiceAdded(int status, BluetoothGattService service) {
            super.onServiceAdded(status, service);
            Log.i(TAG, " onServiceAdded status:" + status + " service:" + service.getUuid().toString());
        }

        @Override
        public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
            Log.i(TAG, " onCharacteristicReadRequest requestId:" + requestId + " offset:" + offset + " characteristic:" + characteristic.getUuid().toString());
            bluetoothGattServer.sendResponse(device, requestId, 0, offset, "agg coming".getBytes());
        }

        @Override
        public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
            super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
            Log.e(TAG, " onCharacteristicWriteRequest requestId:" + requestId + " preparedWrite:" + preparedWrite + " responseNeeded:" + responseNeeded + " offset:" + offset + " value:" + new String(value) + " characteristic:" + characteristic.getUuid().toString());
            runOnUiThread(() -> Toast.makeText(BLEBroadcastActivity.this, "收到请求:" + new String(value), Toast.LENGTH_SHORT).show());
            bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
        }

        @Override
        public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
            super.onDescriptorReadRequest(device, requestId, offset, descriptor);
            Log.i(TAG, " onCharacteristicReadRequest requestId:" + requestId + " offset:" + offset + " descriptor:" + descriptor.getUuid().toString());
        }

        int i = 0;

        @Override
        public void onDescriptorWriteRequest(final BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
            super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
            Log.i(TAG, " onDescriptorWriteRequest requestId:" + requestId + " preparedWrite:" + preparedWrite + " responseNeeded:" + responseNeeded + " offset:" + offset + " value:" + toHexString(value) + " characteristic:" + descriptor.getUuid().toString());
            bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);

            // 添加到通知列表队列:添加前先移除之前添加的!
            for (BluetoothDevice bluetoothDevice : bluetoothDeviceList) {
                if (bluetoothDevice.getAddress().equals(device.getAddress())) {
                    bluetoothDeviceList.remove(bluetoothDevice);
                    break;
                }
            }
            bluetoothDeviceList.add(device);

            // 循环通知3个数据
            new Thread(() -> {
                while (i < 3) {
                    try {
                        Thread.sleep(1000);
                        notifyData(device, ("通知数据" + i++).getBytes(), false);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }

        @Override
        public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
            super.onExecuteWrite(device, requestId, execute);
            Log.i(TAG, " onExecuteWrite requestId:" + requestId + " execute:" + execute);
        }

        @Override
        public void onNotificationSent(BluetoothDevice device, int status) {
            super.onNotificationSent(device, status);
            Log.i(TAG, " onNotificationSent status:" + status);
        }

    };

    private AdvertiseCallback advertiseCallback = new AdvertiseCallback() {
        @Override
        public void onStartSuccess(AdvertiseSettings settingsInEffect) {
            super.onStartSuccess(settingsInEffect);
            Toast.makeText(BLEBroadcastActivity.this, "开启BLE广播成功", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onStartFailure(int errorCode) {
            super.onStartFailure(errorCode);
            Toast.makeText(BLEBroadcastActivity.this, "开启BLE广播失败,errorCode:" + errorCode, Toast.LENGTH_SHORT).show();
        }
    };

    private void sendInfo(String data) {
        Log.e(TAG, "sendInfo: " + data + ",bluetoothDeviceList.size():" + bluetoothDeviceList.size());
        try {
            for (BluetoothDevice bluetoothDevice : bluetoothDeviceList) {
                boolean notifyData = notifyData(bluetoothDevice, data.getBytes(), false);
                Toast.makeText(this, "通知数据\"" + data + "\"给" + bluetoothDevice.getAddress() + "--------" + notifyData, Toast.LENGTH_LONG).show();
            }
        } catch (Exception e) {
            Toast.makeText(this, "请打开广播连接通信", Toast.LENGTH_SHORT).show();
        }
    }

    private boolean notifyData(final BluetoothDevice device, byte[] value, final boolean confirm) {
        BluetoothGattCharacteristic characteristic = null;
        for (BluetoothGattService service : bluetoothGattServer.getServices()) {
            for (BluetoothGattCharacteristic mCharacteristic : service.getCharacteristics()) {
                if (mCharacteristic.getUuid().equals(CHARACTERISTIC_UUID_3)) {
                    characteristic = mCharacteristic;
                    break;
                }
            }
        }
        if (characteristic != null) {
            characteristic.setValue(value);
            return bluetoothGattServer.notifyCharacteristicChanged(device, characteristic, confirm);
        }
        return false;
    }

    private void hideSoftInput() {
        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        assert imm != null;
        imm.hideSoftInputFromWindow(etInfo.getWindowToken(), 0); // 强制隐藏键盘
    }

    private AdvertiseSettings createAdvertiseSettings(boolean connectable, int timeoutMillis) {
        // 设置广播的模式,低功耗,平衡和低延迟三种模式:对应  AdvertiseSettings.ADVERTISE_MODE_LOW_POWER  ,ADVERTISE_MODE_BALANCED ,ADVERTISE_MODE_LOW_LATENCY
        // 从左右到右,广播的间隔会越来越短
        return new AdvertiseSettings.Builder().setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
                // 设置是否可以连接。广播分为可连接广播和不可连接广播,一般不可连接广播应用在iBeacon设备上,这样APP无法连接上iBeacon设备
                .setConnectable(connectable)
                // 设置广播的信号强度,从左到右分别表示强度越来越强.。
                // 常量有AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW,ADVERTISE_TX_POWER_LOW,ADVERTISE_TX_POWER_MEDIUM,ADVERTISE_TX_POWER_HIGH
                // 举例:当设置为ADVERTISE_TX_POWER_ULTRA_LOW时,手机1和手机2放在一起,手机2扫描到的rssi信号强度为-56左右;
                // 当设置为ADVERTISE_TX_POWER_HIGH  时, 扫描到的信号强度为-33左右,信号强度越大,表示手机和设备靠的越近。
                .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
                // 设置广播的最长时间,最大值为常量AdvertiseSettings.LIMITED_ADVERTISING_MAX_MILLIS = 180 * 1000;  180秒
                // 设为0时,代表无时间限制会一直广播
                .setTimeout(timeoutMillis)
                .build();
    }

    private AdvertiseData createAdvertiseData(byte[] broadcastData) {
        return new AdvertiseData.Builder()
                .addServiceUuid(PARCEL_UUID_1)
                .addServiceUuid(PARCEL_UUID_2)
                .addServiceData(PARCEL_UUID_1, new byte[]{0x33, 0x33, 0x33, 0x33})
                .addManufacturerData(MANUFACTURER_ID, broadcastData)
                .build();
    }

    public static String toHexString(byte[] byteArray) {
        if (byteArray == null || byteArray.length < 1) return "";
        final StringBuilder hexString = new StringBuilder();
        for (byte aByteArray : byteArray) {
            if ((aByteArray & 0xff) < 0x10)//0~F前面不零
                hexString.append("0");
            hexString.append(Integer.toHexString(0xFF & aByteArray));
        }
        return hexString.toString().toLowerCase();
    }

    @OnClick(R.id.bt_open_broadcast)
    public void openBroadcast() {
        if (bluetoothLeAdvertiser != null) {
            bluetoothLeAdvertiser.stopAdvertising(advertiseCallback);
            bluetoothLeAdvertiser.startAdvertising(createAdvertiseSettings(true, 0), createAdvertiseData(BROADCAST_DATA), advertiseCallback);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (bluetoothLeAdvertiser != null) {
            bluetoothLeAdvertiser.stopAdvertising(advertiseCallback);
            bluetoothLeAdvertiser = null;
        }
    }

}

2、布局文件activity_blebroadcast.xml:

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <EditText
        android:id="@+id/et_info"
        android:layout_width="match_parent"
        android:layout_height="29dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="10dp"
        android:background="@drawable/et_round_bg_search"
        android:ems="10"
        android:gravity="center_vertical"
        android:hint="@string/et_info"
        android:inputType="text"
        android:paddingEnd="10dp"
        android:paddingStart="12dp"
        android:textColor="@color/colorPrimary"
        android:textColorHint="#999999"
        android:textSize="12sp" />

    <Button
        android:id="@+id/bt_open_broadcast"
        android:layout_width="100dp"
        android:layout_height="60dp"
        android:layout_gravity="center"
        android:layout_marginBottom="20dp"
        android:background="@drawable/et_round_bg_search"
        android:contentDescription="@null"
        android:text="@string/open_broadcast"
        android:textColor="@color/colorPrimary"
        android:textSize="20sp" />

</merge>

3、AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.hy.ble.send">

    <uses-permission android:name="android.permission.BLUETOOTH" /> <!--使用蓝牙所需要的权限-->
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <!--使用扫描和设置蓝牙的权限(申明这一个权限必须申明上面一个权限)-->
    <!--在Android6.0及以上,还需要打开模糊定位的权限。如果应用没有位置权限,蓝牙扫描功能不能使用(其它蓝牙操作例如连接蓝牙设备和写入数据不受影响)-->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

    <uses-feature android:name="android.hardware.location.gps" /><!--在Android5.0之前,是默认申请GPS硬件功能的。而在Android5.0之后,需要在manifest 中申明GPS硬件模块功能的使用-->
    <uses-feature
        android:name="android.hardware.bluetooth_le"
        android:required="true" /><!--App只支持 BLE-->

    <application
        android:allowBackup="false"
        android:icon="@mipmap/expression_normal"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/expression_normal"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        tools:ignore="GoogleAppIndexingWarning">
        <activity android:name=".BLEBroadcastActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

4、app的build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.hy.ble.send"
        minSdkVersion 21
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    // Lambda expressions are not supported at language level '1.7'
    // Java 的版本配置
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

    // rxpermissions
    implementation 'com.github.tbruyelle:rxpermissions:0.10.2'
    // recyclerview
    implementation 'com.android.support:recyclerview-v7:27.1.1'
    // butterknife
    implementation 'com.jakewharton:butterknife:8.4.0'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
    // rxlifecycle
    implementation 'com.trello.rxlifecycle2:rxlifecycle-components:2.1.0'
}

5、其他的values文件夹或者drawable就不列出来了,需要的可以去下载

https://download.csdn.net/download/agg_bin/11045928

https://download.csdn.net/download/agg_bin/11045943

或者

https://github.com/swu-agg/BLESend

https://github.com/swu-agg/BLEReceive

 

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值