我正在开发一个应用程序,用于使用 Android Studio 通过蓝牙远程控制 LED,适用于 Android 版本 13。
如果在应用程序启动之前在手机上启用蓝牙,应用程序会立即崩溃。如果您对我为什么会收到此错误有任何建议,请告诉我!
在 AndroidManifest.xml 文件中,我已声明 BLUETOOTH、BLUETOOTH_ADMIN 和 BLUETOOTH_CONNECT 的权限,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.LEDControl"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.LEDControl">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
</manifest>
UI 有一个简单的文本视图以及在 Activity_main.xml 中定义的“ON”和“OFF”按钮,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<!-- Text view for "LED Controller" -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="LED Controller"
android:textSize="24sp" />
<!-- "ON" Button -->
<Button
android:id="@+id/onButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ON" />
<!-- "OFF" Button -->
<Button
android:id="@+id/offButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="OFF" />
</LinearLayout>
然后,在 MainActivity.kt 中,我正在渲染上述布局,并尝试请求蓝牙权限,如下所示:
package com.example.ledcontrol
import android.util.Log
import android.Manifest
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothSocket
import android.content.pm.PackageManager
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.core.app.ActivityCompat
import com.example.ledcontrol.ui.theme.LEDControlTheme
import java.io.OutputStream
import java.util.*
class MainActivity : ComponentActivity() {
private val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()
private var bluetoothSocket: BluetoothSocket? = null
private var outputStream: OutputStream? = null
private val requestBluetoothPermission = 1
private val esp32DeviceAddress = "08:d1:f9:ce:b1:9e" // ESP32's Bluetooth device address
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (bluetoothAdapter != null && bluetoothAdapter.isEnabled) {
// Check if Bluetooth permissions are granted
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.BLUETOOTH
) == PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(
this,
Manifest.permission.BLUETOOTH_ADMIN
) == PackageManager.PERMISSION_GRANTED
) {
// Permissions are granted, proceed with Bluetooth operations
val device = bluetoothAdapter.getRemoteDevice(esp32DeviceAddress)
setupBluetoothConnection(device)
} else {
// Request Bluetooth permissions
ActivityCompat.requestPermissions(
this,
arrayOf(
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN
),
requestBluetoothPermission
)
}
}
val onButton = findViewById<Button>(R.id.onButton)
val offButton = findViewById<Button>(R.id.offButton)
onButton.setOnClickListener {
sendData("1")
Log.d("ButtonPress", "'On' button pressed")
}
offButton.setOnClickListener {
sendData("0")
Log.d("ButtonPress", "'Off' button pressed")
}
}
private fun setupBluetoothConnection(device: BluetoothDevice) {
// Initialize Bluetooth connection
try {
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.BLUETOOTH_CONNECT
) != PackageManager.PERMISSION_GRANTED
) {
return
}
bluetoothSocket = device.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"))
bluetoothSocket?.connect()
outputStream = bluetoothSocket?.outputStream
} catch (e: Exception) {
e.printStackTrace()
}
}
// Function to send data over Bluetooth
private fun sendData(data: String) {
Log.d("Info", "sendData called with data: $data")
try {
outputStream?.write(data.toByteArray())
// Log a message to indicate that data was sent
Log.d("Bluetooth", "Sent data: $data")
} catch (e: Exception) {
e.printStackTrace()
// Log an error message if there's an issue with sending data
Log.e("Bluetooth", "Error sending data: ${e.message}")
}
}
override fun onDestroy() {
super.onDestroy()
// Close the Bluetooth socket and output stream when the activity is destroyed
try {
outputStream?.close()
bluetoothSocket?.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
当在 UI 中按下按钮时,将调用 sendData 方法,并按预期打印日志,但蓝牙实现似乎存在一些问题。
虽然您的应用程序面向 Android 13,但您选择的权限则面向 Android 11 或更低版本。 Google文档如下:
如果应用程序面向Android 12或更高版本,则您需要在应用程序的清单文件中声明以下权限:
如果您的应用程序扫描BLE外设,您需要声明BLUETOOTH_SCAN权限。如果您的应用程序连接到蓝牙设备,您还必须声明 BLUETOOTH_CONNECT 权限。 由于 BLUETOOTH_ADVERTISE、BLUETOOTH_CONNECT 和 BLUETOOTH_SCAN 权限是运行时权限,因此您必须在应用程序中明确请求用户批准,然后才能搜索蓝牙设备、使设备可被其他设备发现或与已配对的蓝牙设备通信。
针对Android 11或更低版本的应用程序,需要声明以下权限:
执行任何经典蓝牙或 BLE 通信(请求和接受连接、传输数据)都需要蓝牙权限。 ACCESS_FINE_LOCATION 权限是必需的,因为在 Android 11 及更低版本上,蓝牙扫描可能会用于收集有关用户位置的信息,并且如果您希望应用启动设备发现或操作蓝牙设置,则需要声明 BLUETOOTH_ADMIN 权限。