后端被拉来做 Android app,请教各位 Android 大佬

97 天前
 skai0dev

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

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

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

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

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

另外想问一下,如果通过 websocket 传递数据,是只发送相关的事件(对消息的封装),然后客户端根据事件在具体发送 HTTP 请求获取数据呢?还是直接通过 websocket 发送事件的时候附带数据呢?

3761 次点击
所在节点    Android
11 条回复
hohoho
97 天前
不是大佬,说下个人的感觉:

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

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

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

不过看你的描述,只有一个 activity ,而且后端被拉来做 app ,看起来是个比较简单的玩意,所以就用 eventbus 吧~~写起来贼简单
ihgoo
97 天前
你可以确保 app 只有一个 activity 的情况下,也可以把代码都堆 activity 里哦 手动狗头
okakuyang
96 天前
都单 Activity 了,当然是 ws 代码放在 activity 里就行了,接收到任何指令,转发给各个 fragment 就行了,每个 fragment 都有 tag ,根据 tag 发给不同的 fragment ,建议 ws 只传指令就行了,传大数据徒增烦恼。
mtdhllf
96 天前
单 Act ,Service 就用来保活,ws 用一个单例类来相实现即可
fairytale110
96 天前
用 websock 框架写自己封个单例,vm 处理业务,livedata 刷新 ui ,完事。
HtPM
92 天前
不需要追求最佳实践,没有意义
janus77
91 天前
弄个单例消息容器不就行了,service 收到的数据更新到消息容器里面,然后用 flow 也好用 callback 也好,viewmodel 里面去获取数据的更新,剩下就好办了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1064999

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX