在当今移动互联网时代,蓝牙技术作为一种成熟且低功耗的短距离无线通信手段,依然在我们的日常生活中扮演着至关重要的角色。无论是无线耳机、智能手环,还是车载系统,蓝牙都为设备间的数据传输提供了便捷的通道。
作为 Android 开发者,我们经常需要开发涉及设备间通信的应用。你可能已经遇到过这样的需求:不仅仅是要连接到已配对的设备,还需要让你的手机被周围新的、未配对的设备“看到”并发现。这就是我们常说的“蓝牙可发现模式”(Bluetooth Discoverability)。
在这篇文章中,我们将摒弃枯燥的理论堆砌,像老朋友交流一样,深入探讨 Android 蓝牙开发的实战细节。我们将一起学习如何通过代码控制蓝牙的开启与关闭,以及最核心的部分——如何通过编程方式让设备进入可发现模式,从而使其他设备能够搜索并连接到它。
前置知识:理解蓝牙架构与可发现性
在动手敲代码之前,让我们先花点时间理解底层的运作机制,这将帮助我们更好地编写代码。
#### 1. 蓝牙适配器:总指挥官
在 Android 的 API 架构中,BluetoothAdapter 是我们进行所有蓝牙操作的入口。你可以把它想象成设备的“蓝牙指挥官”。无论是启用/关闭蓝牙,还是开始扫描周边设备,亦或是让本机变得可被发现,都需要通过它来调用。
获取 INLINECODEd5f97fe8 的标准代码通常如下所示(建议在 INLINECODEca884983 或初始化阶段调用):
// 获取默认的蓝牙适配器
val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()
// 检查设备是否支持蓝牙
if (bluetoothAdapter == null) {
// 设备不支持蓝牙,此时应给用户适当的提示或禁用相关功能
Log.d("BluetoothDebug", "该设备不支持蓝牙功能")
}
#### 2. 什么是“可发现模式”?
默认情况下,为了保护用户隐私和节省电量,Android 设备开启蓝牙后,虽然处于工作状态,但它是“隐身”的。这意味着它只能与之前已经配对过的设备通信。
如果你想让你的手机出现在隔壁朋友的“可用设备”列表中,你必须显式地开启“可发现模式”。在这种模式下,设备会持续广播自身的存在信息,告诉周围的设备:“我在这里,可以连接我!”。
#### 3. 权限的艺术:从 Android 12 到现在的变化
这里有一个非常关键的实战细节。随着 Android 系统的迭代,蓝牙权限的管理变得越来越严格。
- Android 12 (API Level 31) 之前:我们通常只需要申请 INLINECODE1d4e2288 和 INLINECODEc06de8e7 权限。
- Android 12 及以后:系统引入了新的运行时权限组。如果你需要在应用中进行扫描(Scan)或连接(Connect),你需要申请 INLINECODE5918c875 和 INLINECODEb09f1d5e 权限。虽然仅仅开启蓝牙或设为可发现主要依赖旧权限,但为了确保应用的兼容性和未来扩展性,了解这一点至关重要。
此外,INLINECODE7eb61be1 或 INLINECODEb388bdbf 权限也是必需的(直到 Android 12)。这是因为,通过蓝牙信号强度理论上可以推断出用户的物理位置,所以 Google 将此归类为敏感权限。
项目实战:构建一个蓝牙控制工具
接下来,让我们通过构建一个完整的示例应用来巩固这些知识。我们的目标是创建一个简单的界面,包含三个核心功能:开启蓝牙、设为可发现和关闭蓝牙。
我们将使用 Kotlin 语言,这是目前 Android 开发的主流首选。
#### 第一步:配置 AndroidManifest.xml
首先,我们需要向系统声明我们需要使用这些硬件功能。请打开 AndroidManifest.xml 文件并添加以下权限。
<!-- -->
实用见解:请注意 INLINECODE75cfa443 这个属性。如果你的应用可以在没有蓝牙的设备上运行(比如平板版或仅 WiFi 版),应该设置为 INLINECODE4b762c03,然后在代码中动态检查蓝牙是否可用。
#### 第二步:设计用户界面
为了与我们的代码逻辑交互,我们需要一个简洁的 UI。我们将使用三个按钮。下面是 activity_main.xml 的布局代码。
#### 第三步:编写核心逻辑
这是最关键的部分。我们需要处理按钮的点击事件,并调用系统 API 来完成相应的操作。
为了实现“可发现”功能,我们需要用到 INLINECODE9be5c449 机制。我们不会自己手动去开启广播,而是通过发送一个 INLINECODE7699be91 动作给系统,系统会弹出标准的对话框询问用户是否同意开启可发现模式(通常默认时间为 120 秒)。
以下是 MainActivity.kt 的完整实现。我在代码中添加了详细的注释,解释了每一步的逻辑。
package com.example.bluetoothapp
import android.bluetooth.BluetoothAdapter
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
const val REQUEST_ENABLE_BT = 1 // 请求码:开启蓝牙
const val REQUEST_DISCOVERABLE_BT = 2 // 请求码:设为可发现
class MainActivity : AppCompatActivity() {
// 声明蓝牙适配器
private var bluetoothAdapter: BluetoothAdapter? = null
// UI 组件
private lateinit var btnOn: Button
private lateinit var btnDiscoverable: Button
private lateinit var btnOff: Button
private lateinit var tvStatus: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 1. 初始化视图
btnOn = findViewById(R.id.btnOn)
btnDiscoverable = findViewById(R.id.btnDiscoverable)
btnOff = findViewById(R.id.btnOff)
tvStatus = findViewById(R.id.tvStatus)
// 2. 获取蓝牙适配器
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
// 检查设备是否支持蓝牙
if (bluetoothAdapter == null) {
tvStatus.text = "状态: 该设备不支持蓝牙"
btnOn.isEnabled = false
btnDiscoverable.isEnabled = false
btnOff.isEnabled = false
return
}
updateStatus()
// 3. 设置按钮点击监听器
// 开启蓝牙
btnOn.setOnClickListener {
if (bluetoothAdapter?.isEnabled == false) {
// 调用系统 Intent 开启蓝牙,这是一个异步操作,结果会返回到 onActivityResult
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)
} else {
Toast.makeText(this, "蓝牙已经开启", Toast.LENGTH_SHORT).show()
}
}
// 设为可发现
btnDiscoverable.setOnClickListener {
// 必须先确保蓝牙是开启的
if (bluetoothAdapter?.isEnabled == false) {
Toast.makeText(this, "请先开启蓝牙!", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
// 关键代码:创建 Intent 请求可发现模式
// 这里的 300 是可发现持续时间(秒),最大可以是 300 秒(5分钟)
// 如果设为 0,则使用系统默认时间(通常是 120 秒)
val discoverableIntent = Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE).apply {
putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300)
}
startActivityForResult(discoverableIntent, REQUEST_DISCOVERABLE_BT)
}
// 关闭蓝牙
btnOff.setOnClickListener {
if (bluetoothAdapter?.isEnabled == true) {
// 直接调用 API 关闭蓝牙,不需要 Intent 权限弹窗
// 注意:这需要 BLUETOOTH_ADMIN 权限
bluetoothAdapter?.disable()
tvStatus.text = "状态: 蓝牙已关闭"
Toast.makeText(this, "蓝牙已关闭", Toast.LENGTH_SHORT).show()
updateStatus()
}
}
}
// 更新 UI 状态显示
private fun updateStatus() {
val state = when {
bluetoothAdapter == null -> "不支持"
bluetoothAdapter?.isEnabled == true -> "已开启"
else -> "已关闭"
}
tvStatus.text = "当前蓝牙状态: $state"
// 根据状态动态启用/禁用按钮
btnDiscoverable.isEnabled = (bluetoothAdapter?.isEnabled == true)
btnOff.isEnabled = (bluetoothAdapter?.isEnabled == true)
}
// 处理开启蓝牙和设为可发现的回调结果
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQUEST_ENABLE_BT -> {
if (resultCode == RESULT_OK) {
// 用户同意开启蓝牙
Toast.makeText(this, "蓝牙已成功开启", Toast.LENGTH_SHORT).show()
} else {
// 用户拒绝开启蓝牙
Toast.makeText(this, "蓝牙开启被拒绝", Toast.LENGTH_SHORT).show()
}
updateStatus()
}
REQUEST_DISCOVERABLE_BT -> {
if (resultCode == RESULT_OK) {
// resultCode 实际上包含了可发现的持续时间,或者 RESULT_CANCELED
// 只要弹窗确认了,就会进入可发现模式
Toast.makeText(this, "设备现在可被其他设备发现", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "取消可发现模式", Toast.LENGTH_SHORT).show()
}
}
}
}
override fun onResume() {
super.onResume()
// 每次回到界面刷新状态,防止用户在系统设置中修改了蓝牙状态
updateStatus()
}
}
深入代码解析与最佳实践
通过上面的代码,我们已经实现了基础功能。但作为专业的开发者,我们需要深入了解背后的机制以及如何应对复杂情况。
#### 1. 为什么要用 startActivityForResult?
你可能注意到了,开启蓝牙和设置可发现模式都需要通过 Intent 跳转到一个系统对话框。这是因为这些操作涉及用户隐私(无线广播)和硬件状态改变,属于敏感操作。Android 系统强制要求必须由用户明确点击“同意”,防止应用在后台偷偷开启蓝牙或扫描用户位置。
#### 2. 监听蓝牙状态变化的广播
我们的代码中使用了 INLINECODE0405120a 来手动刷新状态。但在更复杂的应用中,你可能需要实时监听蓝牙的状态变化(例如用户在通知栏下拉关闭了蓝牙)。这时,我们可以使用 INLINECODE57f681be。
这是一个最佳实践的代码片段,展示如何注册广播接收器来监听蓝牙开关事件:
// 定义一个 BroadcastReceiver
private val bluetoothStateReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val action = intent?.action
if (action == BluetoothAdapter.ACTION_STATE_CHANGED) {
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)
when (state) {
BluetoothAdapter.STATE_OFF -> {
Log.d("BluetoothState", "蓝牙已关闭")
// 更新 UI:禁用按钮
}
BluetoothAdapter.STATE_TURNING_OFF -> {
Log.d("BluetoothState", "蓝牙正在关闭...")
}
BluetoothAdapter.STATE_ON -> {
Log.d("BluetoothState", "蓝牙已开启")
// 更新 UI:启用按钮
}
BluetoothAdapter.STATE_TURNING_ON -> {
Log.d("BluetoothState", "蓝牙正在开启...")
}
}
}
}
}
// 在 onCreate 或 onResume 中注册过滤器
// 注意:动态注册广播需要在 onDestroy 或 onPause 中取消注册
private fun registerBluetoothReceiver() {
val filter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
registerReceiver(bluetoothStateReceiver, filter)
}
#### 3. 常见错误与解决方案
- SecurityException:如果你在 Android 12+ 的设备上没有申请 INLINECODE759cf2bc 权限就调用 INLINECODE34ed518d 或
enable(),应用会崩溃。请务必确保针对高版本系统做了权限适配。 - 可发现模式不生效:确保在调用
ACTION_REQUEST_DISCOVERABLE之前,蓝牙已经完全开启。如果蓝牙处于关闭状态,Intent 会直接失败或者只开启蓝牙而不进入可发现模式。
#### 4. 优化用户体验
在实际产品中,当你的应用需要开启可发现模式时,最好给用户一些上下文提示。比如:“为了接收文件,请开启蓝牙可发现模式”。不要在用户毫无防备的情况下突然弹出一个系统对话框。
总结与展望
在这篇文章中,我们完整地走了一遍 Android 蓝牙开发的流程,从基础概念的梳理,到权限配置,再到具体的代码实现。我们学习了如何利用 INLINECODE7560f619 和 INLINECODE995e9b2e 来控制设备的硬件行为。
要记住的核心要点是:
- 权限是基石:时刻关注 Android 版本更新带来的权限变化,特别是 Android 12 的新权限组。
- 状态管理是关键:蓝牙是异步操作的,状态(开启、关闭、扫描)都会通过广播或回调通知,妥善处理这些状态变化是保证应用稳定性的关键。
- 用户体验至上:涉及硬件弹窗的操作(如开启可发现)一定要给用户足够的解释。
掌握了这些内容,你就已经具备了开发点对点蓝牙通信应用的基础。下一步,你可以尝试结合 INLINECODE53b175ed 和 INLINECODE22178e3f 来实现真正的数据传输,比如开发一个简易的“蓝牙聊天室”应用。祝你在开发探索的道路上玩得开心!