Solar Network Push(内部代号戴森老头环 DysonNetwork.Ring)是专门负责推送实时消息的服务,之前还负责过 WebSocket 网关。但是相关业务于许久之前剥离给 Golang 写的 Blade Gateway 去负责了。但是通知部分还是一直都是 .Ring 负责的。
Ring 负责的主要就是两件事,记录和中转。具体就是接受其他 Solar Network 服务的通知请求,记录在数据库中供未来的用户查询。然后向其他系统级的通知服务转发,比如 FCM 和 APNs。
对的,Solar Network 是支持 Android 原生 FCM 推送的。只是因为无形的大手很多人都用不了罢了。因此我们英明的羊发表了 “推送通知一定要能推!” 的伟大论证,下达了开发 CloudySky 的伟大决策。
CloudySky 其实逻辑很简单,就是和服务器保持一个长连接这样就可以知道服务器 What’s happened?! 了。毕竟我们是在用户的允许下保活,所以安卓其实允许我们这么干,只要申请 REQUEST_IGNORE_BATTERY_OPTIMIZATIONS 权限就好了,不用做什么偷偷摸摸的 1px 悬浮窗、无声音频什么的。当然这样会让我们的 App 上不了 Google Play,但是很明显上的了 Google Play 的人也不需要用 CS。
虽然但是这样还是有一些傻逼的国产系统有自己的包名白名单,对白名单外的就不给用了。
同时还顺便写了个 BootReceiver,可以让 SOP 监听器开机自启。
package dev.solsynth.cloudysky.sop
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import dev.solsynth.cloudysky.auth.AuthRepository
class SopBootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
if (intent?.action != Intent.ACTION_BOOT_COMPLETED) return
val appContext = context.applicationContext
val authRepository = AuthRepository(appContext)
val sopRepository = SopRepository(appContext)
val state = sopRepository.currentState()
if (authRepository.authState.value.isAuthorized && state.enabled && state.autoStartOnBoot) {
SopListenerService.start(appContext)
}
}
}
AI 也是真的厉害,让我知道了一堆不知道的事情,包括原来安卓也可以做开机自启。
关于长连接,CS 使用的是 SSE (Server-Side Events) 技术。对于服务器单向推送到客户端是热门的选择,相比维护 WebSocket 链接来说简单很多。其原理就是利用 HTTP 开一个一直不结束的 Stream 流响应,而且不走我们的 WebSocket Gateway 还可以防止用户的在线状态被搞乱掉。但是一直维护长连接还是有缺点的,比如对客户端来说耗电是个问题。
所以我们有增加了个轮询和动态模式。顾名思义,就是定时想服务器查询一下看看有没有什么新的通知。动态模式就是在检查到特定类型的新消息比如说 messages.new 的时候进入长连接模式来获取实时更新。
但是如果你阅读过源码你就会发现我们的通知服务尤其是 messages.new 这类的通知会加上 IsSavable = false 的 Flag,让其不被保存到数据库中。一方面是减少数据库的使用,毕竟消息在消息数据库中存储了一份。第二方面是用户肯定不想要通知界面被消息通知堆满了。
所以因为有这样的问题,我们增加了个 SopReplayBuffer 在服务端。在 SOP 长连接未打开的时候我们会帮助用户先保存 100 条最近的通知,这样下次 CS 客户端在拉取通知的时候就用东西看了。
其实 CloudySky 的名字是做完之后一拍脑袋想出来的,没什么深意。事已至此,赶紧去下载吧!
