Android 13蓝牙LED控制应用开启蓝牙时启动崩溃求助
Android 13蓝牙LED控制应用启动崩溃排查与修复
我正在开发一款基于Android 13的蓝牙远程控制LED应用,当手机提前开启蓝牙时,应用启动后立即崩溃。按钮点击时sendData方法能正常触发并打印日志,但蓝牙模块存在异常导致崩溃。
AndroidManifest.xml 权限配置
<?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>
activity_main.xml 布局文件
<?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() } } }
崩溃原因与修复方案
1. 主线程执行阻塞蓝牙操作
Android 13禁止在主线程执行耗时的蓝牙连接操作,bluetoothSocket?.connect()是阻塞调用,直接在onCreate中执行会导致主线程阻塞,触发ANR或崩溃。
修复:
使用Coroutine将连接操作移至后台线程:
// 确保添加Coroutine依赖,然后修改setupBluetoothConnection方法 private fun setupBluetoothConnection(device: BluetoothDevice) { lifecycleScope.launch(Dispatchers.IO) { try { if (ActivityCompat.checkSelfPermission( this@MainActivity, Manifest.permission.BLUETOOTH_CONNECT ) != PackageManager.PERMISSION_GRANTED ) { return@launch } bluetoothSocket = device.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")) bluetoothSocket?.connect() outputStream = bluetoothSocket?.outputStream withContext(Dispatchers.Main) { Toast.makeText(this@MainActivity, "蓝牙连接成功", Toast.LENGTH_SHORT).show() } } catch (e: Exception) { e.printStackTrace() withContext(Dispatchers.Main) { Toast.makeText(this@MainActivity, "蓝牙连接失败: ${e.message}", Toast.LENGTH_SHORT).show() } } } }
2. 权限请求不完整
Android 13中BLUETOOTH_CONNECT是危险权限,需要动态申请,但当前代码未将其加入权限请求数组,且缺少权限回调处理。
修复:
更新权限请求逻辑,并添加回调:
// 修改onCreate中的权限请求代码 ActivityCompat.requestPermissions( this, arrayOf( Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.BLUETOOTH_CONNECT // 添加该权限 ), requestBluetoothPermission ) // 添加权限回调方法 override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<out String>, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == requestBluetoothPermission) { if (grantResults.all { it == PackageManager.PERMISSION_GRANTED }) { bluetoothAdapter?.let { adapter -> if (adapter.isEnabled) { val device = adapter.getRemoteDevice(esp32DeviceAddress) setupBluetoothConnection(device) } } } else { Toast.makeText(this, "蓝牙权限未授予,无法使用功能", Toast.LENGTH_SHORT).show() } } }
3. 未处理设备未配对/不存在的情况
若目标设备未配对或不存在,connect会抛出未处理的异常,可能导致崩溃。
修复:
添加设备配对状态检查:
// 在setupBluetoothConnection的IO线程中添加检查 if (ActivityCompat.checkSelfPermission( this@MainActivity, Manifest.permission.BLUETOOTH_CONNECT ) != PackageManager.PERMISSION_GRANTED ) { return@launch } if (device.bondState != BluetoothDevice.BOND_BONDED) { withContext(Dispatchers.Main) { Toast.makeText(this@MainActivity, "设备未配对,请先配对", Toast.LENGTH_SHORT).show() } return@launch }
4. 未处理蓝牙适配器为空的情况
部分设备不支持蓝牙,bluetoothAdapter为空时未做处理,可能触发空指针异常。
修复:
在onCreate开头添加空判断:
if (bluetoothAdapter == null) { Toast.makeText(this, "设备不支持蓝牙", Toast.LENGTH_SHORT).show() finish() return }
内容的提问来源于stack exchange,提问作者MiserlyMark
相关产品推荐
相关产品推荐

