小程序原理解析(一)

945 阅读5分钟

双线程模型

小程序的渲染层和逻辑层分别由两个线程管理:渲染层的界面使用 WebView 进行渲染;逻辑层采用 JSCore 运行 JavaScript 代码。一个小程序存在多个界面,所以渲染层存在多个 WebView。这两个线程间的通信经由小程序 Native 侧中转,逻辑层发送网络请求也经由 Native 侧转发,小程序的通信模型下图所示。

小程序的双层架构思想可以追溯到 PWA,但又有所扬弃。

PWA小程序框架
逻辑层以 Service Worker 为载体。开发者需编写业务逻辑、管理资源缓存。以 JSCore 或 V8 引擎为载体。开发者只需编写业务逻辑。
渲染层基于 Web 网页的单页或多标签页方案。基于多个 WebView 组成的页面栈。

小程序框架与 PWA 相比,小程序的开发者可以更聚焦于业务逻辑,而无需关注静态资源的缓存。小程序包的缓存和更新机制交由小程序框架自动完成,开发者可以在适当时机通过 API 影响这一过程。小程序的渲染层由多个 WebView 组成的页面栈构成,这与 PWA 相比有着更接近移动端原生应用的用户体验。同时,小程序的开发者也能更从容地处理多页面间跳转时页面状态的变化。

类似于微信 JSSDK 这样的 Hybrid 技术,微信小程序的界面主要由成熟的 Web 技术渲染,辅之以大量的接口提供丰富的客户端原生能力。同时,每个小程序页面都是用不同的 WebView 去渲染,这样可以提供更好的交互体验,更贴近原生体验,也避免了单个 WebView 的任务过于繁重。此外,界面渲染这一块我们定义了一套内置组件以统一体验,并且提供一些基础和通用的能力,进一步降低开发者的学习门槛。值得一提的是,内置组件有一部分较复杂组件是用客户端原生实现的同层渲染,以提供更好的性能。

为什么要这么设计呢?

为了管控和安全,微信小程序阻止开发者使用一些浏览器提供的,诸如跳转页面、操作 DOM、动态执行脚本的开放性接口。将逻辑层与视图层进行分离,视图层和逻辑层之间只有数据的通信,可以防止开发者随意操作界面,更好的保证了用户数据安全。

微信小程序视图层是 WebView,逻辑层是 JS 引擎。三端的脚本执行环境以及用于渲染非原生组件的环境是各不相同的:

运行环境逻辑层渲染层
AndroidV8Chromium 定制内核
iOSJavaScriptCoreWKWebView
小程序开发者工具NWJSChrome WebView

我们看一下单 WebView 实例与小程序双线程多实例下代码执行的差异点。

单 WebView 模式下,Page 视图与 App 逻辑共享同一个 JSContext,这样所有的页面可以共享全局的数据和方法,能够实现全局的状态管理。多 WebView 模式下,每一个 WebView 都有一个独立的 JSContext,虽然可以通过窗口通信实现数据传递,但是无法共享数据和方法,对于全局的状态管理也相对比较复杂,抽离一个通用的 WebView 或者 JS Engine 作为应用的 JSContext 就可以解决这些问题,但是同时引入了其他问题:视图和逻辑如何通信,在小程序里面数据更新后视图是异步更新的。

双线程交互的生命周期图示:

开发工具

微信开发者工具是基于 NW.js 构建,主要由工具栏、模拟器、编辑器、调试器四大部分组成。通过 微信开发者工具 => 调试 => 调试微信微信开发者工具 可以打开小程序 IDE DevTools 面板。通过 DevTools 审查我们可以发现模拟器是通过 WebView 展示页面。微信小程序是双线程的设计,所以存在视图层和逻辑层两个 WebView。

调试逻辑层

在微信开发者工具中,Workbench 的 DevTools 调试器默认与模拟器逻辑层连接,所以 DevTools 中的 Console 面板进行输入 JS 脚本,JS 脚本实际的执行环境是逻辑层的 JS Context,对于逻辑层的调试,可以直接在调试器中进行。编译运行你的小程序项目,然后打开控制台,输入 document 并回车,就可以看到小程序逻辑层 WebView,如下图:

调试视图层

模拟器视图层 WebView 的就相对逻辑层麻烦一些,需要在 IDE 的 DevTools 下中注入 JS 打开视图层 WebView 的 DevTools。

IDE DevTools 面板的 Console Panel 输入:

// 查找 WebView 元素
$$('webview')
// 打开 视图层 WebView DevTools
$$('webview')[0].showDevTools(true)

然后就可以在视图层 WebView 的 DevTools 中进行调试了。

逆向技巧

获取基础库

我们如何能够拿到视图层和逻辑层 WebView 加载的文件呢?

  • 基于 Sources 面板的 Save AS 功能获取代码

  • 基于开发者工具内置命令 openVendor() 找到 .wxvpkg 包获取代码 在开发者工具中使用 help() 方法,可以查看一些指令和方法。

openVendor 命令可以打开微信开发者工具在小程序框架所在目录。我们可以在微信小程序 IDE 控制台输入 openVendor 命令,可以打开微信小程序开发工具的资源目录:

我们可以看到有 wccwcsc,小程序各版本的基础库包 .wxvpkg.wxvpkg 文件可以使用 wechat-app-unpack 解开,解开后里面就是 WAService.js 和 WAWebView.js 等代码。

  • 利用 apktool 反编译微信客户端

我们可以找到 wxa_library 文件夹,这个和上面微信开发工具中 .wxvpkg 包解开的结构很类似,这就是小程序的基础库。

反编译代码

python2 unwxapkg.py [filename]

解开后的目录结构:

客户端中的 .wxvpkg 包比 IDE WeappVendor 文件夹下的包多一些文件。

find . -type f -name '*.js' -exec js-beautify -r -s 2 -p -f '{}' ;
  • 利用 jsnice 美化代码