Set up Bluetooth
Before your app can communicate over Bluetooth or Bluetooth Low Energy, you need to verify that Bluetooth is supported on the device, and if it is, ensure that it is enabled. Note that this check is only necessary if the android:required attribute in the manifest file entry is set to false .
If Bluetooth isn’t supported, then you should gracefully disable any Bluetooth features. If Bluetooth is supported, but disabled, then you can request that the user enable Bluetooth without leaving your app.
The first step is adding the Bluetooth permissions to your manifest file in order to use the following APIs.
Once the permissions are in place, Bluetooth setup is accomplished in two steps using the BluetoothAdapter :
- Get the BluetoothAdapter . The BluetoothAdapter is required for any and all Bluetooth activity. The BluetoothAdapter represents the device’s own Bluetooth adapter (the Bluetooth radio). To get a BluetoothAdapter , you first need to have a Context . Use this context to obtain an instance of the BluetoothManager system service. Calling BluetoothManager#getAdapter will give you a BluetoothAdapter object. If getAdapter() returns null, then the device doesn’t support Bluetooth. For example:
Kotlin
val bluetoothManager: BluetoothManager = getSystemService(BluetoothManager::class.java) val bluetoothAdapter: BluetoothAdapter? = bluetoothManager.getAdapter() if (bluetoothAdapter == null) < // Device doesn't support Bluetooth >
Java
BluetoothManager bluetoothManager = getSystemService(BluetoothManager.class); BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter(); if (bluetoothAdapter == null) < // Device doesn't support Bluetooth >
Kotlin
if (bluetoothAdapter?.isEnabled == false)
Java
if (!bluetoothAdapter.isEnabled())
A dialog appears requesting user permission to enable Bluetooth, as shown in figure 1. If the user grants permission, the system begins to enable Bluetooth, and focus returns to your app once the process completes (or fails).
Figure 1. The enabling Bluetooth dialog.
The REQUEST_ENABLE_BT constant passed to startActivityForResult() is a locally-defined integer that must be greater than or equal to 0. The system passes this constant back to you in your onActivityResult() implementation as the requestCode parameter.
If enabling Bluetooth succeeds, your activity receives the RESULT_OK result code in the onActivityResult() callback. If Bluetooth was not enabled due to an error (or the user responded «Deny») then the result code is RESULT_CANCELED .
Optionally, your app can also listen for the ACTION_STATE_CHANGED broadcast intent, which the system broadcasts whenever the Bluetooth state changes. This broadcast contains the extra fields EXTRA_STATE and EXTRA_PREVIOUS_STATE , containing the new and old Bluetooth states, respectively. Possible values for these extra fields are STATE_TURNING_ON , STATE_ON , STATE_TURNING_OFF , and STATE_OFF . Listening for this broadcast can be useful if your app needs to detect runtime changes made to the Bluetooth state.
Tip: Enabling discoverability automatically enables Bluetooth. If you plan to consistently enable device discoverability before performing Bluetooth activity, you can skip step 2 in the earlier steps.
Once Bluetooth is enabled on the device, you can use both Bluetooth classic and Bluetooth Low Energy.
Content and code samples on this page are subject to the licenses described in the Content License. Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.
Last updated 2023-02-01 UTC.
Android: Bluetooth в качестве сервиса
Вы когда-нибудь задавали себе вопрос, прочитав официальное руководство по bluetooth для Android, как управлять им внутри вашего приложения? Как сохранить соединение активным, даже когда вы переходите от одного действия к другому?
Что ж, в этом руководстве я постараюсь показать вам, как я реализовал связь bluetooth через Service, чтобы управлять bluetooth и соединением с различными действиями, используя Service Binding, а также установил слушатель обратного вызова для операций, получающих информацию о состоянии связи bluetooth.
В этом руководстве мы создадим четыре файла:
- BluetoothSDKService :который реализует функциональные возможности bluetooth и выдает LocalBroadcast сообщения во время операций
- BluetoothSDKListenerHelper : который выполняет BroadcastReceiver и запускает функции IBluetoothSDKListener
- IBluetoothSDKListener : наш Interface, который определяет функции обратного вызова
- BluetoothUtils : который содержит имена действий, определенных для фильтрации событий в BroadcastReceiver
1) Определите действия
Первым шагом является определение файла BluetoothUtils.kt , который содержит действия, о которых мы хотим получать уведомления в нашей активности:
Я определил несколько, но вы можете добавлять их по своему усмотрению.
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(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: MutableSetprivate 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 * 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-разработчика. >> РЕГИСТРАЦИЯ