├── .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 | --------------------------------------------------------------------------------