├── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── billin
│ │ └── www
│ │ └── rxble
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── billin
│ │ │ └── www
│ │ │ └── rxble
│ │ │ ├── MainActivity.java
│ │ │ └── ble
│ │ │ ├── BluetoothClient.java
│ │ │ ├── BluetoothClientBLEV2Adapter.java
│ │ │ ├── bean
│ │ │ └── BLEDevice.java
│ │ │ ├── callback
│ │ │ ├── BaseResultCallback.java
│ │ │ ├── NONE.java
│ │ │ └── SuccessResultCallback.java
│ │ │ ├── exception
│ │ │ ├── BluetoothCannotAccessLocationException.java
│ │ │ ├── BluetoothConnectExceptionWithMac.java
│ │ │ ├── BluetoothException.java
│ │ │ ├── BluetoothExceptionWithMac.java
│ │ │ ├── BluetoothNotOpenException.java
│ │ │ ├── BluetoothNotifyExceptionWithMac.java
│ │ │ ├── BluetoothReadRssiExceptionWithMac.java
│ │ │ ├── BluetoothSearchConflictException.java
│ │ │ └── BluetoothWriteExceptionWithMac.java
│ │ │ └── originV2
│ │ │ ├── BluetoothLeConnector.java
│ │ │ ├── BluetoothLeInitialization.java
│ │ │ └── BluetoothLeSearcher.java
│ └── res
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── billin
│ └── www
│ └── rxble
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 |
15 | # Gradle files
16 | .gradle/
17 | build/
18 | /*/build/
19 |
20 | # Local configuration file (sdk content, etc)
21 | local.properties
22 |
23 | # Proguard folder generated by Eclipse
24 | proguard/
25 |
26 | # Log Files
27 | *.log
28 | /.idea
29 | *.iml
30 | .gradle
31 | /local.properties
32 | /.idea/workspace.xml
33 | /.idea/libraries
34 | .DS_Store
35 | /build
36 | /captures
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # RxBLE
3 | 这是一个使用 RxJava 封装的低功耗蓝牙类库。封装了低功耗蓝牙的连接,写入数据,读取数据和监听硬件特定通道数据改变的功能。关于低功耗蓝牙的入门介绍可以参阅 [我的简书博客](http://www.jianshu.com/p/3a372af38103)
4 |
5 | # 使用方法
6 | clone 下来,复制 ble 包到本地项目即可使用(确保当前开发的项目有依赖 RxJava2)。可根据自己的需求进行二次开发。
7 |
8 | # 初始化蓝牙
9 | ```java
10 | BluetoothClient mClient;
11 |
12 | mClient = new BluetoothClientBLEV2Adapter(
13 | BluetoothLeInitialization.getInstance(this));
14 | mClient.openBluetooth();
15 | ```
16 |
17 | # 扫描设备
18 |
19 | ```java
20 | // 第一参数指定扫描时间,第二个参数指定是否中断当前正在进行的扫描操作
21 | mClient.search(3000, false)
22 | .observeOn(AndroidSchedulers.mainThread())
23 | .subscribe(new Observer() {
24 | @Override
25 | public void onSubscribe(Disposable d) {
26 | mTextView.setText("start\n");
27 | }
28 |
29 | @Override
30 | public void onNext(BLEDevice value) {
31 | Log.d(TAG, "device " + value);
32 | mTextView.setText(mTextView.getText() + "\n\n" + value);
33 | }
34 |
35 | @Override
36 | public void onError(Throwable e) {
37 | Log.e(TAG, "onError: ", e);
38 | mTextView.setText(mTextView.getText() + "\n\n" + "complete");
39 | }
40 |
41 | @Override
42 | public void onComplete() {
43 | Log.d(TAG, "onComplete: search");
44 | mTextView.setText(mTextView.getText() + "\n\n" + "complete");
45 | }
46 | });
47 | }
48 | ```
49 |
50 | # 连接并写入数据示例
51 |
52 | ```java
53 | private void connectAndWrite() {
54 | mClient.connect(MAC[1])
55 | .flatMap(new Function>() {
56 | @Override
57 | public ObservableSource apply(String s) throws Exception {
58 | Log.d(TAG, "connect test: on write");
59 | return mClient.write(MAC[1], UUID_SERVICE_CHANNEL,
60 | UUID_CHARACTERISTIC_CHANNEL, "01234567876543210#".getBytes());
61 | }
62 | })
63 | .subscribe(new Observer() {
64 | @Override
65 | public void onSubscribe(Disposable d) {
66 | Log.d(TAG, "connect test onSubscribe: ");
67 | }
68 |
69 | @Override
70 | public void onNext(String value) {
71 | Log.d(TAG, "connect test onNext: ");
72 | }
73 |
74 | @Override
75 | public void onError(Throwable e) {
76 | Log.e(TAG, "connect test onError: ", e);
77 | }
78 |
79 | @Override
80 | public void onComplete() {
81 | Log.d(TAG, "connect test onComplete: ");
82 | }
83 | });
84 | }
85 | ```
86 |
87 | # 连接并设置蓝牙特定通道数据的监听
88 |
89 | ```java
90 | mClient.connect(MAC[1])
91 | .flatMap(new Function>() {
92 | @Override
93 | public ObservableSource apply(String s) throws Exception {
94 | return mClient.registerNotify(MAC[1], UUID_SERVICE_CHANNEL,
95 | UUID_CHARACTERISTIC_CHANNEL, new BaseResultCallback() {
96 | @Override
97 | public void onSuccess(byte[] data) {
98 | Log.d(TAG, "I have receive a new message: "
99 | + Arrays.toString(data));
100 | }
101 |
102 | @Override
103 | public void onFail(String msg) {
104 | Log.d(TAG, "oop! setting register is failed!");
105 | }
106 | });
107 | }
108 | })
109 | ```
110 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion "25.0.2"
6 | defaultConfig {
7 | applicationId "com.billin.www.rxble"
8 | minSdkVersion 19
9 | targetSdkVersion 25
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
25 | exclude group: 'com.android.support', module: 'support-annotations'
26 | })
27 | compile 'com.android.support:appcompat-v7:25.3.1'
28 | compile 'com.android.support.constraint:constraint-layout:1.0.2'
29 | testCompile 'junit:junit:4.12'
30 |
31 | compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
32 | }
33 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in E:\ProgramFile\Andoid\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/billin/www/rxble/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.billin.www.rxble;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.billin.www.rxble", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/billin/www/rxble/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.billin.www.rxble;
2 |
3 | import android.os.Bundle;
4 | import android.support.annotation.Nullable;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.util.Log;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.Button;
10 | import android.widget.LinearLayout;
11 | import android.widget.TextView;
12 |
13 | import com.billin.www.rxble.ble.BluetoothClient;
14 | import com.billin.www.rxble.ble.BluetoothClientBLEV2Adapter;
15 | import com.billin.www.rxble.ble.bean.BLEDevice;
16 | import com.billin.www.rxble.ble.originV2.BluetoothLeInitialization;
17 |
18 | import java.util.UUID;
19 |
20 | import io.reactivex.ObservableSource;
21 | import io.reactivex.Observer;
22 | import io.reactivex.android.schedulers.AndroidSchedulers;
23 | import io.reactivex.disposables.Disposable;
24 | import io.reactivex.functions.Function;
25 |
26 | public class MainActivity extends AppCompatActivity {
27 |
28 | BluetoothClient mClient;
29 |
30 | private static final String TAG = "MainActivity";
31 |
32 | private TextView mTextView;
33 |
34 | private static final String[] MAC = {
35 | "98:5D:AD:23:21:DA",
36 | "98:5D:AD:23:21:DD",
37 | "98:5D:AD:23:21:AB",
38 | "C8:FD:19:43:68:E2",
39 | "98:5D:AD:23:23:80",
40 | };
41 |
42 | private static final UUID UUID_SERVICE_CHANNEL
43 | = UUID.fromString("0000ffe0-0000-1000-8000-00805f9b34fb");
44 |
45 | private static final UUID UUID_CHARACTERISTIC_CHANNEL
46 | = UUID.fromString("0000ffe1-0000-1000-8000-00805f9b34fb");
47 |
48 | @Override
49 | public void onCreate(@Nullable Bundle savedInstanceState) {
50 | super.onCreate(savedInstanceState);
51 |
52 | mClient = new BluetoothClientBLEV2Adapter(BluetoothLeInitialization.getInstance(this));
53 | mClient.openBluetooth();
54 |
55 | Button writeButton = new Button(this);
56 | writeButton.setText("write data");
57 | writeButton.setOnClickListener(new View.OnClickListener() {
58 | @Override
59 | public void onClick(View v) {
60 | connectAndWrite();
61 | }
62 | });
63 |
64 | Button scanButton = new Button(this);
65 | scanButton.setText("scan");
66 | scanButton.setOnClickListener(new View.OnClickListener() {
67 | @Override
68 | public void onClick(View v) {
69 | search();
70 | }
71 | });
72 |
73 | mTextView = new TextView(this);
74 | mTextView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
75 | ViewGroup.LayoutParams.MATCH_PARENT));
76 |
77 | LinearLayout layout = new LinearLayout(this);
78 | layout.setOrientation(LinearLayout.VERTICAL);
79 | layout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
80 | ViewGroup.LayoutParams.MATCH_PARENT));
81 |
82 | layout.addView(writeButton);
83 | layout.addView(scanButton);
84 | layout.addView(mTextView);
85 | setContentView(layout);
86 | }
87 |
88 | private void connectAndWrite() {
89 | mClient.connect(MAC[1])
90 | .flatMap(new Function>() {
91 | @Override
92 | public ObservableSource apply(String s) throws Exception {
93 | Log.d(TAG, "connect test: on write");
94 | return mClient.write(MAC[1], UUID_SERVICE_CHANNEL,
95 | UUID_CHARACTERISTIC_CHANNEL,
96 | "01234567876543210#".getBytes());
97 | }
98 | })
99 | .subscribe(new Observer() {
100 | @Override
101 | public void onSubscribe(Disposable d) {
102 | Log.d(TAG, "connect test onSubscribe: ");
103 | }
104 |
105 | @Override
106 | public void onNext(String value) {
107 | Log.d(TAG, "connect test onNext: ");
108 | }
109 |
110 | @Override
111 | public void onError(Throwable e) {
112 | Log.e(TAG, "connect test onError: ", e);
113 | }
114 |
115 | @Override
116 | public void onComplete() {
117 | Log.d(TAG, "connect test onComplete: ");
118 | }
119 | });
120 | }
121 |
122 | void search() {
123 | // 第一参数指定扫描时间,第二个参数指定是否中断当前正在进行的扫描操作
124 | mClient.search(3000, false)
125 | .observeOn(AndroidSchedulers.mainThread())
126 | .subscribe(new Observer() {
127 | @Override
128 | public void onSubscribe(Disposable d) {
129 | mTextView.setText("start\n");
130 | }
131 |
132 | @Override
133 | public void onNext(BLEDevice value) {
134 | Log.d(TAG, "device " + value);
135 | mTextView.setText(mTextView.getText() + "\n\n" + value);
136 | }
137 |
138 | @Override
139 | public void onError(Throwable e) {
140 | Log.e(TAG, "onError: ", e);
141 | mTextView.setText(mTextView.getText() + "\n\n" + "complete");
142 | }
143 |
144 | @Override
145 | public void onComplete() {
146 | Log.d(TAG, "onComplete: search");
147 | mTextView.setText(mTextView.getText() + "\n\n" + "complete");
148 | }
149 | });
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/app/src/main/java/com/billin/www/rxble/ble/BluetoothClient.java:
--------------------------------------------------------------------------------
1 | package com.billin.www.rxble.ble;
2 |
3 |
4 | import com.billin.www.rxble.ble.bean.BLEDevice;
5 | import com.billin.www.rxble.ble.callback.BaseResultCallback;
6 | import com.billin.www.rxble.ble.exception.BluetoothSearchConflictException;
7 |
8 | import java.util.UUID;
9 |
10 | import io.reactivex.Observable;
11 |
12 |
13 | /**
14 | * 蓝牙控制类. 使用这一个类连接蓝牙设备的时候,最好在连接之前扫描一下附件的设备,
15 | * 如果能够扫描得到才进行连接,降低连接蓝牙的出错率。
16 | *
17 | * Created by Billin on 2017/4/14.
18 | */
19 | public interface BluetoothClient {
20 |
21 | /**
22 | * 打开蓝牙扫描操作. 如果此时正在扫描将会抛出正在扫描 {@link BluetoothSearchConflictException} 错误。
23 | * 如果想强制中断当前扫描操作,set cancel value to true.
24 | *
25 | * @param millis 扫描时间
26 | * @param cancel 如果正在进行扫描操作,设置是否中断当前扫描。true 中断当前扫描操作,
27 | * false 如果当前正在进行扫描操作则会抛出 {@link BluetoothSearchConflictException} 错误
28 | * @return 扫描结果的列表(无重复设备)
29 | */
30 | Observable search(int millis, boolean cancel);
31 |
32 | void stopSearch();
33 |
34 | /**
35 | * 连接一台蓝牙设备. 连接的蓝牙设备有最大限制,
36 | * 如果超出这一个数量,即使连接上了蓝牙设备也扫描不到该设备的服务通道
37 | *
38 | * @param mac 需要连接蓝牙设备的地址
39 | * @return 成功,返回连接设备的地址
40 | */
41 | Observable connect(String mac);
42 |
43 | /**
44 | * 断开蓝牙连接, 释放蓝牙连接占用的蓝牙服务
45 | *
46 | * @param mac 需要断开连接的 mac 地址
47 | */
48 | void disconnect(String mac);
49 |
50 | /**
51 | * 向一个蓝牙设备写入值
52 | *
53 | * @param mac 设备 mac 地址
54 | * @param service 设备服务地址
55 | * @param characteristic 设备 characteristic 地址
56 | * @param values 需要写入的值
57 | * @return 写入成功返回
58 | */
59 | Observable write(String mac, UUID service, UUID characteristic, byte[] values);
60 |
61 | /**
62 | * 向蓝牙设备注册一个通道值改变的监听器,
63 | * 每一个设备的每一个通道只允许同时存在一个监听器。
64 | *
65 | * @param mac 需要监听的 mac 地址
66 | * @param service 需要监听的设备的服务地址
67 | * @param characteristic 需要监听设备的 characteristic
68 | * @param callback 需要注册的监听器
69 | * @return 成功或失败返回
70 | */
71 | Observable registerNotify(String mac, UUID service, UUID characteristic,
72 | BaseResultCallback callback);
73 |
74 | /**
75 | * 解除在对应设备对应通道注册了的监听器
76 | *
77 | * @param mac 需要监听的 mac 地址
78 | * @param service 需要监听的设备的服务地址
79 | * @param characteristic 需要监听设备的 characteristic
80 | */
81 | Observable unRegisterNotify(String mac, UUID service, UUID characteristic);
82 |
83 | /**
84 | * 清空对应 MAC 地址的蓝牙设备缓存
85 | *
86 | * @param mac 蓝牙设备硬件地址
87 | */
88 | void clean(String mac);
89 |
90 | /**
91 | * 清空所有缓存的蓝牙设备
92 | */
93 | void cleanAll();
94 |
95 | /**
96 | * 启动蓝牙
97 | */
98 | void openBluetooth();
99 |
100 | /**
101 | * 关闭蓝牙
102 | */
103 | void closeBluetooth();
104 | }
105 |
--------------------------------------------------------------------------------
/app/src/main/java/com/billin/www/rxble/ble/BluetoothClientBLEV2Adapter.java:
--------------------------------------------------------------------------------
1 | package com.billin.www.rxble.ble;
2 |
3 | import android.bluetooth.BluetoothDevice;
4 | import android.bluetooth.BluetoothGatt;
5 | import android.os.HandlerThread;
6 | import android.util.Log;
7 |
8 | import com.billin.www.rxble.ble.bean.BLEDevice;
9 | import com.billin.www.rxble.ble.callback.BaseResultCallback;
10 | import com.billin.www.rxble.ble.exception.BluetoothNotOpenException;
11 | import com.billin.www.rxble.ble.exception.BluetoothSearchConflictException;
12 | import com.billin.www.rxble.ble.exception.BluetoothWriteExceptionWithMac;
13 | import com.billin.www.rxble.ble.originV2.BluetoothLeConnector;
14 | import com.billin.www.rxble.ble.originV2.BluetoothLeInitialization;
15 | import com.billin.www.rxble.ble.originV2.BluetoothLeSearcher;
16 |
17 | import java.util.HashSet;
18 | import java.util.Set;
19 | import java.util.UUID;
20 |
21 | import io.reactivex.Observable;
22 | import io.reactivex.ObservableEmitter;
23 | import io.reactivex.ObservableOnSubscribe;
24 |
25 | public class BluetoothClientBLEV2Adapter implements BluetoothClient {
26 |
27 | private static final String TAG = "BluetoothClient";
28 |
29 | private BluetoothLeInitialization mClient;
30 |
31 | public BluetoothClientBLEV2Adapter(BluetoothLeInitialization client) {
32 | mClient = client;
33 |
34 | HandlerThread workThread = new HandlerThread("bluetooth worker");
35 | workThread.start();
36 | }
37 |
38 | @Override
39 | public Observable search(final int millis, final boolean cancel) {
40 | return Observable.create(new ObservableOnSubscribe() {
41 | @Override
42 | public void subscribe(final ObservableEmitter e) throws Exception {
43 |
44 | BluetoothLeSearcher searcher = mClient.getBluetoothSearcher();
45 |
46 | if (searcher.isScanning() && !cancel) {
47 | e.onError(new BluetoothSearchConflictException("is searching now"));
48 | return;
49 | }
50 |
51 | if (searcher.isScanning()) {
52 | stopSearch();
53 | }
54 |
55 | mClient.getBluetoothSearcher()
56 | .scanLeDevice(millis, new BluetoothLeSearcher.OnScanCallback() {
57 | private Set devices = new HashSet<>();
58 |
59 | @Override
60 | public void onLeScan(BluetoothDevice device,
61 | int rssi,
62 | byte[] scanRecord) {
63 |
64 | BLEDevice bleDevice = new BLEDevice();
65 | bleDevice.setDeviceName(device.getName());
66 | bleDevice.setMac(device.getAddress());
67 | bleDevice.setRssi(rssi);
68 |
69 | if (devices.contains(bleDevice)) {
70 | return;
71 | }
72 |
73 | devices.add(bleDevice);
74 | }
75 |
76 | @Override
77 | public void onComplete() {
78 | for (BLEDevice device : devices) {
79 | e.onNext(device);
80 | }
81 | e.onComplete();
82 | }
83 |
84 | @Override
85 | public void onError(String msg) {
86 | e.onError(new BluetoothNotOpenException(msg));
87 | }
88 | });
89 | }
90 | });
91 | }
92 |
93 | @Override
94 | public void stopSearch() {
95 | mClient.getBluetoothSearcher().stopScanLeDevice();
96 | }
97 |
98 | @Override
99 | public Observable connect(final String mac) {
100 | return Observable.create(new ObservableOnSubscribe() {
101 | @Override
102 | public void subscribe(final ObservableEmitter e) throws Exception {
103 | BluetoothLeConnector connector = mClient.getBluetoothLeConnector(mac);
104 |
105 | connector.setOnDataAvailableListener(new BluetoothLeConnector.OnDataAvailableListener() {
106 | @Override
107 | public void onCharacteristicRead(byte[] values, int status) {
108 |
109 | }
110 |
111 | @Override
112 | public void onCharacteristicChange(UUID characteristic, byte[] values) {
113 |
114 | }
115 |
116 | @Override
117 | public void onCharacteristicWrite(UUID characteristic, int status) {
118 |
119 | }
120 |
121 | @Override
122 | public void onDescriptorWrite(UUID descriptor, int status) {
123 |
124 | }
125 |
126 | @Override
127 | public void onError(String msg) {
128 |
129 | }
130 | });
131 |
132 | connector.connect(new BluetoothLeConnector.OnConnectListener() {
133 | @Override
134 | public void onConnect() {
135 |
136 | }
137 |
138 | @Override
139 | public void onDisconnect() {
140 |
141 | }
142 |
143 | @Override
144 | public void onServiceDiscover() {
145 | e.onNext(mac);
146 | e.onComplete();
147 | }
148 |
149 | @Override
150 | public void onError(String msg) {
151 | e.onError(new Exception(msg));
152 | }
153 | });
154 | }
155 | });
156 | }
157 |
158 | @Override
159 | public void disconnect(String mac) {
160 | mClient.getBluetoothLeConnector(mac).disconnect();
161 | }
162 |
163 | @Override
164 | public Observable write(final String mac, final UUID service, final UUID characteristic,
165 | final byte[] values) {
166 | return Observable.create(new ObservableOnSubscribe() {
167 | @Override
168 | public void subscribe(final ObservableEmitter e) throws Exception {
169 | BluetoothLeConnector connector = mClient.getBluetoothLeConnector(mac);
170 |
171 | final BluetoothLeConnector.OnDataAvailableListener onConnectListener
172 | = connector.getOnDataAvailableListener();
173 |
174 | connector.setOnDataAvailableListener(new BluetoothLeConnector.OnDataAvailableListener() {
175 | @Override
176 | public void onCharacteristicRead(byte[] values, int status) {
177 | onConnectListener.onCharacteristicRead(values, status);
178 | }
179 |
180 | @Override
181 | public void onCharacteristicChange(UUID characteristic, byte[] values) {
182 | onConnectListener.onCharacteristicChange(characteristic, values);
183 | }
184 |
185 | @Override
186 | public void onCharacteristicWrite(UUID characteristic, int status) {
187 | e.onNext(mac);
188 | e.onComplete();
189 | }
190 |
191 | @Override
192 | public void onDescriptorWrite(UUID descriptor, int status) {
193 | onConnectListener.onDescriptorWrite(descriptor, status);
194 | }
195 |
196 | @Override
197 | public void onError(String msg) {
198 | e.onError(new Exception(msg));
199 | }
200 | });
201 | connector.writeCharacteristic(service, characteristic, values);
202 | }
203 | });
204 | }
205 |
206 | @Override
207 | public Observable registerNotify(final String mac, final UUID service, final UUID characteristic,
208 | final BaseResultCallback callback) {
209 | return Observable.create(new ObservableOnSubscribe() {
210 | @Override
211 | public void subscribe(final ObservableEmitter e) throws Exception {
212 |
213 | BluetoothLeConnector connector = mClient.getBluetoothLeConnector(mac);
214 |
215 | final BluetoothLeConnector.OnDataAvailableListener onDataAvailableListener = connector
216 | .getOnDataAvailableListener();
217 |
218 | connector.setOnDataAvailableListener(new BluetoothLeConnector.OnDataAvailableListener() {
219 | @Override
220 | public void onCharacteristicRead(byte[] values, int status) {
221 | onDataAvailableListener.onCharacteristicRead(values, status);
222 | }
223 |
224 | @Override
225 | public void onCharacteristicChange(UUID characteristic, byte[] values) {
226 | callback.onSuccess(values);
227 | }
228 |
229 | @Override
230 | public void onCharacteristicWrite(UUID cha, int status) {
231 | onDataAvailableListener.onCharacteristicWrite(cha, status);
232 | }
233 |
234 | @Override
235 | public void onDescriptorWrite(UUID descriptor, int status) {
236 | if (status == BluetoothGatt.GATT_SUCCESS) {
237 | Log.d(TAG, "registerNotify pass");
238 | e.onNext(mac);
239 | e.onComplete();
240 | } else {
241 | String err = "write exception mac " + mac + " with " + status;
242 | Log.e(TAG, err);
243 | e.onError(new BluetoothWriteExceptionWithMac(err, mac));
244 | }
245 | }
246 |
247 | @Override
248 | public void onError(String msg) {
249 | e.onError(new Exception(msg));
250 | }
251 | });
252 | connector.setCharacteristicNotification(service, characteristic, true);
253 | }
254 | });
255 | }
256 |
257 | @Override
258 | public Observable unRegisterNotify(String mac, UUID service, UUID characteristic) {
259 | return null;
260 | }
261 |
262 | @Override
263 | public void clean(String mac) {
264 | mClient.cleanConnector(mac);
265 | }
266 |
267 | @Override
268 | public void cleanAll() {
269 | mClient.cleanAllConnector();
270 | }
271 |
272 | @Override
273 | public void openBluetooth() {
274 | mClient.initialize();
275 | }
276 |
277 | @Override
278 | public void closeBluetooth() {
279 |
280 | }
281 | }
282 |
--------------------------------------------------------------------------------
/app/src/main/java/com/billin/www/rxble/ble/bean/BLEDevice.java:
--------------------------------------------------------------------------------
1 | package com.billin.www.rxble.ble.bean;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 |
6 | /**
7 | * 存储蓝牙设备的信息
8 | *
9 | * Created by Billin on 2017/3/3.
10 | */
11 | public class BLEDevice implements Parcelable {
12 |
13 | private String deviceName;
14 |
15 | private String mac;
16 |
17 | private int rssi;
18 |
19 | public String getDeviceName() {
20 | return deviceName;
21 | }
22 |
23 | public void setDeviceName(String deviceName) {
24 | this.deviceName = deviceName;
25 | }
26 |
27 | public String getMac() {
28 | return mac;
29 | }
30 |
31 | public void setMac(String mac) {
32 | this.mac = mac;
33 | }
34 |
35 | public int getRssi() {
36 | return rssi;
37 | }
38 |
39 | public void setRssi(int rssi) {
40 | this.rssi = rssi;
41 | }
42 |
43 | public static Creator getCREATOR() {
44 | return CREATOR;
45 | }
46 |
47 | public BLEDevice() {
48 |
49 | }
50 |
51 | private BLEDevice(Parcel in) {
52 | int[] intArr = new int[1];
53 | in.readIntArray(intArr);
54 | setRssi(intArr[0]);
55 |
56 | String[] strings = new String[3];
57 | in.readStringArray(strings);
58 | setDeviceName(strings[0]);
59 | setMac(strings[1]);
60 | }
61 |
62 | public static final Creator CREATOR = new Creator() {
63 | @Override
64 | public BLEDevice createFromParcel(Parcel in) {
65 | return new BLEDevice(in);
66 | }
67 |
68 | @Override
69 | public BLEDevice[] newArray(int size) {
70 | return new BLEDevice[size];
71 | }
72 | };
73 |
74 | @Override
75 | public int describeContents() {
76 | return 0;
77 | }
78 |
79 | @Override
80 | public void writeToParcel(Parcel dest, int flags) {
81 | int[] intArr = new int[1];
82 | dest.writeIntArray(intArr);
83 | setRssi(intArr[0]);
84 |
85 | String[] strings = new String[3];
86 | dest.writeStringArray(strings);
87 | setDeviceName(strings[0]);
88 | setMac(strings[1]);
89 | }
90 |
91 | @Override
92 | public boolean equals(Object o) {
93 | if (this == o) return true;
94 | if (o == null || getClass() != o.getClass()) return false;
95 |
96 | BLEDevice device = (BLEDevice) o;
97 |
98 | return mac.equals(device.mac);
99 |
100 | }
101 |
102 | @Override
103 | public int hashCode() {
104 | return mac.hashCode();
105 | }
106 |
107 | @Override
108 | public String toString() {
109 | return "BLEDevice{" +
110 | "deviceName='" + deviceName + '\'' +
111 | ", mac='" + mac + '\'' +
112 | ", rssi=" + rssi +
113 | '}';
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/app/src/main/java/com/billin/www/rxble/ble/callback/BaseResultCallback.java:
--------------------------------------------------------------------------------
1 | package com.billin.www.rxble.ble.callback;
2 |
3 | /**
4 | * 用于数据回传的基本回调接口
5 | *
6 | * Created by Billin on 2017/3/9.
7 | */
8 | public interface BaseResultCallback {
9 |
10 | /**
11 | * 成功拿到数据
12 | *
13 | * @param data 回传的数据
14 | */
15 | void onSuccess(D data);
16 |
17 | /**
18 | * 操作失败
19 | *
20 | * @param msg 失败的返回的异常信息
21 | */
22 | void onFail(String msg);
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/billin/www/rxble/ble/callback/NONE.java:
--------------------------------------------------------------------------------
1 | package com.billin.www.rxble.ble.callback;
2 |
3 | /**
4 | * 这一个类用于表示什么也不是, 什么也没有
5 | *
6 | * Created by Billin on 2017/3/9.
7 | */
8 | public final class NONE {
9 |
10 | public static final NONE NONE = new NONE();
11 |
12 | private NONE() {
13 | }
14 |
15 | @Override
16 | public String toString() {
17 | return "NONE";
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/billin/www/rxble/ble/callback/SuccessResultCallback.java:
--------------------------------------------------------------------------------
1 | package com.billin.www.rxble.ble.callback;
2 |
3 | /**
4 | * 简化 Callback 的错误回调
5 | *
6 | * Created by Billin on 2017/5/12.
7 | */
8 | public abstract class SuccessResultCallback implements BaseResultCallback {
9 |
10 | private BaseResultCallback errorCallback;
11 |
12 | public SuccessResultCallback(BaseResultCallback errorCallback) {
13 | this.errorCallback = errorCallback;
14 | }
15 |
16 | @Override
17 | public void onFail(String msg) {
18 | errorCallback.onFail(msg);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/billin/www/rxble/ble/exception/BluetoothCannotAccessLocationException.java:
--------------------------------------------------------------------------------
1 | package com.billin.www.rxble.ble.exception;
2 |
3 | /**
4 | * Created by Billin on 2017/4/16.
5 | */
6 | public class BluetoothCannotAccessLocationException extends BluetoothException {
7 |
8 | public BluetoothCannotAccessLocationException(String msg) {
9 | super(msg);
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/billin/www/rxble/ble/exception/BluetoothConnectExceptionWithMac.java:
--------------------------------------------------------------------------------
1 | package com.billin.www.rxble.ble.exception;
2 |
3 | /**
4 | * 蓝牙连接异常
5 | *
6 | * Created by Billin on 2017/4/14.
7 | */
8 | public class BluetoothConnectExceptionWithMac extends BluetoothExceptionWithMac {
9 |
10 | public BluetoothConnectExceptionWithMac(String msg, String mac) {
11 | super(msg, mac);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/billin/www/rxble/ble/exception/BluetoothException.java:
--------------------------------------------------------------------------------
1 | package com.billin.www.rxble.ble.exception;
2 |
3 | /**
4 | * Created by Billin on 2017/4/16.
5 | */
6 | public class BluetoothException extends Exception {
7 |
8 | public BluetoothException(String msg) {
9 | super(msg);
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/billin/www/rxble/ble/exception/BluetoothExceptionWithMac.java:
--------------------------------------------------------------------------------
1 | package com.billin.www.rxble.ble.exception;
2 |
3 | public class BluetoothExceptionWithMac extends BluetoothException {
4 |
5 | String mac;
6 |
7 | public String getMac() {
8 | return mac;
9 | }
10 |
11 | public BluetoothExceptionWithMac(String msg, String mac) {
12 | super(msg);
13 | this.mac = mac;
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/billin/www/rxble/ble/exception/BluetoothNotOpenException.java:
--------------------------------------------------------------------------------
1 | package com.billin.www.rxble.ble.exception;
2 |
3 | /**
4 | * 蓝牙未开启异常
5 | *
6 | * Created by Billin on 2017/4/15.
7 | */
8 | public class BluetoothNotOpenException extends BluetoothException {
9 |
10 | public BluetoothNotOpenException(String msg) {
11 | super(msg);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/billin/www/rxble/ble/exception/BluetoothNotifyExceptionWithMac.java:
--------------------------------------------------------------------------------
1 | package com.billin.www.rxble.ble.exception;
2 |
3 | /**
4 | * Created by Billin on 2017/4/14.
5 | */
6 | public class BluetoothNotifyExceptionWithMac extends BluetoothExceptionWithMac {
7 |
8 | public BluetoothNotifyExceptionWithMac(String msg, String mac) {
9 | super(msg, mac);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/billin/www/rxble/ble/exception/BluetoothReadRssiExceptionWithMac.java:
--------------------------------------------------------------------------------
1 | package com.billin.www.rxble.ble.exception;
2 |
3 | /**
4 | * 蓝牙读取信号错误异常
5 | *
6 | * Created by Billin on 2017/4/14.
7 | */
8 | public class BluetoothReadRssiExceptionWithMac extends BluetoothExceptionWithMac {
9 |
10 | public BluetoothReadRssiExceptionWithMac(String msg, String mac) {
11 | super(msg, mac);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/billin/www/rxble/ble/exception/BluetoothSearchConflictException.java:
--------------------------------------------------------------------------------
1 | package com.billin.www.rxble.ble.exception;
2 |
3 | public class BluetoothSearchConflictException extends BluetoothException {
4 | public BluetoothSearchConflictException(String msg) {
5 | super(msg);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/billin/www/rxble/ble/exception/BluetoothWriteExceptionWithMac.java:
--------------------------------------------------------------------------------
1 | package com.billin.www.rxble.ble.exception;
2 |
3 | /**
4 | * Created by Billin on 2017/4/14.
5 | */
6 | public class BluetoothWriteExceptionWithMac extends BluetoothExceptionWithMac {
7 |
8 | public BluetoothWriteExceptionWithMac(String msg, String mac) {
9 | super(msg, mac);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/billin/www/rxble/ble/originV2/BluetoothLeConnector.java:
--------------------------------------------------------------------------------
1 | package com.billin.www.rxble.ble.originV2;
2 |
3 | import android.bluetooth.BluetoothAdapter;
4 | import android.bluetooth.BluetoothDevice;
5 | import android.bluetooth.BluetoothGatt;
6 | import android.bluetooth.BluetoothGattCallback;
7 | import android.bluetooth.BluetoothGattCharacteristic;
8 | import android.bluetooth.BluetoothGattDescriptor;
9 | import android.bluetooth.BluetoothGattService;
10 | import android.bluetooth.BluetoothProfile;
11 | import android.content.Context;
12 | import android.os.Handler;
13 | import android.os.HandlerThread;
14 | import android.os.SystemClock;
15 | import android.util.Log;
16 |
17 | import java.util.UUID;
18 | import java.util.concurrent.atomic.AtomicBoolean;
19 | import java.util.concurrent.atomic.AtomicInteger;
20 | import java.util.concurrent.atomic.AtomicLong;
21 |
22 | import io.reactivex.functions.Consumer;
23 |
24 | /**
25 | * Service for managing connection and data communication with a GATT server
26 | * hosted on a given Bluetooth LE device.
27 | *
28 | * Created by Billin on 2017/5/12.
29 | */
30 | public class BluetoothLeConnector {
31 |
32 | /**
33 | * 连接状态回调
34 | */
35 | public interface OnConnectListener {
36 | void onConnect();
37 |
38 | void onDisconnect();
39 |
40 | void onServiceDiscover();
41 |
42 | void onError(String msg);
43 | }
44 |
45 | /**
46 | * 读写回调接口
47 | */
48 | public interface OnDataAvailableListener {
49 | void onCharacteristicRead(byte[] values, int status);
50 |
51 | void onCharacteristicChange(UUID characteristic, byte[] values);
52 |
53 | void onCharacteristicWrite(UUID characteristic, int status);
54 |
55 | void onDescriptorWrite(UUID descriptor, int status);
56 |
57 | void onError(String msg);
58 | }
59 |
60 | private final static String TAG = "BluetoothLe";
61 |
62 | private static final UUID CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID
63 | = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
64 |
65 | private Context mContext;
66 |
67 | private BluetoothAdapter mBluetoothAdapter;
68 |
69 | private String mBluetoothDeviceAddress;
70 |
71 | private Handler mWorkHandler;
72 |
73 | private Handler mAlertHandler;
74 |
75 | private BluetoothGatt mBluetoothGatt;
76 |
77 | private OnConnectListener mOnConnectListener;
78 |
79 | private OnDataAvailableListener mOnDataAvailableListener;
80 |
81 | private AtomicInteger mConnectStatus = new AtomicInteger(BluetoothGatt.STATE_DISCONNECTED);
82 |
83 | private AtomicBoolean mIsStartService = new AtomicBoolean(false);
84 |
85 | private AtomicLong mDisconnectTime = new AtomicLong(SystemClock.elapsedRealtime());
86 |
87 | private AtomicLong mConnectTime = new AtomicLong(SystemClock.elapsedRealtime());
88 |
89 | private BluetoothGatt getBluetoothGatt() {
90 | return mBluetoothGatt;
91 | }
92 |
93 | private void setBluetoothGatt(BluetoothGatt bluetoothGatt) {
94 | this.mBluetoothGatt = bluetoothGatt;
95 | }
96 |
97 | private void setOnConnectListener(OnConnectListener l) {
98 | mOnConnectListener = l;
99 | }
100 |
101 | /**
102 | * 分别监听连接状态/服务/读取/写入
103 | */
104 | public void setOnDataAvailableListener(OnDataAvailableListener l) {
105 | mOnDataAvailableListener = l;
106 | }
107 |
108 | private OnConnectListener getOnConnectListener() {
109 | return mOnConnectListener;
110 | }
111 |
112 | public OnDataAvailableListener getOnDataAvailableListener() {
113 | return mOnDataAvailableListener;
114 | }
115 |
116 | BluetoothLeConnector(Context c, BluetoothAdapter adapter, String mac, Handler worker) {
117 | mContext = c.getApplicationContext();
118 | mBluetoothAdapter = adapter;
119 | mBluetoothDeviceAddress = mac;
120 |
121 | mWorkHandler = worker;
122 |
123 | HandlerThread thread = new HandlerThread("bluetooth alerter");
124 | thread.start();
125 | mAlertHandler = new Handler(thread.getLooper());
126 | }
127 |
128 | /**
129 | * Implements callback methods for GATT events that the app cares about. For
130 | * example, connection change and services discovered.
131 | * GATT连接的各种监听回调方法
132 | */
133 | private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
134 | @Override
135 | public void onConnectionStateChange(final BluetoothGatt gatt, final int status,
136 | final int newState) {
137 | mWorkHandler.post(new Runnable() {
138 | @Override
139 | public void run() {
140 |
141 | Log.d(TAG, "onConnectionStateChange: thread "
142 | + Thread.currentThread() + " status " + newState);
143 |
144 | // 清空连接初始化的超时连接任务代码
145 | mAlertHandler.removeCallbacksAndMessages(null);
146 |
147 | if (status != BluetoothGatt.GATT_SUCCESS) {
148 | String err = "Cannot connect device with error status: " + status;
149 | disconnectGatt();
150 | Log.e(TAG, err);
151 | mOnConnectListener.onError(err);
152 | mConnectStatus.set(BluetoothGatt.STATE_DISCONNECTED);
153 | return;
154 | }
155 |
156 | if (newState == BluetoothProfile.STATE_CONNECTED) {
157 | // setting connect status is connected
158 | mConnectStatus.set(BluetoothGatt.STATE_CONNECTED);
159 | mOnConnectListener.onConnect();
160 |
161 | // Attempts to discover services after successful connection.
162 | mIsStartService.set(false);
163 | if (!gatt.discoverServices()) {
164 | String err = "discover service return false";
165 | Log.e(TAG, err);
166 | gatt.disconnect();
167 | mOnConnectListener.onError(err);
168 | return;
169 | }
170 |
171 | // 解决连接 Service 过长的问题
172 | // 有些手机第一次启动服务的时间大于 2s
173 | mAlertHandler.postDelayed(new Runnable() {
174 | @Override
175 | public void run() {
176 | mWorkHandler.post(new Runnable() {
177 | @Override
178 | public void run() {
179 | if (!mIsStartService.get()) {
180 | gatt.disconnect();
181 | }
182 | }
183 | });
184 | }
185 | }, 3000L);
186 |
187 | } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
188 |
189 | if (!mIsStartService.get()) {
190 | String err = "service not found force disconnect";
191 | Log.e(TAG, err);
192 | mOnConnectListener.onError(err);
193 | }
194 |
195 | mOnConnectListener.onDisconnect();
196 | close();
197 | mConnectStatus.set(BluetoothGatt.STATE_DISCONNECTED);
198 | }
199 | }
200 | });
201 | }
202 |
203 | @Override
204 | public void onServicesDiscovered(final BluetoothGatt gatt, final int status) {
205 | mWorkHandler.post(new Runnable() {
206 | @Override
207 | public void run() {
208 | // 清空连接服务设置的超时回调
209 | mIsStartService.set(true);
210 | mAlertHandler.removeCallbacksAndMessages(null);
211 |
212 | if (status == BluetoothGatt.GATT_SUCCESS) {
213 | Log.d(TAG, "进入通道连接!!!! in thread " + Thread.currentThread());
214 | mOnConnectListener.onServiceDiscover();
215 | } else {
216 | String err = "onServicesDiscovered received: " + status;
217 | Log.e(TAG, err);
218 | gatt.disconnect();
219 | }
220 | }
221 | });
222 | }
223 |
224 | @Override
225 | public void onCharacteristicRead(final BluetoothGatt gatt,
226 | final BluetoothGattCharacteristic characteristic,
227 | final int status) {
228 |
229 | Log.d(TAG, "callback characteristic read status " + status
230 | + " in thread " + Thread.currentThread());
231 | if (status == BluetoothGatt.GATT_SUCCESS && mOnDataAvailableListener != null) {
232 | mOnDataAvailableListener.onCharacteristicRead(
233 | characteristic.getValue(),
234 | status);
235 | }
236 |
237 | }
238 |
239 | @Override
240 | public void onCharacteristicChanged(final BluetoothGatt gatt,
241 | final BluetoothGattCharacteristic characteristic) {
242 |
243 | Log.d(TAG, "callback characteristic change in thread " + Thread.currentThread());
244 | if (mOnDataAvailableListener != null) {
245 | mOnDataAvailableListener.onCharacteristicChange(
246 | characteristic.getUuid(), characteristic.getValue());
247 | }
248 |
249 | }
250 |
251 | @Override
252 | public void onCharacteristicWrite(final BluetoothGatt gatt,
253 | final BluetoothGattCharacteristic characteristic,
254 | final int status) {
255 | Log.d(TAG, "callback characteristic write in thread " + Thread.currentThread());
256 | if (mOnDataAvailableListener != null) {
257 | mOnDataAvailableListener.onCharacteristicWrite(
258 | characteristic.getUuid(), status);
259 | }
260 | }
261 |
262 | @Override
263 | public void onDescriptorWrite(final BluetoothGatt gatt,
264 | final BluetoothGattDescriptor descriptor,
265 | final int status) {
266 | Log.d(TAG, "callback descriptor write in thread " + Thread.currentThread());
267 |
268 | if (mOnDataAvailableListener != null) {
269 | mOnDataAvailableListener.onDescriptorWrite(
270 | descriptor.getUuid(), status);
271 | }
272 | }
273 | };
274 |
275 | /**
276 | * Connects to the GATT server hosted on the Bluetooth LE device.
277 | */
278 | public void connect(final OnConnectListener callback) {
279 | mWorkHandler.post(new Runnable() {
280 | @Override
281 | public void run() {
282 | Log.d(TAG, "connect: in thread " + Thread.currentThread());
283 |
284 | if (mBluetoothAdapter == null) {
285 | String err = "BluetoothAdapter not initialized or unspecified address.";
286 | Log.e(TAG, err);
287 | callback.onError(err);
288 | return;
289 | }
290 |
291 | final BluetoothDevice device
292 | = mBluetoothAdapter.getRemoteDevice(mBluetoothDeviceAddress);
293 | if (device == null) {
294 | String err = "Device not found. Unable to connect.";
295 | Log.e(TAG, err);
296 | callback.onError(err);
297 | return;
298 | }
299 |
300 | // 避免自动硬件断开后又自动连接,导致 service 回调被调用
301 | // 这里有隐患,实践证明 close 方法是异步调用的且单例,
302 | // 这就是说当一个 gatt 被创建之后,调用之前的 gatt 可能会把当前的 gatt close掉.
303 | // 最终造成 gatt 泄漏问题.
304 | // 一个解决方案就是延长连接硬件的时间
305 | if (mConnectStatus.get() != BluetoothGatt.STATE_DISCONNECTED) {
306 | String err = "Device is connecting";
307 | Log.e(TAG, err);
308 | callback.onError(err);
309 | return;
310 | }
311 |
312 | // 检查完没有任何错误再设置回调,确保上一次没有完成的操作得以继续回调,而不是被新的回调覆盖
313 | setOnConnectListener(callback);
314 |
315 | // We want to directly connect to the device, so we are setting the
316 | // autoConnect
317 | // parameter to false.
318 | Log.d(TAG, "Trying to create a new connection.");
319 | mConnectTime.set(SystemClock.elapsedRealtime());
320 | setBluetoothGatt(device.connectGatt(mContext, false, mGattCallback));
321 | if (getBluetoothGatt() == null) {
322 | String err = "bluetooth is not open!";
323 | Log.e(TAG, err);
324 | callback.onError(err);
325 | return;
326 | }
327 |
328 | mConnectStatus.set(BluetoothGatt.STATE_CONNECTING);
329 | mIsStartService.set(false);
330 |
331 | // 开一个定时器,如果超出 20s 就强制断开连接
332 | // 这个定时器必须在连接上设备之后清掉
333 | mAlertHandler.removeCallbacksAndMessages(null);
334 | mAlertHandler.postDelayed(new Runnable() {
335 | @Override
336 | public void run() {
337 | mWorkHandler.post(new Runnable() {
338 | @Override
339 | public void run() {
340 | if (mConnectStatus.get() == BluetoothGatt.STATE_CONNECTING) {
341 | disconnectGatt();
342 | String err = "connect timeout, cannot not connect device";
343 | Log.e(TAG, err);
344 | callback.onError(err);
345 | }
346 | }
347 | });
348 | }
349 | }, 20000L);
350 | }
351 | });
352 | }
353 |
354 | /**
355 | * Disconnects an existing connection or cancel a pending connection. The
356 | * disconnection result is reported asynchronously through the
357 | * {@link BluetoothGattCallback#onConnectionStateChange(BluetoothGatt, int, int)}
358 | * callback.
359 | */
360 | public void disconnect() {
361 | mWorkHandler.post(new Runnable() {
362 | @Override
363 | public void run() {
364 | disconnectGatt();
365 | }
366 | });
367 | }
368 |
369 | private void disconnectGatt() {
370 | Log.d(TAG, "disconnect: in thread " + Thread.currentThread());
371 |
372 | if (mBluetoothAdapter == null || getBluetoothGatt() == null) {
373 | Log.e(TAG, "BluetoothAdapter not initialized");
374 | return;
375 | }
376 |
377 | if (mConnectStatus.get() == BluetoothGatt.STATE_DISCONNECTED) {
378 | close();
379 | return;
380 | }
381 |
382 | getBluetoothGatt().disconnect();
383 |
384 | // 确保 Gatt 一定会被 close
385 | if (mConnectStatus.get() == BluetoothGatt.STATE_CONNECTING) {
386 | mAlertHandler.removeCallbacksAndMessages(null);
387 | close();
388 | }
389 | }
390 |
391 | /**
392 | * After using a given BLE device, the app must call this method to ensure
393 | * resources are released properly.
394 | */
395 | private void close() {
396 | Log.d(TAG, "close: in thread " + Thread.currentThread());
397 |
398 | if (getBluetoothGatt() == null) {
399 | Log.e(TAG, "BluetoothAdapter not initialized");
400 | return;
401 | }
402 |
403 | mDisconnectTime.set(SystemClock.elapsedRealtime());
404 | getBluetoothGatt().close();
405 | setBluetoothGatt(null);
406 | mConnectStatus.set(BluetoothGatt.STATE_DISCONNECTED);
407 | }
408 |
409 | private void callDataAvailableListenerError(String err) {
410 | Log.e(TAG, err);
411 | if (mOnDataAvailableListener != null) {
412 | mOnDataAvailableListener.onError(err);
413 | }
414 | }
415 |
416 | private void checkChannelAndDo(UUID service,
417 | UUID characteristic,
418 | Consumer action) {
419 |
420 | if (mBluetoothAdapter == null || getBluetoothGatt() == null
421 | || mConnectStatus.get() != BluetoothGatt.STATE_CONNECTED) {
422 | callDataAvailableListenerError("should be connect first!");
423 | return;
424 | }
425 |
426 | BluetoothGattService serviceChanel = getBluetoothGatt().getService(service);
427 | if (serviceChanel == null) {
428 | callDataAvailableListenerError("service is null");
429 | return;
430 | }
431 |
432 | BluetoothGattCharacteristic gattCharacteristic
433 | = serviceChanel.getCharacteristic(characteristic);
434 |
435 | if (characteristic == null) {
436 | callDataAvailableListenerError("characteristic is null");
437 | return;
438 | }
439 |
440 | try {
441 | action.accept(gattCharacteristic);
442 | } catch (Exception e) {
443 | e.printStackTrace();
444 | }
445 | }
446 |
447 | /**
448 | * 从蓝牙模块读取数据, 读取的数据将会异步回调到
449 | * {@link BluetoothLeConnector#setOnDataAvailableListener(OnDataAvailableListener)}
450 | * 方法设置的监听中
451 | */
452 | public void readCharacteristic(final UUID service, final UUID characteristic) {
453 | mWorkHandler.post(new Runnable() {
454 | @Override
455 | public void run() {
456 | Log.d(TAG, "in readCharacteristic");
457 | checkChannelAndDo(service, characteristic,
458 | new Consumer() {
459 | @Override
460 | public void accept(BluetoothGattCharacteristic bluetoothGattCharacteristic)
461 | throws Exception {
462 |
463 | if (getBluetoothGatt()
464 | .readCharacteristic(bluetoothGattCharacteristic)) {
465 |
466 | callDataAvailableListenerError("cannot start characteristic read");
467 | }
468 | }
469 | });
470 | }
471 | });
472 | }
473 |
474 | /**
475 | * write something data to characteristic
476 | */
477 | public void writeCharacteristic(final UUID service,
478 | final UUID characteristic,
479 | final byte[] values) {
480 | mWorkHandler.post(new Runnable() {
481 | @Override
482 | public void run() {
483 | Log.d(TAG, "writing characteristic in thread " + Thread.currentThread());
484 |
485 | checkChannelAndDo(service, characteristic,
486 | new Consumer() {
487 | @Override
488 | public void accept(BluetoothGattCharacteristic bluetoothGattCharacteristic)
489 | throws Exception {
490 |
491 | bluetoothGattCharacteristic.setValue(values);
492 |
493 | if (!getBluetoothGatt()
494 | .writeCharacteristic(bluetoothGattCharacteristic)) {
495 |
496 | callDataAvailableListenerError("cannot start characteristic write");
497 | }
498 | }
499 | });
500 | }
501 | });
502 | }
503 |
504 | /**
505 | * 往特定的通道写入数据
506 | */
507 | public void writeCharacteristic(UUID service, UUID characteristic, String values) {
508 | writeCharacteristic(service, characteristic, values.getBytes());
509 | }
510 |
511 | /**
512 | * 设置获取特征值UUID通知
513 | * Enables or disables notification on a give characteristic.
514 | *
515 | * @param characteristic Characteristic to act on.
516 | * @param enabled If true, enable notification. False otherwise.
517 | */
518 | public void setCharacteristicNotification(final UUID service,
519 | final UUID characteristic,
520 | final boolean enabled) {
521 |
522 | mWorkHandler.post(new Runnable() {
523 | @Override
524 | public void run() {
525 | checkChannelAndDo(service, characteristic, new Consumer() {
526 | @Override
527 | public void accept(BluetoothGattCharacteristic gattCharacteristic) throws Exception {
528 | if (enabled) {
529 | Log.i(TAG, "Enable Notification");
530 | getBluetoothGatt().setCharacteristicNotification(gattCharacteristic, true);
531 | BluetoothGattDescriptor descriptor = gattCharacteristic
532 | .getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID);
533 | descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
534 |
535 | if (!getBluetoothGatt().writeDescriptor(descriptor)) {
536 | callDataAvailableListenerError("cannot open notification channel");
537 | }
538 | } else {
539 | Log.i(TAG, "Disable Notification");
540 | getBluetoothGatt().setCharacteristicNotification(gattCharacteristic, false);
541 | BluetoothGattDescriptor descriptor = gattCharacteristic
542 | .getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID);
543 | descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
544 |
545 | if (!getBluetoothGatt().writeDescriptor(descriptor)) {
546 | callDataAvailableListenerError("cannot close notification channel");
547 | }
548 | }
549 | }
550 | });
551 | }
552 | });
553 | }
554 | }
555 |
--------------------------------------------------------------------------------
/app/src/main/java/com/billin/www/rxble/ble/originV2/BluetoothLeInitialization.java:
--------------------------------------------------------------------------------
1 | package com.billin.www.rxble.ble.originV2;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.bluetooth.BluetoothAdapter;
5 | import android.bluetooth.BluetoothManager;
6 | import android.content.Context;
7 | import android.os.Handler;
8 | import android.os.HandlerThread;
9 | import android.util.Log;
10 |
11 | import java.util.Map;
12 | import java.util.concurrent.ConcurrentHashMap;
13 |
14 | /**
15 | * 蓝牙操作管理类
16 | *
17 | * Created by Billin on 2017/5/12.
18 | */
19 | public class BluetoothLeInitialization {
20 |
21 | @SuppressLint("StaticFieldLeak")
22 | private static volatile BluetoothLeInitialization mInstance;
23 |
24 | private final static String TAG = "BluetoothLe";
25 |
26 | private final Context mContext;
27 |
28 | private static Handler mBluetoothWorker;
29 |
30 | private BluetoothAdapter mBluetoothAdapter;
31 |
32 | private BluetoothManager mBluetoothManager;
33 |
34 | private Map mGattConnectorMap
35 | = new ConcurrentHashMap<>();
36 |
37 | private BluetoothLeSearcher mBluetoothSearcher;
38 |
39 | private BluetoothLeInitialization(Context context) {
40 | mContext = context.getApplicationContext();
41 |
42 | HandlerThread thread = new HandlerThread("bluetooth worker");
43 | thread.start();
44 | mBluetoothWorker = new Handler(thread.getLooper());
45 | }
46 |
47 | public static BluetoothLeInitialization getInstance(Context context) {
48 | if (mInstance == null) {
49 | synchronized (BluetoothLeInitialization.class) {
50 | if (mInstance == null) {
51 | mInstance = new BluetoothLeInitialization(context);
52 | }
53 | }
54 | }
55 |
56 | return mInstance;
57 | }
58 |
59 | /**
60 | * 初始化BluetoothAdapter
61 | * Initializes a reference to the local Bluetooth adapter.
62 | *
63 | * @return Return true if the initialization is successful.
64 | */
65 | public boolean initialize() {
66 |
67 | // For API level 18 and above, get a reference to BluetoothAdapter
68 | // through BluetoothManager.
69 | if (mBluetoothManager == null) {
70 | mBluetoothManager = (BluetoothManager) mContext
71 | .getSystemService(Context.BLUETOOTH_SERVICE);
72 | if (mBluetoothManager == null) {
73 | Log.e(TAG, "Unable to initialize BluetoothManager.");
74 | return false;
75 | }
76 | }
77 |
78 | if (mBluetoothAdapter == null) {
79 | mBluetoothAdapter = mBluetoothManager.getAdapter();
80 | if (mBluetoothAdapter == null) {
81 | Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
82 | return false;
83 | }
84 | }
85 |
86 | return mBluetoothAdapter.isEnabled() || mBluetoothAdapter.enable();
87 | }
88 |
89 | public BluetoothLeSearcher getBluetoothSearcher() {
90 | if (mBluetoothSearcher == null) {
91 | synchronized (BluetoothLeInitialization.class) {
92 | if (mBluetoothSearcher == null) {
93 | if (mBluetoothAdapter == null) {
94 | // TODO: 2017/5/12 是否需要改成异常呢?
95 | String err = "cannot create BluetoothLeSearcher instance because not " +
96 | "initialize, please call initialize() method";
97 | Log.e(TAG, err);
98 | return null;
99 | }
100 |
101 | mBluetoothSearcher = new BluetoothLeSearcher(mContext, mBluetoothAdapter, mBluetoothWorker);
102 | }
103 | }
104 | }
105 |
106 | return mBluetoothSearcher;
107 | }
108 |
109 | public BluetoothLeConnector getBluetoothLeConnector(String mac) {
110 | BluetoothLeConnector result;
111 | if ((result = mGattConnectorMap.get(mac)) != null) {
112 | return result;
113 | }
114 |
115 | result = new BluetoothLeConnector(mContext, mBluetoothAdapter, mac, mBluetoothWorker);
116 | mGattConnectorMap.put(mac, result);
117 | return result;
118 | }
119 |
120 | public void cleanConnector(String mac) {
121 | BluetoothLeConnector result;
122 | if ((result = mGattConnectorMap.get(mac)) != null) {
123 | mGattConnectorMap.remove(mac);
124 | result.disconnect();
125 | result.setOnDataAvailableListener(null);
126 | }
127 | }
128 |
129 | /**
130 | * 在不在需要连接蓝牙设备的时候,
131 | * 或者生命周期暂停的时候调用这一个方法
132 | */
133 | public void cleanAllConnector() {
134 | for (String mac : mGattConnectorMap.keySet()) {
135 | cleanConnector(mac);
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/app/src/main/java/com/billin/www/rxble/ble/originV2/BluetoothLeSearcher.java:
--------------------------------------------------------------------------------
1 | package com.billin.www.rxble.ble.originV2;
2 |
3 | import android.Manifest;
4 | import android.bluetooth.BluetoothAdapter;
5 | import android.bluetooth.BluetoothDevice;
6 | import android.content.Context;
7 | import android.content.pm.PackageManager;
8 | import android.os.Build;
9 | import android.os.Handler;
10 | import android.os.HandlerThread;
11 | import android.support.v4.content.ContextCompat;
12 | import android.util.Log;
13 |
14 | import java.util.concurrent.atomic.AtomicBoolean;
15 |
16 | /**
17 | * 蓝牙扫描服务封装类
18 | *
19 | * Created by Billin on 2017/5/12.
20 | */
21 | public class BluetoothLeSearcher {
22 | public interface OnScanCallback extends BluetoothAdapter.LeScanCallback {
23 | void onComplete();
24 |
25 | void onError(String msg);
26 | }
27 |
28 | private static final String TAG = "BluetoothLe";
29 |
30 | private BluetoothAdapter mBluetoothAdapter;
31 |
32 | private final Handler mHandler;
33 |
34 | private Handler mAlertHandler;
35 |
36 | private OnScanCallback mScanCallback;
37 |
38 | private Context mContext;
39 |
40 | private AtomicBoolean mScanning = new AtomicBoolean(false);
41 |
42 | BluetoothLeSearcher(Context context, BluetoothAdapter adapter, Handler worker) {
43 | mContext = context;
44 | mBluetoothAdapter = adapter;
45 | mHandler = worker;
46 |
47 | HandlerThread thread = new HandlerThread("bluetooth searcher handler");
48 | thread.start();
49 | mAlertHandler = new Handler(thread.getLooper());
50 | }
51 |
52 | private OnScanCallback wrapCallback(final OnScanCallback callback) {
53 |
54 | return new OnScanCallback() {
55 | @Override
56 | public void onComplete() {
57 | runOn(new Runnable() {
58 | @Override
59 | public void run() {
60 | callback.onComplete();
61 | }
62 | });
63 | }
64 |
65 | @Override
66 | public void onError(final String msg) {
67 | runOn(new Runnable() {
68 | @Override
69 | public void run() {
70 | callback.onError(msg);
71 | }
72 | });
73 | }
74 |
75 | @Override
76 | public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) {
77 | runOn(new Runnable() {
78 | @Override
79 | public void run() {
80 | callback.onLeScan(device, rssi, scanRecord);
81 | }
82 | });
83 | }
84 | };
85 | }
86 |
87 | /**
88 | * 指定开始扫描蓝牙服务. 如果一个扫描服务正在运行,
89 | * 马上停止当前的扫描服务, 只进行新的扫描服务.
90 | */
91 | public void scanLeDevice(final int scanMillis,
92 | final OnScanCallback callback) {
93 |
94 | runOn(new Runnable() {
95 | @Override
96 | public void run() {
97 | int permissionCheck = ContextCompat.checkSelfPermission(mContext,
98 | Manifest.permission.ACCESS_COARSE_LOCATION);
99 |
100 | if (permissionCheck != PackageManager.PERMISSION_GRANTED
101 | && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
102 | String err = "Cannot have location permission";
103 | Log.e(TAG, err);
104 | callback.onError(err);
105 | return;
106 | }
107 |
108 | if (mScanning.get()) {
109 | stopScan();
110 | }
111 |
112 | mScanCallback = wrapCallback(callback);
113 |
114 | // Stops scanning after a pre-defined scan period.
115 | // 预先定义停止蓝牙扫描的时间(因为蓝牙扫描需要消耗较多的电量)
116 | mAlertHandler.removeCallbacksAndMessages(null);
117 | mAlertHandler.postDelayed(new Runnable() {
118 | @Override
119 | public void run() {
120 | stopScanLeDevice();
121 | }
122 | }, scanMillis);
123 |
124 | mScanning.set(true);
125 |
126 | // 定义一个回调接口供扫描结束处理
127 | // 指定扫描特定的支持service的蓝牙设备
128 | // call startLeScan(UUID[], BluetoothAdapter.LeScanCallback)
129 | // 可以使用rssi计算蓝牙设备的距离
130 | // 计算公式:
131 | // d = 10^((abs(RSSI) - A) / (10 * n))
132 | // 其中:
133 | // d - 计算所得距离
134 | // RSSI - 接收信号强度(负值)
135 | // A - 射端和接收端相隔1米时的信号强度
136 | // n - 环境衰减因子
137 | if (!mBluetoothAdapter.startLeScan(mScanCallback)) {
138 | callback.onError("Bluetooth is not opened!");
139 | }
140 | }
141 | });
142 | }
143 |
144 | public void stopScan() {
145 | if (mScanning.get()) {
146 |
147 | mScanning.set(false);
148 | mScanCallback.onComplete();
149 |
150 | mAlertHandler.removeCallbacksAndMessages(null);
151 | mBluetoothAdapter.stopLeScan(mScanCallback);
152 |
153 | mScanCallback = null;
154 | }
155 | }
156 |
157 | public void stopScanLeDevice() {
158 | runOn(new Runnable() {
159 | @Override
160 | public void run() {
161 | stopScan();
162 | }
163 | });
164 | }
165 |
166 | public boolean isScanning() {
167 | return mScanning.get();
168 | }
169 |
170 | private void runOn(Runnable runnable) {
171 | mHandler.post(runnable);
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Belolme/RxBLE/ae1ce65a339122b192b1ad0dad6f88d2e058ae22/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Belolme/RxBLE/ae1ce65a339122b192b1ad0dad6f88d2e058ae22/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Belolme/RxBLE/ae1ce65a339122b192b1ad0dad6f88d2e058ae22/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Belolme/RxBLE/ae1ce65a339122b192b1ad0dad6f88d2e058ae22/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Belolme/RxBLE/ae1ce65a339122b192b1ad0dad6f88d2e058ae22/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Belolme/RxBLE/ae1ce65a339122b192b1ad0dad6f88d2e058ae22/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Belolme/RxBLE/ae1ce65a339122b192b1ad0dad6f88d2e058ae22/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Belolme/RxBLE/ae1ce65a339122b192b1ad0dad6f88d2e058ae22/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Belolme/RxBLE/ae1ce65a339122b192b1ad0dad6f88d2e058ae22/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Belolme/RxBLE/ae1ce65a339122b192b1ad0dad6f88d2e058ae22/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | RxBLE
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/billin/www/rxble/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.billin.www.rxble;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.3.1'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
21 | task clean(type: Delete) {
22 | delete rootProject.buildDir
23 | }
24 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Belolme/RxBLE/ae1ce65a339122b192b1ad0dad6f88d2e058ae22/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat May 13 14:27:26 CST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------