Cordova 源码解析笔记

Cordova 是一个支持使用 html, css, javascript 来进行跨平台开发的开源框架,前身是 PhoneGap, 但是其与 ReactNative 不同,Cordova 是通过各平台内 webview 来进行 UI 展示,而非解释成 native code 的方式。

由于最近在使用 Cordova 进行项目开发,所以抽时间阅读了下 iOS 端源码并把相关解读记录下来。
这篇文章对应的 Cordova native platform 版本号是 4.5.5 。

CLI 简易入门

Install npm install -g cordova
Create Project cordova create <path>
Add Platform cordova platform add <platform name>
Run cordova run <platform name>

Cordova 架构

项目结构

下面是使用 CLI 建项目之后的目录结构

-- Hooks
Hooks 中可以存放一些特殊脚本,由开发者自行添加,自定义一些 cordova 命令,cordova 允许我们围绕生命周期编写钩子函数来进行一些自定义的操作。

-- Platforms
我们初始化时选择或者之后添加的平台代码在这个文件夹中,cordova 默认不允许我们修改其中的代码,因为执行一些操作时可能会被重新覆盖。

-- Plugins
Cordova 中,称提供原生功能的代码模块为插件,这个文件夹就存放一些 cordova 带的插件以及开发者自己添加的插件。

-- Res (Resource)
存放一些资源文件,图片,字体等等。

-- www
html,css,js 文件的根目录。

接下来我们重点来看一下 cordova iOS 部分:

-- www/staging
config 文件, cordova js 相关部分代码以及开发的 www 文件夹下相关代码文件的引用。

-- Public
iOS 原生相关大部分核心代码存放在这个文件夹下。

-- Private
一些私有方法,和 Cordova 核心插件比如 Logger, 手势, Local Storage, Webview 引擎等。

-- Plugins
外部使用的第三方插件。

总体架构

官网的这张图描述了 Cordova 总体架构设计:
cordova-arch

启动入口

Native Side

CDVAppDelegate

Cordova 工程默认使用了自己的 delegate 和 view controller 来接管启动 pipeline 。
Delegate 初始化时设置了 cookie 策略,以及 NSURLCache 的缓存大小,内存 8M, 硬盘 32M。
而 finishLaunch 则没有进行过多的操作,只是初始化 window, root view controller,而主要的启动相关工作则集中在了 CDVViewController 的 init 以及 viewDidLoad 中。

CDVViewController
初始化 CDV VC 时会初始化 Command 队列,监听一些系统回调,打印一些硬件或者版本信息之类的,而 viewDidLoad 后做了大量的配置工作

首先是 load settings 信息,这时会加载 config.xml 文件并解析读取配置, 确定 webview 要 load 的首个页面,默认是 index.html。之后会设置 storage 的备份模式,默认是 cloud, 但由于 iCloud 特性是 iOS6 才有,cordova 会针对备份以及本地 database 的存储位置做一次 fixup。再然后便是调用 createGapView 来创建 webview 了, cordova 默认使用自己的 UIWebview Engine 来创建 UIWebView 类型的实例用于界面展示,开发者也可以自己实现一个插件使用 WKWebView 等等。


这段代码则是开始启动每一个 Plugin, 并借助 CDVTimer 来对每一个插件的启动进行打点计时,并且会输出到控制台上。之后会开始 load 首页页面, 并设置背景颜色。
看到这, Cordova iOS 端的全部初始流程大概就全跑完了,总体的流程就是

初始化配置->加载 config->fixup 备份方式以及数据库位置->创建 webview->加载插件->展示首页

JS Side

index.html 默认会加载 cordova.js ,下面来看下这个 js 文件。

初始定义 require/define 的逻辑实现了一个简易的模块系统。
然后通过 window.cordova = require('cordova'); 加载 window 的 cordova 实例。
cordova 实例作为外部访问 cordova.js 的入口,定义和记录了一些 event handler 桥接方法。

上图是触发 dom, window event 的方法。这里比较特殊的是 devicereadey,cordova 会在环境加载完成时 fire deviceready 的事件回调出来,项目 index.js 默认就有一个对该事件的监听如下图所示:
index.js

启动完成我们就可以做一些自定义的操作了,这里的示例是操作 DOM 修改了样式。

在 cordova/channel 模块,有一段注释描述了 cordova 启动期间的事件顺序:

  1. onDOMContentLoaded(内部事件通道)页面加载后 DOM 解析完成
  2. onNativeReady (内部事件通道) cordova 的 native 准备完成
  3. onCordovaReady (内部事件通道) 所有 cordova 的 JS 对象被创建完成可以开始加载插件
  4. onDeviceReady cordova 全部准备完成

还有另外两个外部事件 onPause (应用退到后台), onResume (应用返回前台)。
channel 模块定义了这些内部以及外部订阅的事件通道,提供了一个自定义的 pub-sub 模型来控制事件什么时候以什么样的顺序被调用,以及各个事件通道的调用。

通信机制

作为一个移动端的 web 开发平台,必定要自己定义通道来完成 native 与 js 的通信。

Native to JS

onResume 事件为例,当 native 端收到 app 进入前台的回调 onAppWillEnterForeground 时, CDVViewController 会调用

[self.commandDelegate evalJs:@"cordova.fireDocumentEvent('resume');"];

而 commandDelegateImpl 实例的代码如下:

而 evalJsHelper 和 evalJsHelper2 最终会使用 UIWebview 的 stringByEvaluatingJavaScriptFromString 方法来访问 cordova 执行上面的 fire function 。

JS to Native

Cordova 有很多的 native code 实现的插件,在 js 中我们需要调用这些插件来访问原生提供的功能,下面以 cordova-plugin-x-socialsharing 为例,看下是如何完成 js -> native 的调用。

安装插件后,来到插件 www 目录下的 SocialSharing.js 文件,发现所有的方法最后都会调用到 cordova.exec:

在 cordova/init 以及 cordova/init_b 中


exec 都被 map 到了 cordova/exec,再来看 cordova/exec 这个模块

这个 proxy 会区分平台来决定具体如何执行,比如 iOS 会选择使用 iOSExec

首先会准备参数,拼装成 command json 对象然后加到 commandQueue 中,之后调用 pokeNative 方法来通知 native 来 fetch 新的 command,而触发的方式是通过往 html 的 body 中插特殊的 iframe 来让 native 感知

        execIframe = document.createElement('iframe');
        execIframe.style.display = 'none';
        execIframe.src = 'gap://ready';
        document.body.appendChild(execIframe);

而 native 端通过 webview delegate 的 - (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType 方法发现 url scheme 为 gap 则获取并执行。
JS -> iOS Native 的总体流程为:

call plugin function -> 准备 command 并 enqueue -> 设置 iframe location=gap://xxx -> native webview delegate 拦截到该 scheme -> native 从 js 端获取 command queue -> 派发执行 command

总结

到这里,Cordova 的基础原理,启动流程以及通信方式都已经介绍完了,还有一些其他的点这篇文章里没有提到比如 native 端如何派发到各个插件执行命令, UA 设置工具,cordova 的一些其他 js 模块比如 build, modulemapper, utils 等等。一开始研究 Cordova 是考虑选择学习下主流的 hybrid 框架,但是 Cordova 严格意义来说并不是一个完全的 "hybrid",它主张尽可能使用 web 来进行所有的功能实现,而只有需要 native 的地方才使用它的插件机制来实现。
接下来我还会投入时间研究下其他的 hybrid 相关的内容,争取总结出一个比较灵活完善的 hybrid 方案。

-- EOF --
以上就是这篇文章全部内容,欢迎提出建议和指正,个人联系方式详见关于

Comments
Write a Comment