Android: Bluetooth в качестве сервиса

от автора

Реализация Bluetooth как сервиса в Android
Реализация Bluetooth как сервиса в Android

Почему? И как?

Вы когда-нибудь задавали себе вопрос, прочитав официальное руководство по bluetooth для Android, как управлять им внутри вашего приложения? Как сохранить соединение активным, даже когда вы переходите от одного действия к другому?

Что ж, в этом руководстве я постараюсь показать вам, как я реализовал связь bluetooth через Service, чтобы управлять bluetooth и соединением с различными действиями, используя Service Binding, а также установил слушатель обратного вызова для операций, получающих информацию о состоянии связи bluetooth.

В этом руководстве мы создадим четыре файла:

  • BluetoothSDKService :который реализует функциональные возможности bluetooth и выдает LocalBroadcast сообщения во время операций

  • BluetoothSDKListenerHelper : который выполняет BroadcastReceiver и запускает функции IBluetoothSDKListener 

  • IBluetoothSDKListener: наш Interface, который определяет функции обратного вызова

  • BluetoothUtils : который содержит имена действий, определенных для фильтрации событий в BroadcastReceiver

1) Определите действия

Первым шагом является определение файла BluetoothUtils.kt , который содержит действия, о которых мы хотим получать уведомления в нашей активности:

class BluetoothUtils {     companion object {         val ACTION_DISCOVERY_STARTED = "ACTION_DISCOVERY_STARTED"         val ACTION_DISCOVERY_STOPPED = "ACTION_DISCOVERY_STOPPED"         val ACTION_DEVICE_FOUND = "ACTION_DEVICE_FOUND"         val ACTION_DEVICE_CONNECTED = "ACTION_DEVICE_CONNECTED"         val ACTION_DEVICE_DISCONNECTED = "ACTION_DEVICE_DISCONNECTED"         val ACTION_MESSAGE_RECEIVED = "ACTION_MESSAGE_RECEIVED"         val ACTION_MESSAGE_SENT = "ACTION_MESSAGE_SENT"         val ACTION_CONNECTION_ERROR = "ACTION_CONNECTION_ERROR"         val EXTRA_DEVICE = "EXTRA_DEVICE"         val EXTRA_MESSAGE = "EXTRA_MESSAGE"     } }

Я определил несколько, но вы можете добавлять их по своему усмотрению.

2) Определите события-функции обратного вызова

Второй шаг — это определение нашего интерфейса, который будет содержать события, соответствующие действиям, которые мы определили в первом шаге. Итак, давайте продолжим и определим IBluetoothSDKListener как:

interface IBluetoothSDKListener {     /**      * from action BluetoothUtils.ACTION_DISCOVERY_STARTED      */     fun onDiscoveryStarted()      /**      * from action BluetoothUtils.ACTION_DISCOVERY_STOPPED      */     fun onDiscoveryStopped()      /**      * from action BluetoothUtils.ACTION_DEVICE_FOUND      */     fun onDeviceDiscovered(device: BluetoothDevice?)      /**      * from action BluetoothUtils.ACTION_DEVICE_CONNECTED      */     fun onDeviceConnected(device: BluetoothDevice?)      /**      * from action BluetoothUtils.ACTION_MESSAGE_RECEIVED      */     fun onMessageReceived(device: BluetoothDevice?, message: String?)      /**      * from action BluetoothUtils.ACTION_MESSAGE_SENT      */     fun onMessageSent(device: BluetoothDevice?)      /**      * from action BluetoothUtils.ACTION_CONNECTION_ERROR      */     fun onError(message: String?)      /**      * from action BluetoothUtils.ACTION_DEVICE_DISCONNECTED      */     fun onDeviceDisconnected() }

Этот интерфейс будет позже реализован в нашей активности, или фрагменте, который будет выполнять некоторые действия при появлении события. Например, когда устройство подключается, срабатывает функция onDeviceDiscovered, и затем вы можете перейти к выполнению определенных операций, например, как мы увидим в следующих шагах, отправить сообщение по bluetooth на только что подключенное устройство через наш BluetoothSDKService.

3) Определение BroadcastReceiver

Следующим шагом будет определение нашего BroadcastReceiver, задачей которого будет фильтрация намерений с нашими действиями, определенными до получения LocalBroadcastManager, для запуска функций обратного вызова, определенных в предыдущем разделе. Поэтому мы используем BluetoothSDKListenerHelper как:

class BluetoothSDKListenerHelper {     companion object {          private var mBluetoothSDKBroadcastReceiver: BluetoothSDKBroadcastReceiver? = null          class BluetoothSDKBroadcastReceiver : BroadcastReceiver() {             private var mGlobalListener: IBluetoothSDKListener? = null              public fun setBluetoothSDKListener(listener: IBluetoothSDKListener) {                 mGlobalListener = listener             }              public fun removeBluetoothSDKListener(listener: IBluetoothSDKListener): Boolean {                 if (mGlobalListener == listener) {                     mGlobalListener = null                 }                  return mGlobalListener == null             }              override fun onReceive(context: Context?, intent: Intent?) {                 val device =                     intent!!.getParcelableExtra<BluetoothDevice>(BluetoothUtils.EXTRA_DEVICE)                 val message = intent.getStringExtra(BluetoothUtils.EXTRA_MESSAGE)                  when (intent.action) {                     BluetoothUtils.ACTION_DEVICE_FOUND -> {                         mGlobalListener!!.onDeviceDiscovered(device)                     }                     BluetoothUtils.ACTION_DISCOVERY_STARTED -> {                         mGlobalListener!!.onDiscoveryStarted()                     }                     BluetoothUtils.ACTION_DISCOVERY_STOPPED -> {                         mGlobalListener!!.onDiscoveryStopped()                     }                     BluetoothUtils.ACTION_DEVICE_CONNECTED -> {                         mGlobalListener!!.onDeviceConnected(device)                     }                     BluetoothUtils.ACTION_MESSAGE_RECEIVED -> {                         mGlobalListener!!.onMessageReceived(device, message)                     }                     BluetoothUtils.ACTION_MESSAGE_SENT -> {                         mGlobalListener!!.onMessageSent(device)                     }                     BluetoothUtils.ACTION_CONNECTION_ERROR -> {                         mGlobalListener!!.onError(message)                     }                     BluetoothUtils.ACTION_DEVICE_DISCONNECTED -> {                         mGlobalListener!!.onDeviceDisconnected()                     }                 }             }         }          public fun registerBluetoothSDKListener(             context: Context?,             listener: IBluetoothSDKListener         ) {             if (mBluetoothSDKBroadcastReceiver == null) {                 mBluetoothSDKBroadcastReceiver = BluetoothSDKBroadcastReceiver()                  val intentFilter = IntentFilter().also {                     it.addAction(BluetoothUtils.ACTION_DEVICE_FOUND)                     it.addAction(BluetoothUtils.ACTION_DISCOVERY_STARTED)                     it.addAction(BluetoothUtils.ACTION_DISCOVERY_STOPPED)                     it.addAction(BluetoothUtils.ACTION_DEVICE_CONNECTED)                     it.addAction(BluetoothUtils.ACTION_MESSAGE_RECEIVED)                     it.addAction(BluetoothUtils.ACTION_MESSAGE_SENT)                     it.addAction(BluetoothUtils.ACTION_CONNECTION_ERROR)                     it.addAction(BluetoothUtils.ACTION_DEVICE_DISCONNECTED)                 }                   LocalBroadcastManager.getInstance(context!!).registerReceiver(                     mBluetoothSDKBroadcastReceiver!!, intentFilter                 )             }              mBluetoothSDKBroadcastReceiver!!.setBluetoothSDKListener(listener)         }          public fun unregisterBluetoothSDKListener(             context: Context?,             listener: IBluetoothSDKListener         ) {              if (mBluetoothSDKBroadcastReceiver != null) {                 val empty = mBluetoothSDKBroadcastReceiver!!.removeBluetoothSDKListener(listener)                   if (empty) {                     LocalBroadcastManager.getInstance(context!!)                         .unregisterReceiver(mBluetoothSDKBroadcastReceiver!!)                     mBluetoothSDKBroadcastReceiver = null                 }             }         }     } }

В действии или фрагменте мы реализуем наш IBluetoothSDKListener, который мы зарегистрируем через две функции registerBluetoothSDKListner() и unregisterBluetoothSDKListner(). Например:

class CoolFragment() : BottomSheetDialogFragment() {      private lateinit var mService: BluetoothSDKService     private lateinit var binding: FragmentPopupDiscoveredLabelerDeviceBinding      override fun onCreate(savedInstanceState: Bundle?) {         super.onCreate(savedInstanceState)     }     override fun onCreateView(         inflater: LayoutInflater,         container: ViewGroup?,         savedInstanceState: Bundle?     ): View? {         val view = inflater.inflate(R.layout.fragment_popup_discovered_labeler_device, container,false)         binding = FragmentPopupDiscoveredLabelerDeviceBinding.bind(view)                  bindBluetoothService()                  // Register Listener         BluetoothSDKListenerHelper.registerBluetoothSDKListener(requireContext(), mBluetoothListener)          return view     }      /**      * Bind Bluetooth Service      */     private fun bindBluetoothService() {         // Bind to LocalService         Intent(             requireActivity().applicationContext,             BluetoothSDKService::class.java         ).also { intent ->             requireActivity().applicationContext.bindService(                 intent,                 connection,                 Context.BIND_AUTO_CREATE             )         }     }      /**      * Handle service connection      */     private val connection = object : ServiceConnection {          override fun onServiceConnected(className: ComponentName, service: IBinder) {             val binder = service as BluetoothSDKService.LocalBinder             mService = binder.getService()         }          override fun onServiceDisconnected(arg0: ComponentName) {          }     }      private val mBluetoothListener: IBluetoothSDKListener = object : IBluetoothSDKListener {         override fun onDiscoveryStarted() {         }          override fun onDiscoveryStopped() {         }          override fun onDeviceDiscovered(device: BluetoothDevice?) {         }          override fun onDeviceConnected(device: BluetoothDevice?) {             // Do stuff when is connected         }          override fun onMessageReceived(device: BluetoothDevice?, message: String?) {         }          override fun onMessageSent(device: BluetoothDevice?) {         }          override fun onError(message: String?) {         }      }      override fun onDestroy() {         super.onDestroy()         // Unregister Listener         BluetoothSDKListenerHelper.unregisterBluetoothSDKListener(requireContext(), mBluetoothListener)     } }

Теперь наш фрагмент может быть запущен для событий, полученных BroadcastListener, который передает их через обратные вызовы в интерфейс нашего фрагмента. Чего теперь не хватает? Ну, важная часть: сервис Bluetooth! 

4) Определите сервис Bluetooth

А теперь самая сложная часть — Bluetooth Service. Мы собираемся определить класс, расширяющий Service, в котором мы определим функции, позволяющие привязывать Service и управлять потоками Bluetooth-соединения:

class BluetoothSDKService : Service() {      // Service Binder     private val binder = LocalBinder()      // Bluetooth stuff     private lateinit var bluetoothAdapter: BluetoothAdapter     private lateinit var pairedDevices: MutableSet<BluetoothDevice>     private var connectedDevice: BluetoothDevice? = null     private val MY_UUID = "..."     private val RESULT_INTENT = 15      // Bluetooth connections     private var connectThread: ConnectThread? = null     private var connectedThread: ConnectedThread? = null     private var mAcceptThread: AcceptThread? = null      // Invoked only first time     override fun onCreate() {         super.onCreate()         bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()     }      // Invoked every service star     override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {         return START_STICKY     }      /**      * Class used for the client Binder.      */     inner class LocalBinder : Binder() {         /*         Function that can be called from Activity or Fragment         */     }       /**      * Broadcast Receiver for catching ACTION_FOUND aka new device discovered      */     private val discoveryBroadcastReceiver = object : BroadcastReceiver() {          override fun onReceive(context: Context, intent: Intent) {             /*               Our broadcast receiver for manage Bluetooth actions             */         }     }      private inner class AcceptThread : Thread() {       // Body     }      private inner class ConnectThread(device: BluetoothDevice) : Thread() {       // Body     }      @Synchronized     private fun startConnectedThread(         bluetoothSocket: BluetoothSocket?,     ) {         connectedThread = ConnectedThread(bluetoothSocket!!)         connectedThread!!.start()     }      private inner class ConnectedThread(private val mmSocket: BluetoothSocket) : Thread() {       // Body     }       override fun onDestroy() {         super.onDestroy()         try {             unregisterReceiver(discoveryBroadcastReceiver)         } catch (e: Exception) {             // already unregistered         }     }      override fun onBind(intent: Intent?): IBinder? {         return binder     }      private fun pushBroadcastMessage(action: String, device: BluetoothDevice?, message: String?) {         val intent = Intent(action)         if (device != null) {             intent.putExtra(BluetoothUtils.EXTRA_DEVICE, device)         }         if (message != null) {             intent.putExtra(BluetoothUtils.EXTRA_MESSAGE, message)         }         LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(intent)     }  }

Чтобы сделать суть более читабельной, я закомментировал части о потоках, которые вы можете получить из официальной документации.

Как вы видите, в LocalBinder можно определить функции, которые будут видны действиям после привязки к ним. Например, мы можем определить функции для операций обнаружения, отправки сообщения или соединения, которые затем будут выполняться операции внутри сервиса.

     /**      * Class used for the client Binder.      */     inner class LocalBinder : Binder() {          /**          * Enable the discovery, registering a broadcastreceiver {@link discoveryBroadcastReceiver}          * The discovery filter by LABELER_SERVER_TOKEN_NAME          */         public fun startDiscovery(context: Context) {             val filter = IntentFilter(BluetoothDevice.ACTION_FOUND)             filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)             registerReceiver(discoveryBroadcastReceiver, filter)             bluetoothAdapter.startDiscovery()             pushBroadcastMessage(BluetoothUtils.ACTION_DISCOVERY_STARTED, null, null)         }                           /**          * stop discovery          */         public fun stopDiscovery() {             bluetoothAdapter.cancelDiscovery()             pushBroadcastMessage(BluetoothUtils.ACTION_DISCOVERY_STOPPED, null, null)         }                  // other stuff      }

Затем в потоках, управляющих сокетами, вы можете использовать функцию pushBroadcastMessage() для генерации событий и добавления информационного наполнения, такого как удаленное устройство и сообщение. Например:

private inner class ConnectedThread(private val mmSocket: BluetoothSocket) : Thread() {          private val mmInStream: InputStream = mmSocket.inputStream         private val mmOutStream: OutputStream = mmSocket.outputStream         private val mmBuffer: ByteArray = ByteArray(1024) // mmBuffer store for the stream          override fun run() {             var numBytes: Int // bytes returned from read()              // Keep listening to the InputStream until an exception occurs.             while (true) {                 // Read from the InputStream.                 numBytes = try {                     mmInStream.read(mmBuffer)                 } catch (e: IOException) {                     pushBroadcastMessage(                         BluetoothUtils.ACTION_CONNECTION_ERROR,                         null,                         "Input stream was disconnected"                     )                     break                 }                  val message = String(mmBuffer, 0, numBytes)                  // Send to broadcast the message                 pushBroadcastMessage(                     BluetoothUtils.ACTION_MESSAGE_RECEIVED,                     mmSocket.remoteDevice,                     message                 )             }         }          // Call this from the main activity to send data to the remote device.         fun write(bytes: ByteArray) {             try {                 mmOutStream.write(bytes)                  // Send to broadcast the message                 pushBroadcastMessage(                     BluetoothUtils.ACTION_MESSAGE_SENT,                     mmSocket.remoteDevice,                     null                 )             } catch (e: IOException) {                 pushBroadcastMessage(                     BluetoothUtils.ACTION_CONNECTION_ERROR,                     null,                     "Error occurred when sending data"                 )                 return             }         }          // Call this method from the main activity to shut down the connection.         fun cancel() {             try {                 mmSocket.close()             } catch (e: IOException) {                 pushBroadcastMessage(                     BluetoothUtils.ACTION_CONNECTION_ERROR,                     null,                     "Could not close the connect socket"                 )             }         }     }

Мы закончили!

Заключение

Мы видели, как из нашей активности можем связать сервис Bluetooth (1), который выполняет и управляет операциями Bluetooth. В нем мы можем запускать многоадресное событие (broadcast event) (2), которые получает Bluetooth-приемник. Получив их, Bluetooth-приемник, в свою очередь, вызывает функцию интерфейса, реализованную (4) в нашей активности, зарегистрированной на bluetooth-приемник(3)

Мой совет — всегда следовать официальному руководству и рекомендациям по написанию чистого кода.


Материал подготовлен в рамках специализации «Android Developer».

Всех желающих приглашаем на двухдневный онлайн-интенсив «Делаем мобильную мини-игру за 2 дня». За 2 дня вы сделаете мобильную версию PopIt на языке Kotlin. В приложении будет простая анимация, звук хлопка, вибрация, таймер как соревновательный элемент. Интенсив подойдет для тех, кто хочет попробовать себя в роли Android-разработчика. >> РЕГИСТРАЦИЯ


ссылка на оригинал статьи https://habr.com/ru/articles/575532/