前情提要:
服务器和客户端( Android )通过 websocket 通信,接收一些服务器的指令或者发送一些数据,比如服务器发送关机指令客户端关机之类的。

目前客户端 app 是采用的单 activity 的结构,通过 fragment 和 navigation 做导航,采用了 mvvm 架构模式,也用了协程了。

我本来想着写一个 service 然后绑定到 activity 上,负责处理各种消息,但是有一些消息需要与 UI 有交互,所以好像不行。

也想过用 callbackFlow 然后在 viewmodel 里面处理,然后在 fragment 中显示,但是感觉好像也不是很好。

所以就想问一下这种场景的最佳实践是什么?各位大佬能说说思路吗?

另外想问一下,如果通过 websocket 传递数据,是只发送相关的事件(对消息的封装),然后客户端根据事件在具体发送 HTTP 请求获取数据呢?还是直接通过 websocket 发送事件的时候附带数据呢?
举报· 148 次点击
登录 注册 站外分享
10 条回复  
hohoho 小成 2024-8-14 18:29:33
不是大佬,说下个人的感觉:

楼主的需求跟 UI 和交互没有太大关系,把 ws 想象成 event bus ,监听服务端事件,获取事件数据(数据小建议放到 ws 中)。

监听事件的逻辑适合放到 view model 里,毕竟是一种数据来源,也可能需要额外的逻辑处理成新的数据。
Parva 小成 2024-8-14 19:53:51
Service 处理长连接是 ok 的,剩下的就是怎么让 Activity 与这个 Service 通信,至于 activity 那边用什么架构是另一回事吧。
murmurkerman 小成 2024-8-14 21:39:05
最重要的是数据要和 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
    }
   
}
xiangyuecn 初学 2024-8-14 22:05:48
换我写 我就一个 XXXActivity.java 所有代码都写里面
ihgoo 小成 2024-8-14 22:18:29
你说的几种方案都可以。

用服务的思路是对的哦。
与 UI 有交互,传统办法就是用回调接口和 viewmodel 中的 ws 做交互,写各种 callback ,属于中规中矩的办法。
比较取巧的办法可以用 eventbus 来做交互,优点是解耦的很彻底,缺点则是不怎么适合写大量不同的 event 事件,event 类型太多会搞的比较晕。

不过看你的描述,只有一个 activity ,而且后端被拉来做 app ,看起来是个比较简单的玩意,所以就用 eventbus 吧~~写起来贼简单
okakuyang 小成 2024-8-14 23:36:14
都单 Activity 了,当然是 ws 代码放在 activity 里就行了,接收到任何指令,转发给各个 fragment 就行了,每个 fragment 都有 tag ,根据 tag 发给不同的 fragment ,建议 ws 只传指令就行了,传大数据徒增烦恼。
mtdhllf 小成 2024-8-15 14:44:30
单 Act ,Service 就用来保活,ws 用一个单例类来相实现即可
fairytale110 小成 2024-8-15 18:29:12
用 websock 框架写自己封个单例,vm 处理业务,livedata 刷新 ui ,完事。
HtPM 小成 2024-8-19 17:03:13
不需要追求最佳实践,没有意义
12下一页
返回顶部