如何在数据源为BluetoothLE Bound Service时应用Clean Architecture,实现Android应用MVVM分层解耦?
当然可以!把BluetoothLeService的绑定和数据交互逻辑从Activity移到数据层(比如Repository)完全可行,这样就能完美实现展示层与数据层的分离,让Activity只专注于UI更新,ViewModel处理业务逻辑,Repository负责和蓝牙服务的交互。
下面是具体的分层实现方案,结合Hilt做依赖注入来简化组件管理:
第一步:将服务绑定与广播接收移到Repository层
创建一个单例的BluetoothRepository,负责管理蓝牙服务的绑定、解绑,以及广播接收器的注册/注销,并用LiveData对外暴露连接状态和原始数据。
@Singleton class BluetoothRepository @Inject constructor( private val appContext: Context ) { private var bluetoothService: BluetoothLeService? = null private var isServiceBound = false private lateinit var targetDevice: BluetoothDevice // 对外暴露连接状态 private val _connectionState = MutableLiveData<ConnectionState>() val connectionState: LiveData<ConnectionState> = _connectionState // 对外暴露原始蓝牙数据 private val _rawDeviceData = MutableLiveData<ByteArray>() val rawDeviceData: LiveData<ByteArray> = _rawDeviceData private val serviceConnection = object : ServiceConnection { override fun onServiceConnected(componentName: ComponentName?, service: IBinder?) { bluetoothService = (service as BluetoothLeService.LocalBinder).getService() bluetoothService?.let { bluetooth -> if (!bluetooth.initialize()) { _connectionState.postValue(ConnectionState.Error("蓝牙初始化失败")) return } // 连接目标设备 bluetooth.connect(targetDevice) } } override fun onServiceDisconnected(name: ComponentName?) { bluetoothService = null isServiceBound = false _connectionState.postValue(ConnectionState.Disconnected) } } private val gattUpdateReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { when (intent.action) { BluetoothLeService.ACTION_GATT_CONNECTED -> { _connectionState.postValue(ConnectionState.Connected) bluetoothService?.enableNotification() } BluetoothLeService.ACTION_GATT_DISCONNECTED -> { _connectionState.postValue(ConnectionState.Disconnected) } BluetoothLeService.ACTION_MTU_UPDATED -> { bluetoothService?.writeGattCharacteristic() } BluetoothLeService.ACTION_DATA_AVAILABLE -> { intent.getByteArrayExtra("byteArray")?.let { _rawDeviceData.postValue(it) } } } } } // 绑定服务并设置目标设备 fun bindToDevice(device: BluetoothDevice) { if (!isServiceBound) { this.targetDevice = device val intent = Intent(appContext, BluetoothLeService::class.java) appContext.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE) appContext.registerReceiver(gattUpdateReceiver, makeGattIntentFilter()) isServiceBound = true } } // 解绑服务并清理资源 fun unbindService() { if (isServiceBound) { appContext.unbindService(serviceConnection) appContext.unregisterReceiver(gattUpdateReceiver) bluetoothService = null isServiceBound = false } } // 蓝牙状态密封类,方便统一处理 sealed class ConnectionState { object Connected : ConnectionState() object Disconnected : ConnectionState() data class Error(val message: String) : ConnectionState() } private fun makeGattIntentFilter(): IntentFilter { return IntentFilter().apply { addAction(BluetoothLeService.ACTION_GATT_CONNECTED) addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED) addAction(BluetoothLeService.ACTION_MTU_UPDATED) addAction(BluetoothLeService.ACTION_DATA_AVAILABLE) } } }
第二步:ViewModel层处理业务逻辑
在ViewModel中注入BluetoothRepository,负责观察Repository的LiveData,处理数据转换逻辑,并对外暴露可供Activity观察的最终数据状态。
@HiltViewModel class DeviceViewModel @Inject constructor( private val bluetoothRepo: BluetoothRepository, private val dataProcessor: DataProcessor // 注入你的数据处理类 ) : ViewModel() { // 对外暴露处理后的最终数据 val processedDeviceData = bluetoothRepo.rawDeviceData.map { byteArr -> // 把原来Activity中的数据处理逻辑移到这里 if (isLessThan3634 && boardVersion == 2.1) { dataProcessor.processData(byteArr) } else { dataProcessor.processData(byteArr, 1) } }.asLiveData(viewModelScope.coroutineContext) // 直接转发连接状态 val connectionState = bluetoothRepo.connectionState var isLessThan3634 = false var boardVersion = 2.1 fun setupDeviceConnection(device: BluetoothDevice) { bluetoothRepo.bindToDevice(device) } // ViewModel销毁时自动解绑服务,防止内存泄漏 override fun onCleared() { super.onCleared() bluetoothRepo.unbindService() } }
第三步:Activity只负责UI更新
Activity现在只需要初始化UI,获取设备参数,观察ViewModel的LiveData并更新UI即可,完全不需要处理服务绑定和广播逻辑。
@AndroidEntryPoint class DeviceActivity : AppCompatActivity() { private lateinit var binding: ActivityDeviceBinding private val viewModel: DeviceViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) super.onCreate(savedInstanceState) binding = ActivityDeviceBinding.inflate(layoutInflater) setContentView(binding.root) setSupportActionBar(binding.myToolbar) // 初始化UI标题 intent.extras?.getString("btDeviceName")?.let { binding.deviceTitle.text = it } setupActionBar() // 获取传入的蓝牙设备(注意要正确传递Parcelable类型的BluetoothDevice) val targetDevice = intent.getParcelableExtra<BluetoothDevice>("btDevice")!! viewModel.setupDeviceConnection(targetDevice) // 观察连接状态,更新UI viewModel.connectionState.observe(this) { state -> when (state) { is BluetoothRepository.ConnectionState.Connected -> { updateConnectionState(getString(R.string.connected)) binding.progressBarCyclic.visibility = View.GONE } is BluetoothRepository.ConnectionState.Disconnected -> { updateConnectionState(getString(R.string.disconnected)) leaveActivity() binding.progressBarCyclic.visibility = View.GONE } is BluetoothRepository.ConnectionState.Error -> { Log.e("Bluetooth", state.message) finish() } } } // 观察处理后的数据,更新RecyclerView viewModel.processedDeviceData.observe(this) { processedData -> binding.recyclerviewDevices.visibility = View.VISIBLE // 这里更新RecyclerView的Adapter即可 // yourAdapter.submitList(processedData) } } // 保留原来的setupActionBar、updateConnectionState等UI相关方法 private fun setupActionBar() { /* ... */ } private fun updateConnectionState(state: String) { /* ... */ } private fun leaveActivity() { /* ... */ } }
关键注意事项
- 内存泄漏预防:使用Application Context绑定服务,避免持有Activity引用;在ViewModel的
onCleared()方法中主动解绑服务和注销广播接收器。 - 依赖注入:通过Hilt注入Repository、ViewModel和DataProcessor,避免手动创建实例导致的依赖混乱和内存泄漏。
- 职责划分:Activity只做UI展示,ViewModel处理业务逻辑,Repository处理数据层交互,完全符合Clean Architecture和MVVM的分层原则。
内容的提问来源于stack exchange,提问作者Dylon Jaynes
相关产品推荐
相关产品推荐

