Good day! I am writing an application in which the connection between the device and the smartphone via bluetooth takes place, namely, I send a command to the device, it sends me a line of data that needs to be parsed. Receive messages by using Handler . The problem is that in the application I have several activities, and when the activity changes, the team is sent, but the device does not receive it. Help to understand the problem. I believe that when changing bluetooth activities, the socket is broken, so the device does not receive a command from me. And another problem is how much need a handler ? For each activity of its own, or one for all?

Bluetooth Service

public class BluetoothService extends Service { public BluetoothAdapter bluetoothAdapter = null; BluetoothSocket mmSocket; public static final String SPP_UUID = "00001101-0000-1000-8000-00805F9B34FB"; //public static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); private ConnectThread mConnectThread; private ConnectedThread mConnectedThread; private static Handler mHandler = new MainActivity.mHandler(); public static int mState = Constants.STATE_NONE; StringBuilder sb; public static int TypeMessage; @Override public void onCreate() { Log.d(Constants.LOG_TAG, "***Service started***"); super.onCreate(); } @Override public IBinder onBind(Intent intent) { Log.d(Constants.LOG_TAG, "***Binding***"); //mHandler = ((MyApplication) getApplication()).getHandler(); return mBinder; } public class LocalBinder extends Binder { BluetoothService getService() { return BluetoothService.this; } } private final IBinder mBinder = new LocalBinder(); @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(Constants.LOG_TAG, "***OnStart Command***"); bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (bluetoothAdapter != null) { String MACaddr = intent.getStringExtra("mac"); Log.d(Constants.LOG_TAG, MACaddr); if (MACaddr != null && MACaddr.length() > 0) { connectToDevice(MACaddr); } else { stopSelf(); return START_STICKY_COMPATIBILITY; } } return START_STICKY; } /*final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (BluetoothDevice.ACTION_FOUND.equals(action)) { device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); deviceName = device.getName(); MAC = device.getAddress(); Message msg = mHandler.obtainMessage(MainActivity.MESSAGE_DEVICE); Bundle bundle = new Bundle(); bundle.putString("name", deviceName); bundle.putString("mac", MAC); msg.setData(bundle); mHandler.sendMessage(msg); Log.d(Constants.LOG_TAG, deviceName + MAC); } } };*/ private synchronized void connectToDevice(String MACaddr) { BluetoothDevice device = bluetoothAdapter.getRemoteDevice(MACaddr); Log.d(Constants.LOG_TAG, "***Получили удаленный Device***" + device.getName()); if (mState == Constants.STATE_CONNECTING) { if (mConnectThread != null) { mConnectThread.cancel(); mConnectThread = null; } } // Cancel any thread currently running a connection if (mConnectedThread != null) { mConnectedThread.cancel(); mConnectedThread = null; } mConnectThread = new ConnectThread(device); mConnectThread.start(); Log.d(Constants.LOG_TAG, "***Соединение с " + device.getName() + " успешно установлено***"); setState(Constants.STATE_CONNECTING); } /*public synchronized void connectToDevice(String MAC) { BluetoothDevice device = bluetoothAdapter.getRemoteDevice(MAC); Log.d(Constants.LOG_TAG, "***Получили удаленный Device***" + device.getName()); try { bluetoothSocket = device.createRfcommSocketToServiceRecord(MY_UUID); Log.d(Constants.LOG_TAG, "...Создали сокет..."); } catch (IOException e) { MyError("Fatal Error", "В onResume() Не могу создать сокет: " + e.getMessage() + "."); } bluetoothAdapter.cancelDiscovery(); Log.d(Constants.LOG_TAG, "***Отменили поиск других устройств***"); Log.d(Constants.LOG_TAG, "***Соединяемся...***"); try { bluetoothSocket.connect(); Log.d(Constants.LOG_TAG, "***Соединение успешно установлено***"); } catch (IOException e) { try { bluetoothSocket.close(); } catch (IOException e2) { MyError("Fatal Error", "В onResume() не могу закрыть сокет" + e2.getMessage() + "."); } } mConnectedThread = new ConnectedThread(bluetoothSocket); mConnectedThread.start(); TypeMessage = Constants.GET_GROUP_INFO; mConnectedThread.sendData("[GET_GROUP_INFO]"); //setState(Constants.STATE_CONNECTING); } private void MyError(String title, String message){ Toast.makeText(getBaseContext(), title + " - " + message, Toast.LENGTH_LONG).show(); stopSelf(); }*/ private void setState(int state) { BluetoothService.mState = state; if (mHandler != null) { mHandler.obtainMessage(MainActivity.MESSAGE_STATE_CHANGE, state, -1).sendToTarget(); } } public synchronized void stop() { setState(Constants.STATE_NONE); if (mConnectedThread != null) { mConnectedThread.cancel(); mConnectedThread = null; } if (bluetoothAdapter != null) { bluetoothAdapter.cancelDiscovery(); } stopSelf(); } @Override public boolean stopService(Intent name) { setState(Constants.STATE_NONE); if (mConnectThread != null) { mConnectThread.cancel(); mConnectThread = null; } if (mConnectedThread != null) { mConnectedThread.cancel(); mConnectedThread = null; } bluetoothAdapter.cancelDiscovery(); return super.stopService(name); } private void connectionFailed() { BluetoothService.this.stop(); Message msg = mHandler.obtainMessage(MainActivity.MESSAGE_TOAST); Bundle bundle = new Bundle(); bundle.putString(Constants.TOAST, getString(R.string.error_connect_failed)); msg.setData(bundle); mHandler.sendMessage(msg); } private void connectionLost() { BluetoothService.this.stop(); Message msg = mHandler.obtainMessage(MainActivity.MESSAGE_TOAST); Bundle bundle = new Bundle(); bundle.putString(Constants.TOAST, getString(R.string.error_connect_lost)); msg.setData(bundle); mHandler.sendMessage(msg); } private static Object obj = new Object(); private synchronized void connected(BluetoothSocket mmSocket, BluetoothDevice mmDevice) { // Cancel the thread that completed the connection if (mConnectThread != null) { mConnectThread.cancel(); mConnectThread = null; } // Cancel any thread currently running a connection if (mConnectedThread != null) { mConnectedThread.cancel(); mConnectedThread = null; } mConnectedThread = new ConnectedThread(mmSocket); mConnectedThread.start(); setState(Constants.STATE_CONNECTED); } private class ConnectThread extends Thread { private final BluetoothDevice mmDevice; public ConnectThread(BluetoothDevice device) { this.mmDevice = device; BluetoothSocket tmp = null; try { tmp = device.createRfcommSocketToServiceRecord(UUID.fromString(SPP_UUID)); Log.d(Constants.LOG_TAG, "...Создали сокет..."); } catch (IOException e) { e.printStackTrace(); } mmSocket = tmp; } @Override public void run() { setName("ConnectThread"); bluetoothAdapter.cancelDiscovery(); Log.d(Constants.LOG_TAG, "***Отменили поиск других устройств***"); try { mmSocket.connect(); } catch (IOException e) { try { mmSocket.close(); } catch (IOException e1) { e1.printStackTrace(); } connectionFailed(); return; } synchronized (BluetoothService.this) { mConnectThread = null; } connected(mmSocket, mmDevice); } public void cancel() { try { mmSocket.close(); } catch (IOException e) { Log.e(Constants.LOG_TAG, "***close() of connect socket failed***", e); } } } private class ConnectedThread extends Thread { private final BluetoothSocket mmSocket; private final InputStream mmInStream; private final OutputStream mmOutStream; private byte[] buffer; public ConnectedThread(BluetoothSocket socket) { mmSocket = socket; InputStream tmpIn = null; OutputStream tmpOut = null; try { tmpIn = socket.getInputStream(); tmpOut = socket.getOutputStream(); } catch (IOException e) { Log.e(Constants.LOG_TAG, "***temp sockets not created***", e); } mmInStream = tmpIn; mmOutStream = tmpOut; } @Override public void run() { buffer = new byte[1024]; int bytes; StringBuilder sb = new StringBuilder(); while (true) { try { bytes = mmInStream.read(buffer); String message = new String(buffer, 0, bytes); sb.append(message); handler.obtainMessage(0, buffer.length, -1, buffer).sendToTarget(); sb.setLength(0); } catch (Exception e) { e.printStackTrace(); BluetoothService.this.stop(); break; } } } public void sendData(String message) { byte[] msgBuffer = message.getBytes(); Log.d(Constants.LOG_TAG, "***Отправляем данные: " + message + "***" ); try { mmOutStream.write(msgBuffer); } catch (IOException e) { Log.e(Constants.LOG_TAG, "***Exception during write***", e); } } public void cancel() { try { mmSocket.close(); } catch (IOException e) { Log.e(Constants.LOG_TAG, "***close() of connect socket failed***", e); } } } public void trace(String msg) { Log.d(Constants.LOG_TAG, msg); toast(msg); } public void toast(String msg) { Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show(); } @Override public void onDestroy() { stop(); Log.d(Constants.LOG_TAG, "***Service Destroyed***"); //unregisterReceiver(broadcastReceiver); //Log.d(Constants.LOG_TAG, "***Receiver unregistered***"); super.onDestroy(); } private void sendMsg(int flag) { Message msg = new Message(); msg.what = flag; handler.sendMessage(msg); } private Handler handler = new Handler() { @Override public void handleMessage(Message msg) {// if (!Thread.currentThread().isInterrupted()) { switch (TypeMessage) { case Constants.GET_GROUP_INFO: break; case Constants.DATETIME_setD: break; case Constants.DATETIME: break; } } super.handleMessage(msg); } }; 

MainActivity

 public class MainActivity extends Activity { public static final int MESSAGE_STATE_CHANGE = 1; public static final int MESSAGE_TOAST = 2; public static final int MESSAGE_WRITE = 3; public static final int MESSAGE_DEVICE = 4; public static final int MESSAGE_ANSWER = 5; BluetoothAdapter bluetoothAdapter; BluetoothService btService; boolean mBound = false; static ArrayAdapter<String> btArrayAdapter; BluetoothDevice device; private StringBuilder sb = new StringBuilder(); public static TextView text, status, impWeight, curValueMeter, curDate, addressAPN, loginAPN, passAPN, URL, num1, num2, comSessMode, curSupVoltage, curBBVoltage, imei, levelGSM, repDate, backupInterval, maxSession, meterSinceFirst, meterFail, curRecInArch, meterType, meterSerNumber; Button synchr; ListView listView; static ProgressBar pb; Context context; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); status = (TextView) findViewById(R.id.textView); impWeight = (TextView) findViewById(R.id.textView2); curValueMeter = (TextView) findViewById(R.id.textView3); curDate = (TextView) findViewById(R.id.textView4); addressAPN = (TextView) findViewById(R.id.textView5); loginAPN = (TextView) findViewById(R.id.textView6); passAPN = (TextView) findViewById(R.id.textView7); URL = (TextView) findViewById(R.id.textView8); num1 = (TextView) findViewById(R.id.textView9); num2 = (TextView) findViewById(R.id.textView10); comSessMode = (TextView) findViewById(R.id.textView11); curSupVoltage = (TextView) findViewById(R.id.textView12); curBBVoltage = (TextView) findViewById(R.id.textView13); imei = (TextView) findViewById(R.id.textView14); levelGSM = (TextView) findViewById(R.id.textView15); repDate = (TextView) findViewById(R.id.textView16); backupInterval = (TextView) findViewById(R.id.textView17); maxSession = (TextView) findViewById(R.id.textView18); meterSinceFirst = (TextView) findViewById(R.id.textView19); meterFail = (TextView) findViewById(R.id.textView20); curRecInArch = (TextView) findViewById(R.id.textView21); meterType = (TextView) findViewById(R.id.textView22); meterSerNumber = (TextView) findViewById(R.id.textView23); bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (bluetoothAdapter == null) { Toast.makeText(getBaseContext(), "Device does not support Bluetooth", Toast.LENGTH_SHORT).show(); finish(); } else { if (!bluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, 1); } } text = (TextView) findViewById(R.id.textView); pb = (ProgressBar) findViewById(R.id.pb); synchr = (Button) findViewById(R.id.synchr); synchr.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yy,HH:mm:ss"); final String d = dateFormat.format(new Date()); runOnUiThread(new Runnable() { @Override public void run() { } }); } }); } @Override protected void onStart() { super.onStart(); Intent intent = new Intent(this, BluetoothService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onStop() { super.onStop(); if (mBound) { unbindService(mConnection); mBound = false; } } private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.d(Constants.LOG_TAG, "***Service connected***"); BluetoothService.LocalBinder binder = (BluetoothService.LocalBinder) service; btService = binder.getService(); mBound = true; } @Override public void onServiceDisconnected(ComponentName name) { mBound = false; } }; final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (BluetoothDevice.ACTION_FOUND.equals(action)) { device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); btArrayAdapter.add(device.getName() + "\n" + device.getAddress()); btArrayAdapter.notifyDataSetChanged(); } } }; public void search() { if (bluetoothAdapter.isDiscovering()) { bluetoothAdapter.cancelDiscovery(); } else { btArrayAdapter.clear(); bluetoothAdapter.startDiscovery(); registerReceiver(broadcastReceiver, new IntentFilter(BluetoothDevice.ACTION_FOUND)); } } @Override protected void onResume() { super.onResume(); //startService(new Intent(this, BluetoothService.class)); //Log.d(Constants.LOG_TAG, "***OnStartCommand started***"); pb.setVisibility(View.VISIBLE); setup(); search(); } private void setup() { listView = (ListView) findViewById(R.id.listView); btArrayAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1); listView.setAdapter(btArrayAdapter); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { pb.setVisibility(ProgressBar.GONE); listView.setVisibility(View.GONE); synchr.setVisibility(View.VISIBLE); String itemValue = (String) listView.getItemAtPosition(position); String MAC = itemValue.substring(itemValue.length() - 17); Intent serviceIntent = new Intent(MainActivity.this, BluetoothService.class); serviceIntent.putExtra("mac", MAC); startService(serviceIntent); } }); } @Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(broadcastReceiver); Log.d(Constants.LOG_TAG, "***Receiver unregistered***"); } public static class mHandler extends Handler { @Override public void handleMessage (android.os.Message msg){ switch (msg.what) { case (MESSAGE_STATE_CHANGE): switch (msg.arg1) { case Constants.STATE_CONNECTED: break; case Constants.STATE_CONNECTING: break; case Constants.STATE_NONE: break; case Constants.STATE_ERROR: break; } break; case (MESSAGE_TOAST): msg.getData().getString(Constants.TOAST); break; case (MESSAGE_WRITE): break; case (MESSAGE_DEVICE): String deviceName = msg.getData().getString("name"); String MACAddr = msg.getData().getString("mac"); btArrayAdapter.add(deviceName +"\n" +MACAddr); btArrayAdapter.notifyDataSetChanged(); break; case (MESSAGE_ANSWER): String GGI = msg.getData().getString("GGI"); String[] result = GGI.split(";"); StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < result.length; i++) { stringBuilder.append(result[i]); if (i != result.length - 1) { stringBuilder.append("\n"); status.setTextColor(Color.GREEN); status.setText("Статус: НОРМА"); impWeight.setText("Вес импульса: " + result[1]); curValueMeter.setText("Текущее значение счётчика: " + result[2]); curDate.setText("Текущая дата: " + result[3]); addressAPN.setText("Адрес APN: " + result[4]); loginAPN.setText("Логин APN: " + result[5]); passAPN.setText("Пароль APN: " + result[6]); URL.setText("URL адрес сервера: " + result[7]); num1.setText("Телефон для СМС №1: " + result[8]); num2.setText("Телефон для СМС №2: " + result[9]); String comsesmode = result[10]; String[] split = comsesmode.split(","); StringBuilder sbb = new StringBuilder(); for (int j = 0; j < split.length; j++) { sbb.append(split[j]); if (j != split.length - 1) { sbb.append(","); } } int k = Integer.parseInt(split[0]); if (k == 1) { comSessMode.setTextColor(Color.RED); comSessMode.setText("Режим сеанса связи: каждый час"); } if (k == 2) { comSessMode.setText("Режим сеанса связи: каждый день в " + split[1]); } if (k == 3) { comSessMode.setText("Режим сеанса связи: каждую неделю в " + split[2] + " день недели в " + split[1]); } if (k == 4) { comSessMode.setText("Режим сеанса связи: " + split[2] + " числа каждый месяц в " + split[1]); } curSupVoltage.setText("Текущее напряжение питания в mV: " + result[11]); curBBVoltage.setText("Текущее напряжение резервной батареи в mV: " + result[12]); imei.setText("IMEI модема: " + result[13]); levelGSM.setText("Уровень GSM сигнала: " + result[14]); repDate.setText("Отчётная дата: " + result[15]); backupInterval.setText("Резервный интервал: " + result[16]); maxSession.setText("Максимальное число сеансов связи: " + result[17]); meterSinceFirst.setText("Счётчик числа сеансов связи с момента первого включения: " + result[18]); meterFail.setText("Счётчик количества неуд. попыток связи до успешной связи: " + result[19]); curRecInArch.setText("Номер текущей записи в интервальном архиве: " + result[20]); meterType.setText("Тип счётчика: " + result[21]); meterSerNumber.setText("Серийный номер счетчика: " + result[22]); } } break; default: break; } } } 

    2 answers 2

    I recommend to take out the logic of working with Bluetooth into a real Android service, in which you will receive commands from the Activity and process responses from the device and then send the data back; this will abstract you from the low-level work in the activation itself. And if you want the data to be saved during the activation, you need to do something like the Observable pattern and notify the service of the readiness to receive data, and make a data queue in the service to send data that the previous activation did not accept.

    • Thanks, I will try! - kozlovaLi
    • I am trying to remove all the logic of working with bluetooth in the service and there was a problem with the search for devices. How to implement it in service? Maybe there are links to examples or you can tell? Thank! - kozlovaLi
    • And what exactly happened can be more specific? - Ruslan Yagupov
    • The fact of the matter is that nothing happens when I try to start a device search not in the Activity, but in the Service. That is, I want to start a device search in the Service, display these devices in the ListView in the Activity, and click on the device to connect to it. Something like that. - kozlovaLi
    • I can offer such a solution - in the service we get a list of devices, convert it into the necessary model for the adapter of the list into activation, send it to the intent, display it, select an id or model back, and connect to the device. Try, if something is wrong, attach the code preferably - Ruslan Yagupov

    Made a service to work with Ble, while raw, but it works stably:

     class BluetoothService : Service(), SearchService, ConnectionService { private val TAG = javaClass.canonicalName companion object { val SERVICE_UUID = UUID.fromString("b546f776-ed87-4944-85b2-edd581cfbd97") val CHARACTERISTIC_1_UUID = UUID.fromString("b546f776-ed87-4944-85b2-edd581cfbd97") val CHARACTERISTIC_2_REGISTRATION_UUID = UUID.fromString("b546f776-ed87-4944-85b2-edd581cfbd97") val CHARACTERISTIC_3_UUID = UUID.fromString("b546f776-ed87-4944-85b2-edd581cfbd97") val CLIENT_CHARACTERISTIC_CONFIG_UUID = convertFromInteger(0x2902) private fun convertFromInteger(i: Int): UUID { val MSB = 0x0000000000001000L val LSB = -0x7fffff7fa064cb05L val value = (i and -0x1).toLong() return UUID(MSB or (value shl 32), LSB) } } enum class ServiceState { UNBIND, BOUND, IDLE, SCANNING } inner class BluetoothServiceBinder : Binder() { internal val searchService: BluetoothService get() = this@BluetoothService } private val binder = BluetoothServiceBinder() private lateinit var bluetoothAdapter: BluetoothAdapter private val devices = HashSet<BluetoothDevice>() private var gattDevice: MutableMap<BluetoothGatt, Device> = mutableMapOf() private var mGattListener: MutableMap<BluetoothGatt, ConnectionService.DeviceConnectionListener> = mutableMapOf() private var mDeviceListener: MutableMap<Device, ConnectionService.AttributesActionListener> = mutableMapOf() private var mGattNotificationListener: MutableMap<BluetoothGatt, ConnectionService.AttributesActionListener> = mutableMapOf() private var bound: Int = 0 private var state: ServiceState = ServiceState.UNBIND private val mListeners: MutableSet<DosimeterSearchService.ConnectionListener> = mutableSetOf() private data class Argument( val action: ConnectionService.Action, val attribute: ConnectionService.Attribute, val characteristic: UUID, val value: Any? = null) private val attributeArgument: MutableMap<BluetoothGatt, MutableMap<BluetoothGattCharacteristic, Argument>> = mutableMapOf() private val attributeArgumentChanged: MutableMap<BluetoothGatt, MutableMap<BluetoothGattCharacteristic, Argument>> = mutableMapOf() override fun onBind(intent: Intent): IBinder? { bound++ state = ServiceState.BOUND val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager bluetoothAdapter = bluetoothManager.adapter return binder } override fun onUnbind(intent: Intent): Boolean { if (--bound == 0) { state = ServiceState.UNBIND } return super.onUnbind(intent) } override fun startScan(scanTime: Long, listener: DosimeterSearchService.ConnectionListener) { devices.clear() mListeners.add(listener) if (!bluetoothAdapter.isEnabled) { listener.onEnableBluetoothRequest(this) } else { devices.clear() Handler().postDelayed({ stopScan() }, scanTime) bluetoothAdapter.startLeScan(arrayOf(SERVICE_UUID), mLeScanCallback) listener.onScanStarted(this) } } private val mLeScanCallback = BluetoothAdapter.LeScanCallback { device, rssi, scanRecord -> if (devices.add(device)) { mListeners.forEach { it.onDeviceFound(this) } } } override fun stopScan() { bluetoothAdapter.stopLeScan(mLeScanCallback) mListeners.forEach { it.onScanStopped(this) } } override fun getFoundDevices(): List<Device> { return devices.map { Device(it) } } override fun connect(device: Device, autoConnect: Boolean, connectionListener: ConnectionService.DeviceConnectionListener) { if (state != ServiceState.SCANNING) { val gatt = device.device.connectGatt(this, autoConnect, mGattCallback) gattDevice[gatt] = device mGattListener[gatt] = connectionListener } else { } } override fun disconnect(device: Device) { if (device.connectionState == Device.ConnectionState.DISCOVERED) { if (gattDevice.containsValue(device)) { gattDevice .filter { it.value == device } .forEach { it.key.disconnect() it.key.close() } } } } private val mGattCallback = object : BluetoothGattCallback() { override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) { when (newState) { BluetoothProfile.STATE_CONNECTED -> { gattDevice[gatt]?.connectionState = Device.ConnectionState.CONNECTED mGattListener[gatt]?.onConnected(this@BluetoothService, gattDevice[gatt]!!) gatt?.discoverServices() } BluetoothProfile.STATE_DISCONNECTED -> { gattDevice[gatt]?.connectionState = Device.ConnectionState.DISCONNECTED mGattListener[gatt]?.onDisconnect(this@BluetoothService, gattDevice[gatt]!!) mDeviceListener.remove(gattDevice[gatt]) gattDevice.remove(gatt) mGattListener.remove(gatt) mGattNotificationListener.remove(gatt) attributeArgument.remove(gatt) attributeArgumentChanged.remove(gatt) gatt?.disconnect() gatt?.close() } } state = ServiceState.IDLE } override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) { if (status == BluetoothGatt.GATT_SUCCESS) { gattDevice[gatt]?.connectionState = Device.ConnectionState.DISCOVERED mGattListener[gatt]?.onDiscovered(this@BluetoothService, gattDevice[gatt]!!) } else { } } override fun onCharacteristicRead(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?, status: Int) { val attribute = attributeArgument[gatt]!![characteristic]!! attributeArgument[gatt]!!.remove(characteristic) mDeviceListener[gattDevice[gatt]]?.onAttributeAction(this@BluetoothService, gattDevice[gatt]!!, attribute.action, attribute.attribute, characteristic?.value) } override fun onCharacteristicWrite(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?, status: Int) { val attribute = attributeArgument[gatt]!![characteristic]!! attributeArgument[gatt]!!.remove(characteristic) mDeviceListener[gattDevice[gatt]]?.onAttributeAction(this@BluetoothService, gattDevice[gatt]!!, attribute.action, attribute.attribute, characteristic?.value) } override fun onCharacteristicChanged(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?) { val attribute = attributeArgumentChanged[gatt]!![characteristic]!! mDeviceListener[gattDevice[gatt]]?.onAttributeAction(this@BluetoothService, gattDevice[gatt]!!, attribute.action, attribute.attribute, characteristic?.value) } } override fun attribute(device: Device, attribute: ConnectionService.Attribute, action: ConnectionService.Action, attributesActionListener: ConnectionService.AttributesActionListener, value: Any?) { if (device.connectionState == Device.ConnectionState.DISCOVERED) { if (gattDevice.containsValue(device)) { mDeviceListener[device] = attributesActionListener gattDevice.entries .filter { it.value == device } .forEach { val uuid: UUID = when (attribute) { ConnectionService.Attribute.SAMPLING -> { CHARACTERISTIC_1_UUID } ConnectionService.Attribute.PARTICLE_REGISTRATION -> { CHARACTERISTIC_2_REGISTRATION_UUID } ConnectionService.Attribute.DOZE -> { CHARACTERISTIC_3_UUID } } val gatt = it.key val characteristic = gatt.getService(SERVICE_UUID).getCharacteristic(uuid) when (action) { ConnectionService.Action.READ -> { if (attributeArgument[gatt] == null) { attributeArgument[gatt] = mutableMapOf() } attributeArgument[gatt]!![characteristic] = Argument(action, attribute, uuid) gatt.readCharacteristic(characteristic) } ConnectionService.Action.WRITE -> { if (attributeArgument[gatt] == null) { attributeArgument[gatt] = mutableMapOf() } attributeArgument[gatt]!![characteristic] = Argument(action, attribute, uuid, value) characteristic.value = value as ByteArray gatt.writeCharacteristic(characteristic) } ConnectionService.Action.NOTIFICATION -> { if (attributeArgumentChanged[gatt] == null) { attributeArgumentChanged[gatt] = mutableMapOf() } attributeArgumentChanged[gatt]!![characteristic] = Argument(action, attribute, uuid) gatt.setCharacteristicNotification(characteristic, value as Boolean) val particleRegistrationDescriptor = characteristic?.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID) if (particleRegistrationDescriptor?.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)!!) { gatt.writeDescriptor(particleRegistrationDescriptor) } } } } } } }} 

    SearchService:

     interface SearchService { interface ConnectionListener { fun onEnableBluetoothRequest(search: SearchService) fun onScanStarted(search: SearchService) fun onScanStopped(search: SearchService) fun onDeviceFound(search: SearchService) } fun startScan(scanTime: Long, listener: ConnectionListener) fun stopScan() fun getFoundDevices(): List<Device>} 

    ConnectionService:

     interface ConnectionService { enum class Action { READ, WRITE, NOTIFICATION } enum class Attribute { BOOLEAN_TYPE { override fun <T> value(value: Any?): T { return ((value as ByteArray)[0].toInt() == 1) as T } }, NOTIFICATION_TYPE { override fun <T> value(value: Any?): T { return 0 as T } }, LONG_TYPE { override fun <T> value(value: Any?): T { val byte = value as ByteArray return unsignedBytesToInt(byte[0], byte[1], byte[2], byte[3]) as T } }; abstract fun <T> value(value: Any?): T protected fun unsignedBytesToInt(b0: Byte, b1: Byte, b2: Byte, b3: Byte): Int { return (unsignedByteToInt(b0) + (unsignedByteToInt(b1) shl 8) + (unsignedByteToInt(b2) shl 16) + (unsignedByteToInt(b3) shl 24)) } private fun unsignedByteToInt(b: Byte): Int { return b.toInt() and 0xFF } } interface AttributesActionListener { fun onAttributeAction(searchService: SearchService, device: Device, action: Action, attribute: Attribute, value: Any? = null) } interface DeviceConnectionListener { fun onConnected(service: ConnectionService, device: Device) fun onDiscovered(service: ConnectionService, device: Device) fun onDisconnect(service: ConnectionService, device: Device) } fun connect(device: Device, autoConnect: Boolean, connectionListener: DeviceConnectionListener) fun disconnect(device: Device) fun attribute(device: Device, attribute: Attribute, action: Action, attributesActionListener: AttributesActionListener, value: Any? = null)} 

    Device:

     data class Device(val device: BluetoothDevice) : Parcelable { enum class ConnectionState { DISCONNECTED, SCAN, CONNECTED, DISCOVERED } var connectionState: ConnectionState = ConnectionState.DISCONNECTED constructor(parcel: Parcel) : this(parcel.readParcelable(BluetoothDevice::class.java.classLoader) as BluetoothDevice) fun getName(): String { return device.name } fun getAddress(): String { return device.address } override fun writeToParcel(parcel: Parcel, flags: Int) { parcel.writeParcelable(device, flags) } override fun describeContents(): Int { return 0 } companion object CREATOR : Parcelable.Creator<Device> { override fun createFromParcel(parcel: Parcel): Device { return Device(parcel) } override fun newArray(size: Int): Array<Device?> { return arrayOfNulls(size) } }} 

    Use this:

     var mSearchService: BluetoothService? = null var mDevice: Device? = null private val mConnection = object : ServiceConnection { override fun onServiceConnected(className: ComponentName, service: IBinder) { val binder = service as BluetoothService.BluetoothServiceBinder mSearchService= binder.searchService } override fun onServiceDisconnected(arg0: ComponentName) { mSearchService = null }} override fun onStart() { val bluetooth = ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH) == PackageManager.PERMISSION_GRANTED val bluetoothAdmin = ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_ADMIN) == PackageManager.PERMISSION_GRANTED val gps = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED if (bluetooth && bluetoothAdmin && gps) { val intent = Intent(this, BluetoothService::class.java) bindService(intent, mConnection, Context.BIND_AUTO_CREATE) } else { ActivityCompat.requestPermissions(this, arrayOf( Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION), PERMISSIONS_REQUEST_BLUETOOTH) } super.onStart() } override fun onStop() { unbindService(mConnection) super.onStop() } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (requestCode == REQUEST_SELECT_DEVICE && resultCode == Activity.RESULT_OK) { connect.isEnabled = false mDevice = data?.getParcelableExtra(DEVICE) mSearch?.connect(mDevice!!, false, this) } }