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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章