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

如何在数据源为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() { /* ... */ }
}

关键注意事项

  1. 内存泄漏预防:使用Application Context绑定服务,避免持有Activity引用;在ViewModel的onCleared()方法中主动解绑服务和注销广播接收器。
  2. 依赖注入:通过Hilt注入Repository、ViewModel和DataProcessor,避免手动创建实例导致的依赖混乱和内存泄漏。
  3. 职责划分:Activity只做UI展示,ViewModel处理业务逻辑,Repository处理数据层交互,完全符合Clean Architecture和MVVM的分层原则。

内容的提问来源于stack exchange,提问作者Dylon Jaynes

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 16:02:46