后端被拉来做 Android app,请教各位 Android 大佬 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
JUSTIGNOREME
V2EX    Android

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

  •  
  •   JUSTIGNOREME 2024-08-14 18:01:30 +08:00 7059 次点击
    这是一个创建于 425 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

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

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

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

    11 条回复    2024-08-20 17:21:40 +08:00
    hohoho
        1
    hohoho  
       2024-08-14 18:29:33 +08:00   1
    不是大佬,说下个人的感觉:

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

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

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

    不过看你的描述,只有一个 activity ,而且后端被拉来做 app ,看起来是个比较简单的玩意,所以就用 eventbus 吧~~写起来贼简单
    ihgoo
        6
    ihgoo  
       2024-08-14 22:19:36 +08:00
    你可以确保 app 只有一个 activity 的情况下,也可以把代码都堆 activity 里哦 手动狗头
    okakuyang
        7
    okakuyang  
       2024-08-14 23:36:14 +08:00
    都单 Activity 了,当然是 ws 代码放在 activity 里就行了,接收到任何指令,转发给各个 fragment 就行了,每个 fragment 都有 tag ,根据 tag 发给不同的 fragment ,建议 ws 只传指令就行了,传大数据徒增烦恼。
    mtdhllf
        8
    mtdhllf  
       2024-08-15 14:44:30 +08:00
    单 Act ,Service 就用来保活,ws 用一个单例类来相实现即可
    fairytale110
        9
    fairytale110  
       202408-15 18:29:12 +08:00 via Android
    用 websock 框架写自己封个单例,vm 处理业务,livedata 刷新 ui ,完事。
    HtPM
        10
    HtPM  
       2024-08-19 17:03:13 +08:00
    不需要追求最佳实践,没有意义
    rb6221
        11
    rb6221  
       2024-08-20 17:21:40 +08:00
    弄个单例消息容器不就行了,service 收到的数据更新到消息容器里面,然后用 flow 也好用 callback 也好,viewmodel 里面去获取数据的更新,剩下就好办了
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3103 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 23ms UTC 12:34 PVG 20:34 LAX 05:34 JFK 08:34
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86