如何实现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,提问作者Артем Урманов
相关产品推荐
相关产品推荐

