讓別人的小程序運行在自己的app中

概要

本文包括的內(nèi)容:

  • 小程序在微信開發(fā)者工具中,通過構(gòu)建生成真正的執(zhí)行代碼和安裝包,****.wxapkg。wxml和wxss在構(gòu)建這一步就被轉(zhuǎn)換成了html和css(virtual-DOM)。微信開發(fā)者工具中可以得到構(gòu)建腳本和各個版本的js運行SDK文件。
  • 小程序是以wxapkg文件的形式下發(fā)的,可以拿到wxapkg后解壓拿到執(zhí)行代碼。同時解壓微信.ipa拿到當(dāng)前的js運行SDK文件,主要是WAWebView.js和WAService.js。
  • 小程序在App中執(zhí)行時的時候分為三個不同的模塊,View/Service/Natvie,各司其職。
  • 實現(xiàn)過程遇到大量的細(xì)節(jié)和坑。

介紹小程序原理的文章比較多,這篇講的比較細(xì):微信小程序架構(gòu)分析。這篇文章的作者也成功的實現(xiàn)了wept,讓小程序運行在自己的webapp里。

參考最多的是微店的Hera,完成度非常高的小程序框架,能夠?qū)⑿〕绦虻膁emo代碼在web/iOS/android運行起來,而且實現(xiàn)了很多工具。Hera的問題是開發(fā)于比較早期的版本,不兼容最新的版本了。Hera還有一個問題是他修改了小程序構(gòu)建之后的目錄結(jié)構(gòu),采用了service.html作為service部分的入口,跟小程序本身的實現(xiàn)尚有有一些區(qū)別。所以Hera只能夠構(gòu)建執(zhí)行自己編寫的小程序,不能執(zhí)行別人編寫的小程序。

我的目標(biāo)是能夠運行其它人開發(fā)的app,意味著我只能通過逆向的方式拿到wxapkg。但是因為拿不到源碼,所以要盡可能在構(gòu)建環(huán)節(jié)跟小程序保持一致。

經(jīng)過數(shù)周的掙扎,目前已經(jīng)實現(xiàn)了運行官方demo。已經(jīng)達(dá)到"可行"的階段,但是還遠(yuǎn)遠(yuǎn)談不上“可用”,因為需要實現(xiàn)小程序大量的API,這是個體力活,依賴個人的力量難以完成。

構(gòu)建

官方demo小程序原先的目錄分為幾類文件:

  • 配置: 在這個目錄中,app.json里保存了頁面信息、tabbar信息、網(wǎng)絡(luò)超時配置等等。 config.js保存了騰訊云后臺服務(wù)解決方案的配置。
  • js文件: js定義了各種函數(shù)接口邏輯
  • wxml: 類似于html,定義了頁面結(jié)構(gòu)
  • wxss: 類似css
demo
├── app.js
├── app.json
├── app.wxss
├── config.js
├── image
│   ├── green_tri.png
│   ├── ...
├── page
│   ├── API
│   │   ├── index.js
│   │   ├── index.json
│   │   ├── index.wxml
│   │   ├── index.wxss
│   │   ├── pages
│   │   │   ├── action-sheet
│   │   │   │   ├── action-sheet.js
│   │   │   │   ├── action-sheet.json
│   │   │   │   ├── action-sheet.wxml
│   │   │   │   └── action-sheet.wxss
│   │   │   ├── ...
│   │   └── resources
│   │       └── ...
│   ├── ...
├── project.config.json
├── util
│   └── util.js
└── vendor
    └── qcloud-weapp-client-sdk
        ├── ...

經(jīng)過小程序的開發(fā)環(huán)境構(gòu)建后,生成了一個*.wxapkg文件。
這個文件可以通過從越獄的iPhone或者root的安卓手機(jī)上拿到。有部分人用charles通過https抓包拿到了下載鏈接,也拿到了包。
拿到后要進(jìn)行解包。有大神已經(jīng)通過反編譯安卓apk的方式拿到了解包部分的代碼,然后用python重寫了一遍。源碼見wechat-app-unpack。

解包后得到的目錄如下:

1.wxapkg_dir
├── app-config.json
├── app-service.js
├── app-service.js.map
├── image
│   ├── green_tri.png
│   ├── ...
├── page
│   ├── API
│   │   ├── index.html
│   │   ├── pages
│   │   │   ├── action-sheet
│   │   │   │   └── action-sheet.html
│   │   │   ├── ...
│   │   └── resources
│   │       └── kind
│   │           ├── api.png
│   │           ├── ...
│   ├── ...
└── page-frame.html

轉(zhuǎn)換過程可以分為三部分:

  • 從wxml/wxss到html。 page-frame.html里定義了所有的virtural-dom,來自所有的wxss和wxml的轉(zhuǎn)換,文件非常大。 page.html里很簡單,就是從page-frame.html里提取對應(yīng)的virtual-dom。包括wxss和wxml對應(yīng)的邏輯。
  • app-service.js是從之前所有的js文件轉(zhuǎn)換而來。
  • app-config.json是從app.json轉(zhuǎn)換而來。

openVendor命令可以在小程序中獲取到構(gòu)建腳本wcc和wcsc, 以及各個版本小程序的執(zhí)行SDK ****.wxvpkg,這個SDK也可以用wechat-app-unpack解開,解開后里面就是WAService.js和WAWebView.js等代碼。

wxml/wxss的構(gòu)建原理

wxss 轉(zhuǎn)換成了css,wxml轉(zhuǎn)換成了inject_js,實際上就是virtual_dom。
是用什么工具轉(zhuǎn)換的?小程序里是叫wcc和wcsc。在開源工具h(yuǎn)era自己實現(xiàn)了一套wxss-transpiler和wxml-transpiler。而hera的前身wept是直接使用wcc和wcsc。
我們?yōu)榱藴p少維護(hù)成本,直接采用wcc和wcsc。
因為我們沒有wcc和wcsc的源碼,所以只能借助wxss-transpiler和wxml-transpiler來幫助我們理解wxml/wxss的構(gòu)建原理。

wxss-transpiler調(diào)用了一個PostCSS的插件,用來處理wxss。
PostCSS 提供了一種方式用 JavaScript 代碼來處理 CSS。它負(fù)責(zé)把 CSS 代碼解析成抽象語法樹結(jié)構(gòu)(Abstract Syntax Tree,AST),再交由插件來進(jìn)行處理。插件基于 CSS 代碼的 AST 所能進(jìn)行的操作是多種多樣的,比如可以支持變量和混入(mixin),增加瀏覽器相關(guān)的聲明前綴,或是把使用將來的 CSS 規(guī)范的樣式規(guī)則轉(zhuǎn)譯(transpile)成當(dāng)前的 CSS 規(guī)范支持的格式。

wxml-transpiler:實現(xiàn)了一個轉(zhuǎn)譯器的工作,比如postcss也是轉(zhuǎn)譯器,包括解釋器(parser),代碼轉(zhuǎn)換器(Transformer),代碼生成器(Generator)。這個是閉源的。

  • 根據(jù)輸入的列表,讀取所有文件
  • 調(diào)用VUE的HTML Parser,解析輸入的標(biāo)簽及屬性,生成一顆DOM樹。vue解決不了的js語言,用babylon庫來處理。(Parse)
  • 在解析組件的標(biāo)簽時,對其上包含的屬性值進(jìn)行解析(邊Parse邊Transform)
  • 根據(jù)已有的AST生成JS文件(Generate)

更多實現(xiàn)原理見這篇文章

模塊之前的通信

image

小程序在App中執(zhí)行時的時候分為三個不同的模塊,View/Service/Native,各司其職。

View和Service都在WKWebView中執(zhí)行,互相無法調(diào)用。他們之間通過Native層通信。

Native和WebView之間通過webkit.messagehandler和evaluateJavascript互相調(diào)用。

  • WeixinJSBridge.publish: view和service之間的透傳,在WKWebView之間傳遞消息。

  • WeixinJSBridge.subscribe: 注冊監(jiān)聽,監(jiān)聽view和service之間的消息調(diào)用。

  • WeixinJSBridge.invoke: View或者Service傳遞消息到Native,然后Native使用邏輯調(diào)用js callback。

  • WeixinJSBridge.on:監(jiān)聽Native的事件。

如何執(zhí)行

這里以iOS為例介紹Native執(zhí)行過程。安卓類似。

通過解壓微信的ipa可以拿到WAService.js和WAWebView.js兩個基礎(chǔ)庫文件,文件內(nèi)容與hera的service.js/view.js已經(jīng)有了較大的區(qū)別。
我們采用小程序的架構(gòu)和hera的兩個webView的方案,盡可能模仿小程序的執(zhí)行過程。

View部分

View部分是比較直觀的,就是WKWebView加載web頁。這里需要在app-config.json里讀取到首頁的路徑,然后加載該頁面。這個路徑下的xxx/index.html是無法直接加載的,需要做一些處理。要引入本地執(zhí)行SDK里的index.css和view.js, 然后把page-frame.html里的virtual-dom全部塞進(jìn)該頁面。 然后loadHTML即可。

View所有的WKWebView也是要注冊WKUserContentController的,用于通信。
通過反匯編可以得知這個類在微信中叫YYWAWebView,調(diào)用js是直接調(diào)用-evaluateJavaScript:completionHandler:方法的。

view.js中包含的邏輯:

  • WeixinJSBridge 對象處理消息通信: invoke invokeCallbackHandler on publish subscribe subscribe subscribeHandler。
  • Reporter 對象
  • wxparser 對象,提供 dom 到 wx element 對象之間的映射操作,提供元素操作管理和事件管理功能。
  • virtual dom 渲染算法實現(xiàn),提供 diff apply render 等方法,該模塊接口基本與 virtual-dom 一致,這里特別的地方在于它所 diff 和生成的并不是原生 DOM,而是各種模擬了 DOM 接口的 wx element 對象

Service部分

Service部分的實現(xiàn),Hera和微信小程序采取的了不同的架構(gòu)。
Hera的實現(xiàn)較為簡潔,跟View部分保持一致,采用了WKWebView,調(diào)用-evaluateJavaScript:completionHandler:方法執(zhí)行js,js回調(diào)OC時使用WKScriptMessageHandler。
通過反匯編可以得知這個類在微信中叫WAJSCoreService,js和OC之間的調(diào)用是采用JavascriptCore互相調(diào)用。

JavascriptCore它首先要加載app-config.json并把這個配置賦給一個全局對象__wxConfig。然后他要加載service.js是SDK基礎(chǔ),再然后他要加載app-service.js,這里面包含了用戶編寫的js邏輯。最后它發(fā)出全局消息
WeixinJSBridge.publish('serviceReady',,);</script>喚起小程序app的初始化。

Service.js中包含的邏輯:

  • 跟 view.js 一樣的 WeixinJSBridge 兼容模塊
  • view.js 一樣的 Reporter 模塊
  • appServiceEngine 模塊,提供 Page,App,GetApp 接口

Native部分

Native執(zhí)行的問題比較復(fù)雜,因為基本是黑盒,里面發(fā)生了什么并不知道。
hera的方案在構(gòu)建過程就已經(jīng)跟小程序?qū)嶋H的方案有所區(qū)別,會提高維護(hù)成本。所以我們只能靠猜測來實現(xiàn)Native的執(zhí)行過程。

Native部分就是作為入口,運行環(huán)境,跳轉(zhuǎn),轉(zhuǎn)發(fā)消息,實現(xiàn)擴(kuò)展。包括網(wǎng)絡(luò)模塊/攝像頭/tabbar實現(xiàn)的都是擴(kuò)展。
我們可以得知的是消息傳遞的協(xié)議。然后只能通過safari來調(diào)試webView,根據(jù)協(xié)議的名稱和出入?yún)聿聹y協(xié)議的內(nèi)容。

主要的困難點

  • 由于壓縮后的view.js和service.js基本不具備可讀性,而virtual-dom又徹底不具備可讀性...所以就算猜中了協(xié)議,最后也往往是魔改。可維護(hù)性極差。
  • 工作量不小,因為調(diào)試?yán)щy,無法閱讀。之后隨著小程序SDK升級,能用多久也不可知。加上也有各種微逆向的操作,小程序封上任何一個接口,都會導(dǎo)致?lián)浣???沙掷m(xù)性極差??傊浅E宸⒌甑耐瑢W(xué)能把Hera搞出來:)

小程序的性能啟發(fā)

小程序是顛覆我對Web的固有印象,最初還以為是類似weex或者rn的調(diào)用原生的方式,沒想到幾乎完全是運行在WKWebView之上的。

  • 采用virtual DOM,操作JS比html性能高很多,因為是diff后再操作dom,不需要全部重新渲染,快很多。
  • WKWebView,滑動60fps,在獨立于App之外的進(jìn)程執(zhí)行。
  • 部分邏輯Native化,比如收發(fā)網(wǎng)絡(luò)請求,比如數(shù)據(jù)持久化。 逐步用native組件來替換h5組件。比如tabbar。
  • 重用webView以及提前初始化webView等等技巧。

存在的問題:

  • 看消息傳遞的原理就能發(fā)現(xiàn),傳遞的過程太長了,尤其是setData:這種傳遞整個model對象,是兩次對象的深拷貝,可能會增加兩次json的序列化和反序列化,如果model對象很復(fù)雜對性能影響比較大。
  • 頁面初始化/響應(yīng)速度/UI細(xì)節(jié)還是跟原生有差距。

當(dāng)然已經(jīng)比純web頁強很多了。目前來看還是只適合輕量化的應(yīng)用。受制于架構(gòu)以及微信的平臺,個人認(rèn)為是對Web的替代和改善。但是就算在可見的未來,還是很難跟native抗衡。

現(xiàn)代瀏覽器和操作系統(tǒng)之間的界限越來越模糊。App的"下載/安裝"過程本身就是一種妥協(xié)。只要小程序的體驗足夠好,應(yīng)該沒有人會拒絕。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 每個人都有拖延癥,有自制力、堅持做一件事情,其實很消耗能量,如果總是失敗,其實很傷害自信和意志力。 但如果通過循序...
    曹門霞客行閱讀 591評論 7 20
  • 東野圭吾先生的小說以前沒怎么看過,偶然機(jī)會在朋友的桌頭看到一本他的推理小說《嫌疑人x的獻(xiàn)身》,一時興起用了大概一個...
    吃嗎帶走閱讀 392評論 0 0
  • 感賞今天晚上有很多顧客試衣服,這樣就提高了顧客買單的幾率。 感賞自己因為前天的事情受宇宙爸爸點化,讓我認(rèn)識到了努力...
    離不若閱讀 245評論 0 0

友情鏈接更多精彩內(nèi)容