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

如何实现Android多Fragment共享同一蓝牙连接?

解决方案

问题根源在于你当前的实现是每个Fragment单独创建连接,且连接的Listener绑定到单个Fragment,切换Fragment时要么重新创建连接导致旧连接断开,要么Fragment销毁后Listener失效,连接也可能被意外终止。以下是具体解决步骤:

1. 用ViewModel托管BluetoothController

将BluetoothController的实例交由与Activity生命周期绑定的ViewModel管理,确保Fragment切换时实例不会被销毁,连接保持稳定。

import androidx.lifecycle.ViewModel
import android.bluetooth.BluetoothAdapter

class BluetoothViewModel(private val adapter: BluetoothAdapter) : ViewModel() {
    val bluetoothController = BluetoothController(adapter)

    override fun onCleared() {
        super.onCleared()
        // Activity销毁时关闭连接
        bluetoothController.closeConnection()
    }
}

2. 改造BluetoothController支持多Listener

修改原有的BluetoothController,允许多个Fragment注册消息监听,而不是每次连接替换Listener:

import android.bluetooth.BluetoothAdapter
import java.util.concurrent.CopyOnWriteArrayList

class BluetoothController(private val adapter: BluetoothAdapter) {
    private var connectThread: ConnectThread? = null
    // 用线程安全的集合存储多个Listener
    private val listeners = CopyOnWriteArrayList<Listener>()

    fun connect(mac: String) {
        if (adapter.isEnabled && mac.isNotEmpty() && connectThread == null) {
            val device = adapter.getRemoteDevice(mac)
            connectThread = ConnectThread(device, object : Listener {
                override fun onReceive(message: String) {
                    // 收到消息时通知所有注册的Listener
                    listeners.forEach { it.onReceive(message) }
                }
            })
            connectThread?.start()
        }
    }

    fun sendMessage(message: String) {
        connectThread?.sendMessage(message)
    }

    fun closeConnection() {
        connectThread?.closeConnection()
        connectThread = null
        listeners.clear()
    }

    // 注册Listener
    fun registerListener(listener: Listener) {
        if (!listeners.contains(listener)) {
            listeners.add(listener)
        }
    }

    // 注销Listener
    fun unregisterListener(listener: Listener) {
        listeners.remove(listener)
    }

    // 检查当前是否已连接
    fun isConnected(): Boolean {
        return connectThread?.isConnected ?: false
    }

    companion object {
        const val BLUETOOTH_CONNECTED = "Bluetooth соединение подключено"
        const val BLUETOOTH_NO_CONNECTED = "Bluetooth соединение отключено"
    }

    interface Listener {
        fun onReceive(message: String)
    }
}

注意:需要在你的ConnectThread中添加isConnected属性,用于判断当前连接状态。

3. Fragment中统一使用ViewModel的BluetoothController

在每个需要使用蓝牙的Fragment中,获取同一个ViewModel实例,在生命周期方法中注册/注销Listener,连接按钮点击时先检查连接状态:

MainFragment示例:

class MainFragment : Fragment(), BluetoothController.Listener {
    private lateinit var bluetoothViewModel: BluetoothViewModel
    private val mac: String? = "你的设备MAC地址"

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        // 初始化ViewModel
        bluetoothViewModel = ViewModelProvider(requireActivity())[BluetoothViewModel::class.java]
        return inflater.inflate(R.layout.fragment_main, container, false)
    }

    override fun onStart() {
        super.onStart()
        // 注册Listener
        bluetoothViewModel.bluetoothController.registerListener(this)
    }

    override fun onStop() {
        super.onStop()
        // 注销Listener,避免内存泄漏
        bluetoothViewModel.bluetoothController.unregisterListener(this)
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return when (item.itemId) {
            R.id.id_bt_connect -> {
                val controller = bluetoothViewModel.bluetoothController
                if (!controller.isConnected()) {
                    controller.connect(mac ?: "")
                    Toast.makeText(requireActivity(), "Подключение к маяку", Toast.LENGTH_SHORT).show()
                } else {
                    Toast.makeText(requireActivity(), "Уже подключено", Toast.LENGTH_SHORT).show()
                }
                true
            }
            else -> super.onOptionsItemSelected(item)
        }
    }

    override fun onReceive(message: String) {
        // 处理收到的蓝牙消息
    }
}

ColorGlow Fragment示例:

class ColorGlow : Fragment(), BluetoothController.Listener {
    private lateinit var bluetoothViewModel: BluetoothViewModel
    private val mac: String? = "你的设备MAC地址"

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        bluetoothViewModel = ViewModelProvider(requireActivity())[BluetoothViewModel::class.java]
        return inflater.inflate(R.layout.fragment_color_glow, container, false)
    }

    override fun onStart() {
        super.onStart()
        bluetoothViewModel.bluetoothController.registerListener(this)
    }

    override fun onStop() {
        super.onStop()
        bluetoothViewModel.bluetoothController.unregisterListener(this)
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return when (item.itemId) {
            R.id.id_bt_connect -> {
                val controller = bluetoothViewModel.bluetoothController
                if (!controller.isConnected()) {
                    controller.connect(mac ?: "")
                    Toast.makeText(requireActivity(), "Подключение к маяку", Toast.LENGTH_SHORT).show()
                } else {
                    Toast.makeText(requireActivity(), "Уже подключено", Toast.LENGTH_SHORT).show()
                }
                true
            }
            else -> super.onOptionsItemSelected(item)
        }
    }

    override fun onReceive(message: String) {
        // 处理收到的蓝牙消息
    }
}

关键说明

  • ViewModel与Activity生命周期绑定,所以所有属于该Activity的Fragment都会共享同一个ViewModel实例,进而共享同一个BluetoothController和连接。
  • 多Listener机制让每个Fragment都能独立接收蓝牙消息,无需单独创建连接。
  • 在Fragment的onStart/onStop中注册/注销Listener,确保Fragment不可见时不再接收消息,同时避免内存泄漏。
  • 连接前先检查isConnected状态,避免重复创建连接导致旧连接断开。

内容的提问来源于stack exchange,提问作者Артем Урманов

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 19:47:26