通过 GeckoView 内核统一 Android 平台下 Ionic App 的用户体验

使用 Web 技术栈绘制 GUI 基本上已经成为公认的、成本效益最为平衡的一种解决方案。无论是桌面端、移动端,甚至是你在使用的操作 系统也充斥着大量的 WebView。从十年前(掰手指——)我还是大学生的时候这股「妖风」就刮了起来,Intel 造了个叫做 XDK 的东西, IBM 甚至还自己裁切了一个叫做 CrossWalk 的嵌入式 WebView 引擎(虽然现在已经没人维护了)。

经历了这么多年的发展,在移动端用 Web 技术栈开发混合程序依然是一个非常麻烦的事情。造成这一景象的原因有两方面,一方面是 Chromium 本身与 Android 系统高度耦合(Android 里有些 API 是专门给 Chromium 做的)、内部工程实践混乱、剪裁难度极大,另外 一方面,Android 本身碎片化非常严重,各中「发展中国家」「自研」的「OS」还会用各种方式作妖,比如之前臭名昭著的 MIUI ,内置 浏览器虽然看起来规矩,但是会假装自己是高版本浏览器,还有一些民间 ROM 会裁掉一些浏览器的 API,也不知道是为了什么。

为了应对这个问题,各个「大厂」基本都有自己的应对,字节有一个自己剪裁的 WebView;腾讯有两个,微信他们自己裁了一个,还有 一个天天出毛病的 X5;阿里有俩,支付宝他们自己有一个,UC 还有一个。也有一些团队会把 Web 标准当中的某一部分单独裁出来用, 腾讯、美团、字节内部都有一个 WebGL 标准的 Native Binding,用来渲染一些活动页面。

但是因为这一部分工作和业务高度耦合,因此没有任何一个大厂将自己的实现开源,唯一的一个闭源实现腾讯 X5 也是问题一大堆根本 没法用。

但开源界还是有「人类的希望」,Mozilla 把 Firefox 的内核单独做了一个组件化封装,造了一个气氛上还算能用的产品GeckoView

知道这个东西的人其实很少,下手适配它的混合开发框架更少。但在做交互视频项目的时候我们还真的很需要一个能够自主控制内核版本的 功能。所以就着手把它跟 Ionic 集成了一下。于是就有了 @web-media/capacitor-geckoview

README

先叠个甲,这东西的实现原理是直接给 @capacitor/android 打 Patch,因此插件版本跟 Capacitor 版本是有强制绑定的,目前我们 的实现只支持到 4.6.0 版本,最新版的 Patch 我们已经打了,但是编译还跑不通,群友还在抢修。

另外一方面,这个插件并不是我独立完成的,Android 端由抱枕开发,而 Web 端是我和 Hyper 一起做的,我在这里仅出一份技术介绍。

安装项目

如果你想要把 Capacitor 的 WebView 换成 GeckoView,要做的第一件事情是改一下 package.json 里面的解析安装包解析,插一个 叫做 resolution 的 field,并写下这些信息:

1
2
3
"resolutions": {
"@capacitor/android": "npm:@web-media/capacitor-geckoview@2.0.0",
}

一定要确保你的 Capacitor 版本是 4.6.0,并且 @web-media/capacitor-geckoview 的版本号被锁定在了 2.0.0 否则很有可能会 出现不可预知的 bug。

接下来,正常配置你的 Ionic 项目工程,在生成出来的 Android 工程里,做一点小修改。打开 android/build.gradle,在 allprojects 小节下面找到 repositories,在末尾加上如下配置:

1
2
3
maven {
url "https://maven.mozilla.org/maven2/"
}

接下来你就可以正常 sync、编译了。没有什么额外的东西需要配置,任何 Native 插件的开发、集成理论上讲也不会受到影响,唯一 需要改变的就是调试方法,从 Chrome 的开发者工具变成 Firefox 的调试工具。

一些你可能不想知道的技术细节

Ionic/Capacitor 的整体架构主要分成了三层:插件、Ionic 胶水、Web 层。这几层切的非常干净没有任何耦合,所以修改中间的胶水层 原则上不会对其他层由任何影响。

具体修改的地方有这些:

Android层面的修改

  • WebView 的替换: 字面意义上把系统的 WebView 换成 GeckoView。
  • Web 文件请求的替换: 和 Android 内置的 WebView 以及 iOS 的 WebView 不同,GeckoView 本身没有办法造一个虚拟文件协议, 或者拦截文件传输,所以我们做了一点比较脏的工作,直接开了个随机端口的 HTTP 服务器,让 WebView 去访问它。
  • 通信功能的注入: 重新绑定了一下插件和 Web 容器之间的通信协议,本质还是两边飞 post message,没什么特别的。

Web容器内部的修改

  • 实现一个插件用来注入脚本: 由于 GeckoView 不支持直接向页面当中脚本注入(当然其他家都支持¯\(ツ)/¯),Hyper 写了一个 Firefox 插件来单独处理这一块的问题。
  • 插件与Web内容的初始化时序问题: 我们发现插件加载总是比 Web 内容慢,这可能导致早期通信丢失。为此,我们在 Ionic 的通信桥 上进行了改造,创建了一个队列来缓存所有通信,直到插件加载完成后再一次性发送。
  • 随机端口的数据同步: 既然端口是随机的,那肯定会涉及到跨域、localStorage 同步之类的问题,这一块也是在插件层面上做了一点 小处理。

一些你可能需要注意的坑

  • 因为混合开发场景下对 Cookies 的需求很罕见,所以我们没有实现这块功能,如果你有需要的话可以发 PR 把这部分东西补上,它需 要后面额外接一个 SQLite 做数据存储。
  • 在一些极端设备上,初次启动应用的时候插件没有办法被初始化(怀疑是 GeckoView 本身有啥 Bug),所以我们内置了一个定时器, 如果五秒钟内,Native 与 Web 之间的通信机制都没有建立起来就会强制刷新页面,这个情况非常罕见所以你可能不用太在意它。
  • Again, 5.7.0 的 Patch 目前有 bug,抱枕正在处理,预计下周就会上了。

如果你在使用者东西的时候遇到了什么问题,请用英文@web-media/infrastructure 的 issue 区联系我们,有时间的时候我们会尽量回复。

以上,Happy Coding!

Comments

No comments here,

Why not write something?