前情提要: 服务器和客户端( Android )通过 websocket 通信,接收一些服务器的指令或者发送一些数据,比如服务器发送关机指令客户端关机之类的。
目前客户端 app 是采用的单 activity 的结构,通过 fragment 和 navigation 做导航,采用了 mvvm 架构模式,也用了协程了。
我本来想着写一个 service 然后绑定到 activity 上,负责处理各种消息,但是有一些消息需要与 UI 有交互,所以好像不行。
也想过用 callbackFlow 然后在 viewmodel 里面处理,然后在 fragment 中显示,但是感觉好像也不是很好。
所以就想问一下这种场景的最佳实践是什么?各位大佬能说说思路吗?
另外想问一下,如果通过 websocket 传递数据,是只发送相关的事件(对消息的封装),然后客户端根据事件在具体发送 HTTP 请求获取数据呢?还是直接通过 websocket 发送事件的时候附带数据呢?
1
hohoho 134 天前 1
不是大佬,说下个人的感觉:
楼主的需求跟 UI 和交互没有太大关系,把 ws 想象成 event bus ,监听服务端事件,获取事件数据(数据小建议放到 ws 中)。 监听事件的逻辑适合放到 view model 里,毕竟是一种数据来源,也可能需要额外的逻辑处理成新的数据。 |
2
Parva 134 天前 1
Service 处理长连接是 ok 的,剩下的就是怎么让 Activity 与这个 Service 通信,至于 activity 那边用什么架构是另一回事吧。
|
3
murmurkerman 134 天前 1
最重要的是数据要和 UI 解耦,无论你用 Android Service 还是普通类。和后端的 MVC 类似,Dao 、Connection Pool 是全局对象,Controller 只是用于处理用户交互,更新用户 UI 状态。你需要将自己的 ws 业务逻辑抽象到服务层中,viewmodel 负责从服务层获取数据,处理交互事件,service 层管理连接,发送接收处理消息。
至于是否使用 Android Service ,取决于你的应用是否需要在应用界面后继续运行,你还希望服务继续运行直到系统终止服务。一般情况下,例如媒体播放、录音、推送等及时性要起高的需要放到服务中。 一般只需要用普通的类来管理, 至于 ws 数据传输,ws 一般只用于同步状态: 1. 例如多人协作文档,需要同步输入位置,锁定编辑区域。 2. 大型二进制,例如文件、图片、音频,建议分开。 3. 实时翻译等,短时低延迟要求等,使用 ws 传输数据。 下面的示例中将没有处理消息放到了一个 ShardFlow 中,UI 收集这些数据,YourService 负责管理连接创建、关闭,ServiceConnection 处理消息通讯。 class YourApplication : Application() { lateinit var yourService: YourService override fun onCreate() { super.onCreate() yourService = YourService() } } // 这里用 Application 类管理全局依赖 val Context.application: YourApplication get() = applicationContext as YourApplication val Context.yourService: YourService get() = application.yourService // 服务层,用于管理链接和处理数据 class YourService { /** * 一个简单的没有任何附加逻辑的 WebSocket 连接 Handler ,只是把消息缓存到一个 Flow 中 * 你可以加上你自己的逻辑,比如消息解析,消息处理等,链接重试之类的 */ class ServiceConnection { internal var websocket: WebSocket? = null private val messageBuffer = MutableSharedFlow<String>( replay = 0, extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST ) val receivedMessages = messageBuffer.asSharedFlow() fun onConnected(websocket: WebSocket) { // ... } fun onMessageReceived(message: String) { // ... } fun disconnect(code: Int, reason: String, cause: Throwable? = null) { // ... } fun sendMessage(message: String) { // ... } } private val client = OkHttpClient() // 一个简单的 WebSocket 连接缓存,只保留一个连接 private var activeConnection: ServiceConnection? = null @Synchronized fun connect(): Result<ServiceConnection> { if (activeConnection != null) { Result.success(activeConnection!!) } return connectChecked().onSuccess { activeConnection = it } } private fun connectChecked(): Result<ServiceConnection> { val request = Request.Builder() .url("wss://echo.websocket.org") .build() val connection = ServiceConnection() return kotlin.runCatching { client.newWebSocket(request, object : WebSocketListener() { override fun onOpen(webSocket: WebSocket, response: Response) { connection.onConnected(webSocket) } override fun onMessage(webSocket: WebSocket, text: String) { connection.onMessageReceived(text) } override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { connection.disconnect(code, reason) } override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { connection.disconnect(0, t.message ?: "Unknown error", t) } }) .apply { connection.websocket = this } connection } } } // 处理 UI 状态、用户事件,和与服务层拉取数据 class YourViewModel( application: Application, ) : AndroidViewModel(application) { private val yourService: YourService get() = getApplication<Application>().yourService private var connection: YourService.ServiceConnection? = null fun connect() { viewModelScope.launch { connection = yourService.connect() .onSuccess { it.receivedMessages.collect(::onReceiveMessage) }.onFailure { // handle error }.getOrNull() } } private fun onReceiveMessage( message: String ) { // update ui state } } class YourUi: Fragment() { private val viewModel: YourViewModel by viewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) } override fun onStart() { super.onStart() viewModel.connect() // connect to service } } |
4
xiangyuecn 134 天前
换我写 我就一个 XXXActivity.java 所有代码都写里面
|
5
ihgoo 134 天前 1
你说的几种方案都可以。
用服务的思路是对的哦。 与 UI 有交互,传统办法就是用回调接口和 viewmodel 中的 ws 做交互,写各种 callback ,属于中规中矩的办法。 比较取巧的办法可以用 eventbus 来做交互,优点是解耦的很彻底,缺点则是不怎么适合写大量不同的 event 事件,event 类型太多会搞的比较晕。 不过看你的描述,只有一个 activity ,而且后端被拉来做 app ,看起来是个比较简单的玩意,所以就用 eventbus 吧~~写起来贼简单 |
6
ihgoo 134 天前
你可以确保 app 只有一个 activity 的情况下,也可以把代码都堆 activity 里哦 手动狗头
|
7
okakuyang 134 天前
都单 Activity 了,当然是 ws 代码放在 activity 里就行了,接收到任何指令,转发给各个 fragment 就行了,每个 fragment 都有 tag ,根据 tag 发给不同的 fragment ,建议 ws 只传指令就行了,传大数据徒增烦恼。
|
8
mtdhllf 133 天前
单 Act ,Service 就用来保活,ws 用一个单例类来相实现即可
|
9
fairytale110 133 天前 via Android
用 websock 框架写自己封个单例,vm 处理业务,livedata 刷新 ui ,完事。
|
10
HtPM 129 天前
不需要追求最佳实践,没有意义
|
11
janus77 128 天前
弄个单例消息容器不就行了,service 收到的数据更新到消息容器里面,然后用 flow 也好用 callback 也好,viewmodel 里面去获取数据的更新,剩下就好办了
|