Codename One发布VoIP与VPN原生API,构建器自动完成平台集成
2019年,一位读者询问我们的 WhatsApp 克隆是否包含 VoIP。我说没有。那需要原生集成,而当时的配置对那个项目来说太重了。多年来,这就是我们的界限。
[LOADING...]
Codename One 是什么? Codename One 是一个开源框架,可用同一套 Java 或 Kotlin 代码库构建原生 iOS、Android、桌面和 Web 应用。了解更多请访问 codenameone.com。
VPN 也位于同一条线之后。这些功能并不整齐地存在于应用进程内部。在 iOS 上,VoIP 推送可能在应用安装 Java 监听器之前到达,但 CallKit 期望来电在其截止时间前到达锁屏。Call Directory 集成是一个单独的扩展可执行文件,有自己的生命周期和签名身份。托管 VPN 会增加 Network Extension 权益和配置文件规则。Android 通过 ConnectionService、VpnManager、VpnService、清单声明和前台服务规则来解决同样的任务。
用 Java 包装这些 API 只是小部分。跨平台运行时无法在安装前添加服务、在应用就绪前响应原生回调,也无法在应用包内创建并签名另一个可执行文件。一旦每个团队都必须在 Xcode 和 Android Studio 中维护这些部分,共享抽象就在最难的地方止步了。
Codename One 针对这个问题有一件秘密武器:构建器。
构建器已经负责组装原生产物,因此它们可以根据应用实际使用的 Java 包来改变其形态。它们链接框架、注入权限和后台模式、生成原生委托和服务,并在需要时创建扩展目标。不引入某个包,其原生机制也不会被引入。
PR #5604 就是我们跨越那条旧界限的地方。它引入了一等公民的呼叫管理、VoIP 推送处理、托管 VPN,以及数据包隧道 API 和 Android 宿主。PR #5693 通过生成单独翻译并签名的 Network Extension 补全了 iOS 路径。Java API 是可见的结果。更大的变化在于,构建器围绕它组装系统集成,而无需让每个应用团队都成为两个原生构建项目的维护者。
简要说明
Calls将应用呼叫状态连接到 iOS 上的 CallKit 和 Android 上自管理的ConnectionService。你的媒体和信令栈仍由你掌控。Vpn在 iOS 和 Android 上安装并控制托管 IKEv2 配置文件。iOS 还支持托管 IPsec 配置文件。VpnTunnel在 Android 和 iOS 上为应用实现的隧道暴露原始数据包。iOS 构建器工作 会生成并签名一个单独的 Network Extension,其中包含翻译后的 Java 隧道。- 对话框和次级窗口 现在可以协同工作,包括可选启用的原生模态窗口。
- 新的 AppKit 移植 构建真正的 Mac 应用,而不是通过 Catalyst 呈现 iOS 应用。
- 短信一次性验证码自动填充 可填充验证码,而无需请求读取收件箱。
- 每日平台观察 现在将 Apple 和 Google 的通知转化为源码级检查,在截止日期变成火灾之前采取行动。
- 新的联系人选择器 返回所选联系人数据,而无需广泛的通讯录访问权限。
呼叫首先是一个系统会话,其次才是媒体会话
VoIP 应用有两条时间线。其信令服务器和媒体引擎有一条,操作系统有另一条。在 iOS 上,传入的 VoIP 推送必须立即报告给 CallKit。此时 Java 应用可能尚未运行。在 Android 上,即使应用自己的界面在别处,自管理连接也会参与系统呼叫路由和音频焦点。
新 API 将这些职责分开:
Calls 不选择编解码器、不移动音频数据包,也不发明信令协议。它负责通往系统呼叫 UI 和生命周期的桥梁。应用只在原生音频会话激活后才启动媒体,而不是仅仅因为用户点击了“接听”。
包边界也有意义。com.codename1.call.session 添加核心系统呼叫集成。com.codename1.call.voip 添加 PushKit 和 iOS VoIP 后台模式。com.codename1.call.directory 添加目录集成,而不会悄悄引入自管理呼叫权限。Apple 可能拒绝声明了 VoIP 后台执行却不使用它的应用,因此引入更少代码就必须构建更少原生机制。
模拟器提供确定性的呼叫和音频会话事件。这样,在构建产物到达手机之前,就可以测试那些别扭的顺序。
托管 VPN 及一个重要的平台边界
许多企业应用并不实现 VPN 协议。它们安装操作系统配置文件,并请求平台连接它。这条路径现在有一个 Java API:
iOS 和 Android 11 或更高版本支持托管 IKEv2。iOS 还支持使用预共享密钥的托管 IPsec。操作系统负责用户同意、凭据存储和活动配置。该 API 暴露这些边界,而不是把 VPN 密码存储在应用偏好设置中。
数据包隧道边界
对于不适合托管 VPN 配置文件的工作,VpnTunnel 让应用代码检查原始 IP 数据包,并决定转发还是丢弃它们:
同一个 Java 数据包循环在 Android 和 iOS 上运行,但进程边界不同。Android 将它托管在应用进程内的 VpnService 中。iOS 需要 NEPacketTunnelProvider Network Extension:一个单独的可执行文件,拥有自己的 bundle identifier、配置文件、内存限制和翻译后的 VM。PR #5693 中的构建器工作会创建该目标,从选定的 VpnTunnel 类而不是 UIKit 应用外壳进行翻译,添加框架和权益,并将签名后的扩展嵌入应用中。
iOS 扩展包含翻译后的隧道和 VM,但不包含 Codename One 网络栈。它可以在设备上检查、改写、丢弃和 forward 数据包。它不能通过 com.codename1.io.Socket、ConnectionRequest 或 ParparVM 的 java.net 打开远程连接。Android 可以中继数据包,因为其隧道运行在应用进程中。需要远程中继的协议应将这种传输放在平台特定接口之后,而不是假设两个宿主完全相同。
Apple 逐案授予 Network Extension 权益,因此该目标有意采用可选启用。一旦 App ID 拥有该权益,就在 codenameone_settings.properties 中指定隧道类:
宿主应用和 <packageName>.vpntunnel App ID 都需要 Apple 的 Network Extension 权益。该扩展还需要自己的配置文件。它从全新状态启动,需要可访问的无参构造函数,并且看不到应用静态成员或 Display。通过 TunnelSetup.data 传递本地配置,并在 VpnTunnel.onStart() 中读取它。这是一个真正的跨平台 API,而不假装两个操作系统具有相同的生命周期或网络设施。
构建器是运行时契约的一部分
浏览器应用无法注册 CallKit、自管理的 Android 连接服务或 Apple Network Extension。Flutter、React Native 和 .NET MAUI 可以通过原生插件和平台项目访问这些 API。困难的工作于是存在于目标成员资格、清单、权益、后台模式、原生委托和签名中。
构建器改变了这些困难工作所在的位置。构建服务器已经拥有原生产物图。它可以看见哪些 Java 包最终保留在应用中,并生成与之匹配的平台部件:
依赖注入无法完成这项工作。构建器可以添加必须在 Java 启动之前就存在的服务,编译满足操作系统截止时间的原生回调,并作为同一产物的一部分签名扩展。应用团队可以继续掌控其 UI、协议和业务逻辑,而无需维护一套并行的原生构建系统。
Dialog 可以变成原生模态窗口
上周的原生窗口发布列出了几个仍然假设每个顶层都是 Form 的组件。PR #5624 关闭了该列表中的大部分。Dialog、Sheet、ToastBar、组合框弹出菜单、浮动操作子菜单、进度覆盖层、工具提示和 HTMLComponent 现在都会解析包含它们的窗口。无障碍状态也按窗口进行跟踪。
Dialog 可以保持为其所有者内部的轻量级覆盖层,也可以选择启用真正的操作系统窗口:
窗口后续文章 涵盖了优先级、回退行为、锚定弹出窗口以及剩余限制。
基于 AppKit 构建的 Mac 应用
Mac Catalyst 帮助我们把 iOS 应用带到 macOS,但它的桌面行为仍受 UIKit 限制。它无法提供若干普通窗口操作,并且一个次级 4K 表面在图像被复制到位之前,每个中间帧可能需要大约 33 MB。
PR #5601 用 AppKit 替换默认 Mac 原生路径。生成的应用现在拥有 NSApplication、NSWindow、NSMenu、NSScreen,以及每个窗口一个 CAMetalLayer。始终置顶、实用工具窗口、最小化、恢复、最大化、原生模态和独立脏区域绘制都是真正的桌面操作。
AppKit 文章 解释了渲染变化、构建目标、测试证据以及通往 VoiceOver 的原生无障碍桥接。
无需收件箱权限的 OTP
PR #5642 添加了一次性验证码约束以及可复用的电话号码和验证组件。在 iOS、Android 和 Web 上,操作系统可以从短信中提供验证码,而无需授予应用读取消息的权限。
Codename One 不发送或验证短信。你的服务器仍然负责过期、尝试次数限制、速率限制和会话签发。OTP 文章 包含完整组件和更底层的文本字段选项。
别再通过失败的构建来了解平台变化
直到本周之前,Apple 或 Google 的变更通常是通过社区报告传到我们这里的。这很有用,但它是在有人已经受到影响之后才开始计时。
我们现在每天运行一个定时的 Codex 任务,读取官方平台通知,提取要求和日期,追踪受影响的 Codename One 生产者,并检查是否存在已有 issue 或修复。它发现了在 PR #5673 中修复的 Android 16 返回导航漏洞,以及在 PR #5680 中促成 Contact Picker 的隐私方向。
该任务并不会让通知自动成真,也不能替代平台测试。它的职责是将一手来源的截止日期连接到必须更改的确切构建器、生成项目或 API。平台观察文章 展示了这条链。联系人选择有自己的一篇以代码为重点的文章,因为权限模型值得不止一段话。
四个影响范围很大的小改动
Google Play Billing 8
PR #5651 将默认 Android 计费依赖移到 8.0.0,并完成从已停用的 SKU API 到 ProductDetails 的迁移。Google 的计费弃用时间表 现在使版本 8 成为更新的最低要求。构建器吸收了生成项目迁移,大多数应用团队本不该自己去追这件事。
JavaScript 密度现在表示设备像素
PR #5634 修复了当浏览器设备像素比既不是 1 也不是 2 时的显示宽度、高度和毫米换算。一个在比例为 3 时宽度为 390 CSS 像素的视口,现在会报告 1,170 设备像素。
这可能会改变现有 JavaScript 应用中的布局和截图结果。当断点确实需要 CSS 像素时,请读取 CN.getProperty("browser.window.devicePixelRatio", "1")。
鼠标滚轮不再是伪造的拖拽
桌面移植版过去会把一次滚轮转动转换成一次按下、三次拖拽事件和一次释放。这可能在什么都没滚动时触发点击。PR #5660 直接路由原生滚轮事件。
内置滚动会查找最近的可滚动祖先。现在,消费滚轮输入的自定义组件应重写 mouseWheel(WheelEvent) 或注册监听器:
开发者指南变成了受维护的产品
我们移除了过时的 IDE 和 Ant 时代指南,将 Maven 工作流并入主书,恢复了缺失的示例,并重新调整了若干章节的顺序。更重要的是,CI 现在会检查指南的结构、交叉引用、承诺的代码块、提供的链接、图片、正文和生成输出。
文档漂移现在会导致构建失败,而不是留给未来某一周清理的项目。恢复的呼叫、VPN、桌面、安全、身份、事件线程、图形和性能材料是发布契约的一部分。## 以更窄的默认权限实现原生触达
本周,Codename One 在两个方向上同时拓展。通话、VPN 配置、AppKit 窗口、联系人选择和 OTP 更深入地接入各个操作系统。由包触发的构建器则让这种触达范围保持狭窄。应用不会因为另一个产品需要 VoIP 后台执行而获得该能力,也不会为了挑选一个人而获得广泛的联系人访问权限,更不会为了填写六位验证码而获得读取消息的权限。
默认安全编程依赖于小型公共 API、生成的原生集成、明确的平台限制,以及更少将便利功能变成永久访问权限的机会。你仍然掌控应用 UI 和协议选择。构建器则吸收了交付这些功能所需原生产品底层管线。
先阅读通话管理或 VPN章节,然后在发送原生构建之前,在模拟器中测试失败路径。
分享此页面
- Bluesky
- Mastodon
- Hacker News
- 电子邮件
- 复制链接
- 分享...
发现错误,或有内容要补充?在 GitHub 上编辑此页面
[LOADING...]
作者
Shai Almog
作者、开发者关系、博主、开源黑客、Java 摇滚明星、会议演讲者、讲师和企业家。
相关文章
Java [LOADING...]
Shai Almog2026年8月7日 499 次浏览
Push V3:从你的服务器向所有终端发送一条消息
Java Java [LOADING...]
Shai Almog2026年8月29日 169 次浏览
SQLite 跨越每个平台:同一份契约,同一种加密文件格式
Java Java [LOADING...]
Shai Almog2026年8月28日 165 次浏览
App Shield:你的服务器不应信任调用它的应用
Java Java [LOADING...]
Shai Almog2026年6月17日 238 次浏览
Mac 原生构建、实时协议,以及 350 以下的未解决问题
Java Java [LOADING...]
Shai Almog2026年6月15日 342 次浏览
核心中的 AI、OAuth 及其他平台 API
Java Java [LOADING...]
Shai Almog2026年6月3日 1,112 次浏览
NFC、加密技术、生物识别与新的构建云
加入讨论
Java [LOADING...]
Shai Almog2026年8月7日 499 次浏览
Push V3:从你的服务器向所有终端发送一条消息
Java
Java [LOADING...]
Shai Almog2026年8月29日 169 次浏览
SQLite 跨越每个平台:同一份契约,同一种加密文件格式
Java
Java [LOADING...]
Shai Almog2026年8月28日 165 次浏览
App Shield:你的服务器不应信任调用它的应用
Java
Java [LOADING...]
Shai Almog2026年6月17日 238 次浏览
Mac 原生构建、实时协议,以及 350 以下的未解决问题
Java
Java [LOADING...]
Shai Almog2026年6月15日 342 次浏览
核心中的 AI、OAuth 及其他平台 API
Java
Java [LOADING...]
Shai Almog2026年6月3日 1,112 次浏览