Element Android — Matrix 协议官方 Android 客户端(Kotlin + Realm)
是什么
Element Android 是 Matrix 协议的官方 Android 客户端,由 Element 公司维护,全 Kotlin 编写。日常类比:把”邮箱客户端”改成”聊天客户端”——你在手机上点开 Element 看消息,它在背后向你自己(或别人架的)Matrix homeserver 不停拉新事件,再把端到端加密的密文解出来摆给你看。
Matrix 是去中心化协议:每家服务器(Synapse / Dendrite)跑一份,用户消息在不同服务器之间联邦同步。Element Android 不关心联邦怎么走——那是服务端的事——它只跟自己登录的那台 homeserver 说话,剩下的它当作”远端用户”看待。
最小用法:从 Google Play 装一个 Element,登录 matrix.org(或自架的 homeserver),就能加房间、收发加密消息、视频通话(走 Jitsi)。
// matrix-android-sdk2 的核心入口大致长这样val matrix = Matrix.getInstance(context)val session = matrix.authenticationService() .getLoginWizard() .login("alice", "pw", "device-name")session.startSync(true) // 后台长轮询拉事件注意:Element 公司已经在主推新一代 Element X Android(用 matrix-rust-sdk 当核心,启动更快、加密 bug 三端一次修),原 Element Android 进入”安全维护,不再加新功能”模式。
为什么重要
- 不理解 Element Android,就解释不清”为什么 Slack/Discord 是中心化的、Matrix 不是”——前者数据全在一家公司,后者每个 homeserver 各自存
- 它是迄今为止 Matrix 在 Android 端用户量最大的客户端,几乎所有政府/医疗/军方私有部署都先选它(德国 TI-M 医疗即时通讯、法国 Tchap 都是基于它改的)
- 想看”Kotlin + 协程 + Realm + 端到端加密”在一个真生产 app 里怎么搭,这是公开样本里最完整的一个
- 看它怎么被 Element X 替代,能理解”为什么 Element 公司决定把核心搬到 Rust”——这是 matrix-rust-sdk 的反面教材
核心要点
Element Android 的工程价值集中在 三件事:
-
matrix-android-sdk2 是协议大脑:所有 Matrix 协议逻辑都在 SDK 里——登录、长轮询
/sync、解析房间事件、维护时间线、跑加密握手。UI 层(activity/fragment)只订阅 SDK 的 LiveData/Flow,不直接碰 HTTP 或 JSON。 -
Realm 当本地缓存:每个房间的事件、状态、密钥都先写进 Realm 数据库,UI 从 Realm 读。好处是离线可用 + 秒级冷启动;代价是 Realm 强引用线程(见踩坑 1),跨线程传对象会崩。
-
E2EE 走 Olm + Megolm + libolm:双人对话用 Olm(基于 X3DH 双棘轮,类似 Signal);群聊用 Megolm(一把”会话密钥”分发给所有成员,单向棘轮省算力)。底层调 C 写的 libolm,Kotlin 包一层 JNI。密钥本身存在 Realm 加密 store 里。
外面再裹两件事:flavor 拆分(Google Play 用 FCM 推送、F-Droid 用 UnifiedPush)和 Jitsi WebRTC(视频会议另起一套,不走 Matrix 自己的 1:1 RTP)。
关键设计决定:SDK 与 UI 分两层 repo——matrix-android-sdk2 是协议层、element-android 是 UI 层,每次发版前者拷一份到独立仓库给社区集成。这种”可剥离”的设计在维护期反而成了优势:UI 不再演进,SDK 也能独立给三方 app 续命。
实践案例
案例 1:政府/医院私有部署
德国国家医疗 TI-M、法国 Tchap、英国 NHS 都是这么干的:架自己的 synapse homeserver,把 Element Android 源码 fork 一份,改默认 homeserver URL、改品牌色 logo、改账号注册流程(接对接已有的 SSO),重打包发内部分发。
<string name="matrix_org_server_url">https://matrix.gov.example</string><string name="matrix_org_identity_server_url">https://vector.gov.example</string>整个 app 因此变成”政府专用 IM”,员工装上即用,数据完全留在本国机房。
案例 2:F-Droid 隐私党
Google Play 版用 Firebase Cloud Messaging 推送——这意味着每条消息的”提醒”事件先经 Google 服务器。隐私党不接受,所以 F-Droid 上有一个独立 flavor 用 UnifiedPush(开源推送规范),消息提醒走自己选的开源 Push Distributor(比如 ntfy)。
flavorDimensions "store"productFlavors { gplay { dimension "store" } // 含 FCM fdroid { dimension "store" } // 不含 FCM,用 UnifiedPush}同一份代码两个 APK,谁也不依赖谁。
案例 3:迁移到 Element X Android
老 Element Android 加密会话存在本地 crypto store。换设备或迁移到新 app(Element X)时,得先打开密钥备份(SSSS):用一串恢复密码把所有 Megolm 密钥加密上传到 homeserver,新设备登录后输入恢复密码拉回来。
设置 → 加密 → 安全密钥与备份 → 设置恢复密码 → 备份所有密钥(在 Element X Android 登录同账号,输入恢复密码,历史加密消息能解开)没做这一步直接卸载 = 历史加密消息永久解不开——这是新人最容易踩的悬崖。
踩过的坑
-
Realm 跨线程崩:Realm 对象只在打开它的线程有效。把
RoomSummary从 IO 线程传去主线程直接 throwIllegalStateException,必须realm.copyFromRealm(obj)出来一份普通 POJO 再传。 -
crypto store 丢了 = 历史 E2EE 消息解不开:升级、刷机、清数据前必须先做 SSSS 密钥备份。新人常以为”重新登录就能看到老消息”——错,密钥没了再登也是密文。
-
推送通道分裂:Google Play 版用 FCM,F-Droid 版用 UnifiedPush,两个 flavor 的 BroadcastReceiver 注册路径不一样。改推送相关代码必须两个 flavor 都测,否则一边收得到、一边收不到。
-
/sync 在大账号慢:账号加了 500+ 房间后,首次
/sync?full_state=true能拉几兆 JSON,冷启动要十几秒。sliding sync(MSC3575)能解决,但需要 synapse 开实验功能或挂 sliding sync proxy,部署门槛高。
适用 vs 不适用场景
适用:
- 在 Android 上做一个能联邦互通、自托管的 IM 客户端
- 给政府/医院/军方做”换皮版” Matrix 客户端(fork 改品牌即上线)
- 想读”Kotlin + Realm + 端到端加密”的生产级源码
不适用:
- 新启动的 Matrix 客户端项目——官方推荐直接用 matrix-rust-sdk + Element X Android 模板
- 想要 6 倍冷启动速度——matrix-android-sdk2 的纯 Kotlin 状态机比 Rust 实现慢
- 不需要 E2EE 的内部公司 IM——直接用 Mattermost / Rocket.Chat 简单得多
历史小故事(可跳过)
- 2014 年:Matrix 协议在英国诞生(创始公司 Amdocs Unified Communications 内部孵化)
- 2016 年:第一代 Android 客户端发布,叫 Riot Android,用老的 matrix-android-sdk(Java)
- 2019 年:matrix-android-sdk2 重写为纯 Kotlin + 协程,Riot Android 切到新 SDK
- 2020 年:Riot 整体改名 Element(公司、产品同时改),Riot Android → Element Android
- 2022-2023 年:Element 启动”Rust 化”,新一代 Element X Android 基于 matrix-rust-sdk
- 2024 年起:Element Android 标记为 previous-generation,仅接收安全更新
学到什么
- 去中心化协议的客户端只面对自己那台服务器——联邦由服务端代办,移动端代码不必懂联邦
- Realm 这种”对象数据库”换走线程上下文很危险——任何”持久化对象”设计都要想清楚生命周期边界
- 加密客户端的备份策略不是可选项——SSSS / 密钥备份要做成默认开启,否则用户必丢消息
- “重写比修旧”是 Element 的选择——matrix-android-sdk2 本来就够好了,但跨端共享逻辑的需求让 Rust 核心赢了
延伸阅读
- 仓库:element-hq/element-android(含 matrix-android-sdk2 子模块入口)
- Matrix 协议规范:spec.matrix.org(Client-Server API + Olm/Megolm)
- Element X Android:element-hq/element-x-android(继任者)
- libolm 源码:gitlab.matrix.org/matrix-org/olm
- Realm Kotlin:github.com/realm/realm-kotlin
- UnifiedPush 规范:unifiedpush.org(F-Droid flavor 用的开源推送)
关联
- matrix-rust-sdk —— 继任者用的核心 SDK(Element X Android 吃这个,Element Android 不吃)
- matrix-js-sdk —— Matrix Web 端老牌客户端 SDK,跟 matrix-android-sdk2 是兄弟
- synapse —— Matrix 主流参考服务端,Element Android 默认连的就是它(matrix.org 跑的也是它)
- dendrite —— Matrix 第二代 Go 服务端,Element Android 也能连
- diffie-hellman —— Olm/Megolm 端到端加密底层用的密钥交换数学
一句话总结
全 Kotlin 写、跑了八年的 Matrix Android 客户端——它把 Matrix 推到了千万级私有部署用户面前,也用自己的”维护成本”促成了 matrix-rust-sdk 的诞生。
反向链接
- dendrite —— Dendrite — Go 写的第二代 Matrix homeserver,组件可拆可合
- diffie-hellman —— Diffie-Hellman 密钥交换
- element-web —— Element Web — Matrix 协议旗舰 web 客户端(React + matrix-js-sdk)
- matrix-js-sdk —— matrix-js-sdk — Matrix Web/Node 端的”老大哥”客户端 SDK
- matrix-rust-sdk —— matrix-rust-sdk — Matrix 客户端的”共享发动机”
- signal-android —— Signal Android — 让 Android 上的每条消息都只有两端能看见
- signal-ios —— Signal iOS — 让 iPhone 上的每条消息都只有两端能看见
- signal-server —— Signal-Server — 服务端看不到任何明文的即时通信后端
- synapse —— Synapse — Matrix 协议的参考 homeserver,让聊天像电邮一样能跨服务器互通