Ohhnews

分类导航

$ cd ..
foojay原文

Codename One 的 JavaScript 移植现已免费开源

#codename one#javascript#开源#移动开发#跨平台

[LOADING...]

Codename One JavaScript 移植版现已开源,并可在包括免费版在内的所有套餐中使用。你还可以在没有 Codename One 账号的情况下,在本地构建 JavaScript 目标。

PR #5423 使基于 ParparVM 的移植版成为默认方案,并将其源码迁移到与 Codename One 其余部分相同的 GPLv2(含 Classpath 例外)许可证下。TeaVM 仍可作为备用方案。

TL;DR

  • JavaScript:ParparVM JavaScript 移植版现已开源,所有套餐免费,且无需账号即可在本地构建。
  • Android API 36:API 36 将于下周成为默认版本,你可以通过构建提示进行测试或临时覆盖。
  • ParparVM 垃圾回收:一个真实应用暴露了基准测试缺口、冗余回收周期,以及一个失败的首次修复。
  • 日历:新 API 涵盖设备日历和云日历、重复事件、任务、增量同步、离线变更和冲突处理。
  • 蓝牙:蓝牙已移入核心,支持 BLE、GATT、L2CAP、RFCOMM、Web Bluetooth、桌面蓝牙硬件和可脚本化模拟器。
  • 文本编辑EditFieldRichTextAreaCodeEditor 可以在不将原生字段覆盖到组件上的情况下编辑和绘制文本。新的剪贴板模型在纯文本回退之外,还可携带富文本、图像和文件引用。
  • 富文本RichTextComponent 无需 WebView 即可渲染 HTML、Markdown、AsciiDoc、RTF 和带样式的 Java 文本段。
  • 紧凑字符串:当每个字符都适合时,字符串现在使用 byte[],字符存储减半,且无需添加第二个后备数组指针。
  • 账号、网站和视频:授权迁移已完成,改版网站已上线,每周文章现在会生成解说视频。

我们为什么构建 ParparVM JavaScript 移植版

我们最初的 Web 移植版使用的是 TeaVM,这是一个非常出色的项目。TeaVM 接收 Java 字节码,支持 Codename One 所需的线程模型,并在当时看似几乎不可能的情况下生成了浏览器应用。

在我们基于 ParparVM 构建第二条 JavaScript 路径时,它已经在为其他 Codename One 目标提供支持。如果我们当初知道这需要多少工作量,或许会把精力花在改进 TeaVM 集成上。完成后的移植版让我们可以直接控制 VM、运行时、浏览器集成和测试基础设施。

VM 级 API 现在可以通过与原生目标相同的翻译器和 Java 运行时来覆盖 Web 目标。我们不再需要先在第三方运行时中单独实现,然后浏览器才能使用新的 VM API。

共享框架和运行时的变更也会触发 ParparVM 浏览器测试套件。拉取请求会在 Chromium 中渲染并比较 Codename One 测试应用。每晚任务会在 Chromium、Firefox 和 WebKit 中启动生成的应用。

该移植版还将 Java 代码与浏览器代码分开:

旧路径在浏览器主线程上并行运行翻译后的 Java 和其 JavaScript 集成。ParparVM 路径则将翻译后的 Java 应用放在后台 Web Worker 中。Java 代码中没有 document、没有 DOM,也不依赖全局浏览器对象。

一条轻量消息桥将 Worker 与运行在浏览器主线程上的标准前端 JavaScriptPort 连接起来。该移植层负责处理画布、DOM、剪贴板、媒体、Web Bluetooth 以及其他浏览器 API。应用特定的浏览器代码仍位于 native/JavaScript 下。

浏览器 API 仍然会带来浏览器规则。例如,Web Bluetooth 需要 HTTPS 和用户手势。这些要求保留在移植层中,而不会影响普通 Java 代码。

不使用我们的云服务即可构建 Web 目标

生成的 Maven 项目现在包含本地 JavaScript 构建命令:

$ bash
mvn -pl javascript package \
  -Dcodename1.platform=javascript \
  -Dcodename1.buildTarget=local-javascript

该构建使用捆绑的 ParparVM 编译器和 JavaScriptPort 资源。它会在你的机器上生成一个静态浏览器应用。同一命令也已接入生成的 IntelliJ、Eclipse、NetBeans 和 VS Code 项目中。

云构建默认也使用 ParparVM。如果遇到回归,可以通过添加一个构建提示来与原来的 TeaVM 路径进行比较:

$ properties
javascript.port=teavm

完整的选择方式如下:

构建提示结果
javascript.port=parparvm当前的 JavaScriptPort 构建器,也是默认值。
javascript.port=teavm原始的 TeaVM 构建器,用于兼容性和诊断。

TeaVM 不会被移除。在 ParparVM 获得更多生产使用的同时,它仍可用于兼容性测试。

代理现在已成为部署的一部分

浏览器应用可以直接调用同源接口。对其他源的调用则取决于该服务器的 CORS 策略。Codename One 传统上通过捆绑在 TeaVM WAR 中的 servlet 代理来处理第二种情况。

该代理来自较旧的 Java EE 技术栈。它调用了一个已从当前 Jakarta 容器中移除的 servlet API,因此仅将 javax.servlet 导入改为 jakarta.servlet 无法修复它。

ParparVM 构建器现在会为你选择的部署目标创建代理包装器:

$ properties
javascript.proxy.target=cloudflare-workers
javascript.proxy.allowedTargets=https://api.example.com,*.services.example.org

可用的目标包括 jakarta-servletjavax-servletnodephpaws-lambdagoogle-cloud-functionscloudflare-workersnone。默认值是 Jakarta Servlet。

[LOADING...]

在公开部署前设置 javascript.proxy.allowedTargets。如果省略允许列表,构建会产生警告,因为开放转发代理不是生产环境的安全默认值。你也可以通过 javascript.proxy.url 指向现有代理,或通过 javascript.proxy.target=none 禁用生成。

为什么我们要向所有人开放 JavaScript

JavaScript 构建以前是 Enterprise 版功能,也是购买该套餐的重要原因。我们预计这一变化会减少其中部分收入。对于一家小公司来说,这影响很大,而这也是我很难做出的决定。

付费套餐会继续为云构建机器、推送服务、崩溃保护、支持、版本保留以及从事这项工作的人员提供资金。如果你的团队依赖付费服务,请继续通过适合你需求的套餐支持这项工作。

我们不知道更广泛的采用能否弥补我们失去的收入。但我们知道,让移植版更易于使用和测试是正确的技术方向。

Android API 36 将于下周成为默认版本

一些开发者已经收到 Google Play 关于 Android 16 和 API 级别 36 的通知。Issue #4466 让我们有时间在更改默认版本之前测试 edge-to-edge 行为、尺寸调整、权限和原生集成。

立即尝试新目标:

$ properties
android.targetSDKVersion=36

请提交一个构建,并测试应用中涉及系统 UI、后台行为、蓝牙、通知和原生库的部分。一旦 36 成为默认版本,你可以在调查回归问题时临时固定使用之前的工具链:

$ properties
android.buildToolsVersion=35
android.targetSDKVersion=35

像 #4466 这样的 issue 是小团队跟上 Apple、Google、浏览器厂商、桌面平台和硬件变化的方式。如果你看到可能影响 Codename One 的平台公告,请尽早提交 issue。提前几个月发出简短预警,远比在商店截止日期后紧急迁移要省事得多。

Issue #5425:Dave 对垃圾回收器的判断是对的

issue #5425 中,Dave 报告了一个 iOS 工作负载,该负载大约需要五分钟才能完成,而同样的代码在 Android 和旧版 ParparVM 回收器上只需几秒即可完成。我一开始只关注分配密集的应用代码,并认为这不是回归问题。Dave 减少了临时分配,持续测量,并证明我的解释与结果不符。

他是对的。我们最近的 BiBOP 回收器工作让短命小对象便宜得多,但基准测试集没有覆盖“大量存活对象之后再进行更大分配”的场景。固定的触发条件可能在存活集合几乎没有可回收对象时,仍然过于频繁地运行回收。

PR #5436 添加了一个与该 issue 形态对应的基准测试和自适应节奏。第一个修复还引入了一个新对象追踪 bug,它可能释放仍存活的对象,并使父对象指向已复用的内存。Dave 反复基于 master 构建,暴露了字典词条损坏和一个不可能的 null 指针。PR #5442 修复了这一正确性回归,并增加了有针对性的审计。

针对当前开发分支最新版的版本化构建使这一反馈循环得以实现:

$ properties
build.cn1Version=master

在我们修改回收器的过程中,Dave 可以在他的真实应用中测试每个回收器修订版。

谢谢你,Dave。我确实需要这一记当头棒喝。

日历 API:本地日历、云端同步与冲突

TL;DR: com.codename1.calendar 为设备日历、Google、Microsoft、CalDAV 和 .ics 文件提供统一 API。它包括重复事件、任务、提供方版本、增量同步、离线变更队列和冲突处理。

[LOADING...]

添加事件的请求往往会演变成一系列情况:重复事件、来自另一台设备的编辑、离线更改,以及可能覆盖提供方较新副本的过期写入。新 API 将这些情况统一放在一个模型中,并公开每个数据源支持的操作。

$ java
LocalCalendarSource source = LocalCalendarSource.getInstance();
CalendarCapabilities capabilities = source.getCapabilities();
if (capabilities.supports(CalendarCapability.READ_EVENTS)) {
    source.queryEvents(query).ready(page ->
            page.getItems().forEach(System.out::println));
}

模拟器使用独立的内存日历,因此测试绝不会触碰开发者真实的日程安排。PR #5413 包含了实现。阅读日历 API 文章,了解重复事件、OAuth 边界、冲突解决和提供方同步。

蓝牙现已纳入核心

TL;DR: 新的核心蓝牙 API 支持所有 Codename One 目标平台,包括 JavaScript。它涵盖 BLE 中央和外围角色、GATT、L2CAP 流、经典 RFCOMM、浏览器 Web Bluetooth、真实桌面蓝牙硬件,以及可脚本化模拟器。

[LOADING...]

蓝牙远不止 BLE 扫描。医疗传感器可能需要通过 GATT 接收通知。扫描仪或打印机可能使用经典 RFCOMM。测试需要能重现连接失败或回调永不触发的情况。

该 API 会如实报告能力,而不是假装每种角色在所有地方都能工作。浏览器通过由用户控制的选择器提供中央 GATT。iOS 不暴露任意的 RFCOMM。不引用蓝牙的应用不会获得任何蓝牙权限或原生代码。

PR #5399 是从零开始重写、用于替代源自 Cordova 的 cn1lib 的实现。阅读蓝牙文章,了解角色模型、平台矩阵、GATT 队列、模拟器和构建器行为。

纯 Codename One 文本编辑

TL;DR: EditFieldRichTextAreaCodeEditor 现在可以完全在 Codename One 组件层内编辑和绘制文本。操作系统提供键盘和输入法操作,而无需在组件上覆盖原生字段。

[LOADING...]

Codename One 传统上会在用户编辑时,将一个原生文本字段覆盖在轻量字段上。这种方式仍是 TextFieldTextArea 的默认做法。它适用于普通表单,但无法在 Codename One 界面内绘制语法着色、内联对象、应用自定义掩码或自定义选择。

新的 TextInputClient 合约发送语义化操作,如 commitText(...)setComposingText(...)deleteSurroundingText(...)。可移植编辑器负责管理文档、光标、选择、双向布局、撤销状态和像素。

$ java
EditField notes = new EditField();
notes.setSingleLineTextArea(false);
notes.setRows(5);
form.add(notes);

这项工作还取代了“剪贴板只支持文本”的假设。ClipboardContent 可以为同一内容携带多种表示形式:纯文本、HTML、RTF、Markdown、AsciiDoc、PNG、JPEG、GIF 和文件引用。移植层会公布其系统剪贴板支持的格式,同时保留纯文本,以兼容旧代码和不理解更丰富负载的应用。

RichTextArea 直接使用这一协商机制。复制带格式的选择内容时,会同时提供纯文本、HTML、RTF、Markdown 和 AsciiDoc。粘贴时会选择它能理解的最丰富的文本表示,而剪贴板中的图像会变成内联图像,而不是文件名或被丢弃的负载。

PR #5386 添加了移植层绑定和可移植编辑器引擎。阅读文本输入文章,了解组合输入、UTF-16 偏移、双向命中测试、富剪贴板协商,以及原生覆盖层的取舍。

无需 WebView 的轻量富文本

TL;DR: RichTextComponent 可在普通 Codename One 布局中渲染 HTML、Markdown、AsciiDoc、RTF 和带样式的 Java 文本段。它与 RichTextArea 共享文档模型和绘制器,但没有编辑会话或浏览器对等组件。

[LOADING...]

SpanLabel 为换行文本提供单一样式。BrowserComponent 提供完整的浏览器能力。RichTextComponent 填补了两者之间的空白,用于格式化应用内容。

$ java
RichTextComponent view = new RichTextComponent();
view.setMarkdown("# Trip summary\n\n"
        + "Departs **09:40**. See the [itinerary](app://itinerary).");
form.add(view);

该组件会在 Codename One 布局内测量其内容。链接处理、图像加载、身份验证和缓存仍由应用控制。HTML 导入器处理文档标记,但不会执行脚本,也不会实现 CSS 页面布局。

PR #5421 包含查看器和共享绘制器。阅读富文本文章,了解支持的格式、链接和图像策略、剪贴板协商,以及何时应继续使用 BrowserComponent

ParparVM 中的紧凑字符串

TL;DR: 当每个字符都适合时,字符串现在会把字符存储在 byte[] 中。需要更宽代码单元的文本继续使用 char[]。这使常见字符串的字符存储减半,而无需在每个 String 中增加第二个数组指针。

[LOADING...]

URL、JSON 键、类名、数字、日志消息以及许多西欧文本每个字符只需要一个字节。ParparVM 之前将所有这些内容存储在双字节 char[] 中。

$ java
String compact = "Résumé 2026"; // byte[] backing
String wide = "שלום";           // char[] backing

实现保留一个 Object value 字段,其具体数组类型用于标识表示形式。翻译器还保留了融合分配:String 字段和后端存储占用同一个 BiBOP 块。应用代码无需更改。

阅读紧凑字符串文章,了解对象布局、融合分配更改、原生操作审计和 Unicode 行为。## 账户、网站和视频更新

授权迁移

我们完成了上周开始的账户授权迁移。我们过早关闭了旧的授权服务器,在修复剩余路径时将其恢复,然后再次关闭。

下次发送构建时,您可能需要重新登录。如果全新登录无法成功,请通过网站聊天联系我们。这仍然是联系团队最快的方式。

新网站设计

PR #5402 带来了新的网站设计,并重写了几个核心页面。请告诉我们哪些地方好用、哪些地方有阻碍、哪些地方需要改进。

由这些帖子生成的视频

Codename One YouTube 频道 现在拥有由这些帖子生成的解说视频。PR #5441 添加了视频构建器,使用 Codename One 编写,可以将审阅过的脚本转换为定时场景、旁白、代码、字幕以及最终视频。

视频剪辑不是我的强项。生成的语音并不让我困扰,而且省去数小时的旁白和时间线工作意味着我们可以覆盖更多已发布的内容。问题是这些视频是否对你有帮助。我们应该继续投入这种格式并用它来替代老化的课程,还是你更希望看到更少但由人工旁白的视频?请告诉我们。

本文《The Codename One JavaScript Port Is Now Free and Open Source》首发于 foojay