V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
skai0dev
V2EX  ›  Android

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

  •  
  •   skai0dev · 135 天前 · 4467 次点击
    这是一个创建于 135 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

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

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

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

    11 条回复    2024-08-20 17:21:40 +08:00
    hohoho
        1
    hohoho  
       135 天前   ❤️ 1
    不是大佬,说下个人的感觉:

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

    监听事件的逻辑适合放到 view model 里,毕竟是一种数据来源,也可能需要额外的逻辑处理成新的数据。
    Parva
        2
    Parva  
       135 天前   ❤️ 1
    Service 处理长连接是 ok 的,剩下的就是怎么让 Activity 与这个 Service 通信,至于 activity 那边用什么架构是另一回事吧。
    murmurkerman
        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
    }

    }
    xiangyuecn
        4
    xiangyuecn  
       134 天前
    换我写 我就一个 XXXActivity.java 所有代码都写里面
    ihgoo
        5
    ihgoo  
       134 天前   ❤️ 1
    你说的几种方案都可以。

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

    不过看你的描述,只有一个 activity ,而且后端被拉来做 app ,看起来是个比较简单的玩意,所以就用 eventbus 吧~~写起来贼简单
    ihgoo
        6
    ihgoo  
       134 天前
    你可以确保 app 只有一个 activity 的情况下,也可以把代码都堆 activity 里哦 手动狗头
    okakuyang
        7
    okakuyang  
       134 天前
    都单 Activity 了,当然是 ws 代码放在 activity 里就行了,接收到任何指令,转发给各个 fragment 就行了,每个 fragment 都有 tag ,根据 tag 发给不同的 fragment ,建议 ws 只传指令就行了,传大数据徒增烦恼。
    mtdhllf
        8
    mtdhllf  
       134 天前
    单 Act ,Service 就用来保活,ws 用一个单例类来相实现即可
    fairytale110
        9
    fairytale110  
       134 天前 via Android
    用 websock 框架写自己封个单例,vm 处理业务,livedata 刷新 ui ,完事。
    HtPM
        10
    HtPM  
       130 天前
    不需要追求最佳实践,没有意义
    janus77
        11
    janus77  
       129 天前
    弄个单例消息容器不就行了,service 收到的数据更新到消息容器里面,然后用 flow 也好用 callback 也好,viewmodel 里面去获取数据的更新,剩下就好办了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2735 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 12:31 · PVG 20:31 · LAX 04:31 · JFK 07:31
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.