在 App Store 诞生之前,Java 桌面应用就是 OS X 上的一流公民,它毁掉了所有的乐趣。
在本文中,我将以简略的校记,记述常被公众遗忘的 Mac Java 开发的黄金时代,那是在千禧年的头十年,从 2001 年首次发行 OS X 到 2011 年的 Mac App Store 的推出。HTML5 的问世也许扰乱了 Java 的企业阵营,但是那些开发 Mac 桌面应用的 Java 开发人员却没有受到任何影响。直到苹果宣布其 Mac App Store 的计划,“事情才变成现实”。
大约在 2006 年,我利用 Java 开发了一款工具,利用 OCR 将 PDF 文档转换成文本文档。起初,我只在 Mac 上发布,并在苹果网站的下载区进行推广(题外话:苹果的下载区是一个庞大的流量来源,远远超过了最终取代它的 Mac App Store)。彼时,Mac 仍然配备了 Java,因此,使用 Java 开发 Mac 应用并不存在“大小”的区别。它的外观和使用看起来就像原生应用一样,关键是在这个网络带宽还很有限的年代里,应用的大小非常小。
Windows 市场
我本来打算把这款应用移植到 Windows 上,但因为我是 Mac 的用户,我决定在解决这些问题之前先把它搁置一边。因为它是用 Java 写成的,所以要把它移植到 Windows 上并不是什么难事。我一直在使用一些 Mac 原生库进行图像增强,我需要为它们开发对 Windows 友好的替代品,并且我还得修改一些 UI 项目(比如,把“Quit”换成“Exit”,还有在文件关联上做了一些细微的修改)。
在将其移植到 Windows 的过程中,最难的就是为它开发安装程序。一开始,我用 Launch4J 为它创建了 Windows.exe 的启动程序。我将其作为一个 zip 压缩包发布,让用户将其拷贝到他们计算机上所需要的位置。但是,在常常被要求提供“适当的”安装程序后,我就用 Install4J 来创建了安装程序。因为这个“安装程序”本身就是 Java 应用,所以我使用 Launch4J 为这个安装程序创建了启动程序。
在 Windows 上的效果并不是很好,因为不完全是原生的,但是 Windows 用户不像 Mac 用户那样挑剔,所以它已经“足够好”。
当我第一次发布这款工具的 Windows 版本时,由于 Windows 市场远大于 Mac 市场,所以我预期销量会大幅增长。但我很失望地发现,尽管 Windows 的市场规模更大,但是其分布也更加广泛。在 Mac 上,你只要把你的应用放在苹果网站的下载区,你就可以获得几乎所有的用户。所有 Mac 用户在搜索软件时都会到这里来。
但是,在 Windows 上,并没有一个地方可以推广我的应用。有数以百计的下载站点,每个站点都充斥着和我的应用差不多的软件。在我的首次发布中,我将它提交给了几十个下载站点。我甚至可能为一个提交服务支付了费用,广告上写着他们会在数百个站点上自动发布你的应用。但是,在发布最初的几个版本之后,工作变得过于繁重,而带来的收益却微乎其微。Windows 版本对我的销量没有什么帮助。每销售一个 Windows 版本,我就能卖出 100 个 Mac 版本。所以,对于大多数版本,我把 Windows 版本发布在 CNET 下载(即 upload.com 和 download.com) 上,然后就不再发布了。
在随后的数年中,我逐渐建立起一种相当稳定的模式,每隔两三个星期就会进行一次小小的更新。我会在 MacUpdate 中发布 Mac 版本,在 CNET 中发布 Windows 版本。每一次更新都包含了 Bug 修复和新特性,这些新的发布都会吸引一些访问量,以维持下载量。
App Store:“我们不会为你这样的人服务”
在 2006 年至 2010 年期间,Java 在桌面计算机领域最大发展是:
iPhone。这一点非常重要,因为它是另一个热门的新平台,而这个平台上面没有 Java。
JavaFX。这很重要,因为它为 Java 的老化的 UI 工具包注入了急需的青春元素。
这两件事对我和我的小众工具都没有太大的影响,至少没有什么直接的影响。我的应用在 iPhone 上并没有多大的意义,因为这是为了处理你平时在桌面计算机上使用的文档。我的用户界面非常简洁,我不需要 JavaFX 提供的任何华丽的新图形。尽管如此,我还是饶有兴趣地关注着它们,因为我的雄心壮志早已超越了我那卑微的 OCR 应用,而现代图形和现代化平台对我来说实在太有吸引力了,以至于我无法忽视。
2010 年,苹果公布了 Mac App Store 的消息,引发了极大的轰动。苹果给了我一个许诺,但却在一次新闻发布会上将这个许诺从我身边扯走。他们宣称,他们将会把 Mac 应用放进新的应用商店。这是好的方面。不好的方面是,他们将不再推荐自己的 Java 发行版,而且,未来的 OSX 也不会包含 Java。此外,为了给 Java 致命一击,他们还在 App Store 的指导方针中明文规定,App Store 的应用不允许使用任何已经过时的库。
我并非一名律师,我生来就是一个乐观的人,因此,我还抱有一种想法,那就是,我还有一种方法可以把我的应用放到 App Store 里。我是说,公告并没有明确说 Java 应用不允许进入。它只是说 Java 现已被废弃,而且,应用不能再利用任何废弃的库。你必须将两者结合起来,才能得出这样的结论。这就好像是在拍一部电影,你不会亲眼看见那些坏人死去。“也许他从 50 楼摔下来的时候还活着!”很遗憾,苹果的技术支持部门证实了我的应用由于依赖 Java 而不能满足 App Store 要求。
Java 在 Mac 上的前途黯淡
在新闻发布会后的数个月中,人们对 Java 在 Mac 上的前途提出了疑问。Sun(现为 Oracle)一直在 Linux 和 Windows 上维护 Java,而苹果则一直维护和开发 Mac 版本。现在,苹果表示,他们将不会再这样做了。几个月后,Oracle 宣布他们将接手 Mac JDK 的开发,并将其纳入 OpenJDK 7 中,但是这还需要一段时间,并且在 2011 年 1 月 Mac App Store 的盛大开幕之前,它是不可能实现的。
替代 JVM
我从心底里感到,Mac App Store 是通往无限财富的钥匙,而就当时的情况来看,我已经被排斥在外了。那时候我还可以进入苹果网站的下载区,但是,从现实的角度来看,苹果有了 App Store 之后,还能坚持多久。我记得,App Store 刚上线没多久,苹果就把下载区给关掉了。
依我看,当时我有三个选择:
用 Objective-C 将我的应用重写为原生 Mac 应用。
等待 Oracle 新的 JDK7 Mac 版本,并尝试将其与我的应用程序捆绑。
使用替代的 JVM,并将其与我的 Mac 捆绑。
我是一个“不遗余力”的人,所以我基本上把这三个选择都试过了,但是最后还是选项 2(Oracle 的 JDK7)赢得了胜利。我只是错过了 Mac App Store 热棒的头一年。
在那一年里,我花费了大量的时间去测试其他 Java 虚拟机。我看到了所有有希望的事情,但是最令我记忆犹新的是 GCJ(The GNU Compiler for Java)、Avian 和 IKVM + Mono。所有这些都存在着同样的局限性:没有 Swing 支持。如果我可以重构代码,让 UI 完全模块化,那么我就有可能在这些替代编译器中编译业务逻辑,并将其与另一个 UI 工具包(比如 SWT、QT 或 Cocoa)配对。
我发现 GCJ 的输出很难处理。我想不起具体的细节了,我只记得,我花了好几个星期的时间和它搏斗,最后把自己搞得遍体鳞伤,却找不到任何切实可行的办法。
我很喜欢使用 Avian,但它的运行时库没有包括所有的标准 JavaSE 类,所以它需要做太多的改动才能实用。(或许我就是太懒了,不愿意去做这些改动)。我用 Avian 做了几个测试,用 SWT 做用户界面,效果相当好。它们启动起来很迅速,而且可执行文件的大小也相当小,因此,虽然它并不适合这个项目,我还是在心里记下了它,以备将来之需。
到目前为止,我对另一种工具链的最佳体验是 IKVM+Mono。IKVM 是一个 Java 到 DotNet 的编译器,而 Mono 是 DotNet 的开源、跨平台版本。我能够将我的 Swing 代码剔除,并生成一个只有我的应用的业务逻辑的 jar,然后使用 IKVM 将其转换为一个 .dll 文件。Mono Mac 项目使用了 Cocoa 绑定,所以我能够在 interface builder 中建立一个 UI,然后用 C# 编写一些胶水代码,将其与我的应用的业务逻辑相连接。
我从来没有发布过我的应用的 Mono 版本,因为当它接近准备好的时候,Oracle 的 JDK7 就已经有了早期访问版本,这将允许我在发布时基本不做改动,从而大大降低长期的维护工作。
为 App Store 捆绑 App
JDK7 已经问世,唯一的困难就是捆绑原生应用。我所用的老式捆绑器和苹果的 Java 绑定,并没有将 JRE 捆绑到应用中,而是将它和系统中的 Java 安装绑定。在 JDK7 中,你需要将整个 JRE 捆绑到你的应用捆绑器中。这样你的应用会变得更大,但同时让你不再需要依赖过时的 API。这款应用将是独立的。
Oracle 提供了一款工具 javafxpackager,本应帮助你把你的应用捆绑成原生应用,但是却缺乏了 App Store 部署所需的某些重要特性。我只是凭着记忆工作,但是我还记得,除其他之外,在新的应用沙盒中,这款工具并没有起到很好的作用。所有 Mac App Store 的应用都要在其“沙盒”里运行。它们在 ~/Library/Containers/YOUR_APP_ID 目录下有自己的“小游乐场”,它们的所有文件都存储在那里。这只是需要一点额外的照顾和准备。(可这真是一件麻烦的事情!)
我正准备写自己的捆绑器,这时开源社区出现并拯救了我。一位名叫“InfiniteKind”的好心开发者开发或复刻了一个应用捆绑器,它可以与新的 JDK7 一起使用,并包括一些调整以满足苹果 App Store 的要求。谷歌搜索结果表明,这个应用捆绑器项目还在,而且在 README 文档里也还有我的一点贡献。
终于,在首次发布将近一年之后,我获准向 Mac App Store 提交我的应用。由于包含了捆绑的 Java 运行时,我的应用增加了 50 兆字节(压缩过的),但这并不重要,只要它能带来更多的销量。结果是,这并不会对销量造成什么实质性的影响。我猜想,如果没有这种渠道,那些从 App Store 购买的用户也会在我的网站上购买,因此,这其实就是在转移我的销售来源。App Store 的销售额每年都会有一定的增长,但同时网站的销售额会下降。现在,我的大部分销售都来自 App Store。
最后还是成功了……
对于我这种 Java 开发人员而言,在 Mac 上废弃 Java 的决定是一件很痛苦的事情。但是回顾过去,我觉得这样做是对的,也是无法避免的。假如他们当时没有“扣动扳机”,那么他们最后很有可能会被迫作出改变,而他们等得越久,就会越痛苦。
通过将 Mac 移植到标准的 OpenJDK 中,可以保证 Mac 用户可以跟上 Java 的发展。他们不会再受阻,等待一个不情愿的第三方管家来更新他们的版本。
尽管如此,在那个时候,Java 运行时必须和每个应用捆绑,这在那时是不必要的负担,现在仍然如此。史蒂夫·乔布斯曾说过:“没人会用 Java,它就是个巨大的锁链。”(Nobody uses Java anymore. It's this big heavyweight ball and chain.)由于 JRE 捆绑在每个 Java 应用中,所以每次下载应用更新时,用户都会收到提示。当然,在不同的应用中,也有一些共享这个“锁链”的方法。我对这个问题总是感到沮丧。这也是 jDeploy 开发的一个重要原因。
下次,我们将谈论“桌面 Java 的衰落与灭亡”(The Decline and Fall of Java on the Desktop)这一话题。具体来说,它是如何应对桌面上的 HTML5 巨无霸(JavaFX)的,以及它是如何悄悄地将自己定位为当今跨平台桌面开发的最佳平台的。
作者介绍:
Steve Hannah,jDeploy 作者。
原文链接:
https://jdeploy.substack.com/p/how-the-app-store-ended-a-golden?s=r