You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 13:07:05