混合開發(fā)的直白解釋是 Native 和 Web 技術(shù)都要用兔辅。但形式上,應(yīng)用仍然和瀏覽器無關(guān)击喂,用戶還是需要在 App Store 和 Android Market 下載應(yīng)用维苔。只是在開發(fā)時(shí),開發(fā)者以 Native 代碼為主體懂昂,在合適的地方部分使用 Web 技術(shù)介时。比如在 iOS 中的 UIViewController 內(nèi)放置一個(gè) UIWebview(一個(gè)瀏覽器引擎,只擁有渲染 HTML凌彬,CSS 和執(zhí)行 JavaScript 的核心功能)沸柔。這樣,部分用戶界面就可以在 UIWebView 中使用 Web 技術(shù)實(shí)現(xiàn)铲敛。
促使我們?cè)谝苿?dòng)開發(fā)中使用 Web 技術(shù)主要?jiǎng)恿υ谟诤峙欤啾扔?Native 技術(shù),Web 技術(shù)具有諸多優(yōu)勢(shì):
高效率的界面開發(fā):HTML原探,CSS乱凿,JavaScript 的組合被證明在用戶界面開發(fā)方面具有很高的效率。
跨平臺(tái):統(tǒng)一的瀏覽器內(nèi)核標(biāo)準(zhǔn)咽弦,使得 Web 技術(shù)具有跨平臺(tái)特性徒蟆。iOS 和 Android 可以使用一套代碼。
熱更新:可越過發(fā)布渠道自主更新應(yīng)用型型。
這些優(yōu)勢(shì)都和開發(fā)效率有關(guān)段审。Web 技術(shù)具有這些優(yōu)勢(shì)的原因是,Web 技術(shù)是一個(gè)開放標(biāo)準(zhǔn)闹蒜∷峦鳎基于開放的標(biāo)準(zhǔn)已經(jīng)發(fā)展出來龐大生態(tài),而且這個(gè)生態(tài)從 PC 時(shí)代發(fā)展至今已積累多年绷落,開發(fā)者可以利用生態(tài)中產(chǎn)出的各種成果姥闪,從而省去很多重復(fù)工作。
在大型移動(dòng)應(yīng)用的開發(fā)中砌烁,項(xiàng)目代碼龐雜筐喳,通常還需要 iOS催式,Android,移動(dòng) Web 和 桌面 Web 全平臺(tái)支持避归。這種情況下荣月,更高的開發(fā)效率就成了開發(fā)者不得不考慮的議題。這也是為何雖然移動(dòng)端的 Web 技術(shù)在使用范圍和性能上有諸多劣勢(shì)梳毙,仍然有很多開發(fā)者付出努力哺窄,探索如何在移動(dòng)開發(fā)中使用 Web 技術(shù)。
我們也是基于以上各種考慮账锹,決定在豆瓣的移動(dòng)開發(fā)中實(shí)踐一些混合開發(fā)技術(shù)萌业。
Rexxar 的背景
豆瓣在 2014 年推出一個(gè)主應(yīng)用:豆瓣 App。這個(gè)主應(yīng)用慢慢成長牌废,逐漸覆蓋了豆瓣在 Web 上的大部分功能咽白。隨著項(xiàng)目的擴(kuò)大,產(chǎn)品線的擴(kuò)展鸟缕,豆瓣App 成為了一個(gè)需要同時(shí)提供 iOS晶框,Android 和移動(dòng) Web 頁面的多平臺(tái)支持的服務(wù)。工程技術(shù)團(tuán)隊(duì)為了更從容地應(yīng)對(duì)這種狀況懂从,開始投入較大的精力提高團(tuán)隊(duì)的開發(fā)效率授段。混合開發(fā)是其中主要的措施之一番甩。
由于項(xiàng)目已經(jīng)發(fā)展到一定程度侵贵,我們并不希望推倒以往的開發(fā)方式,也沒有一切從頭來的野心和勇氣缘薛。只是希望在不影響 App 的性能前提下窍育,在合適的地方使用 Web 技術(shù)部分地提高開發(fā)效率。而豆瓣App 中又確實(shí)存在部分頁面是重度展示宴胧,并輕度交互的漱抓。這些頁面恰恰適合使用 Web 技術(shù)來實(shí)現(xiàn)。
經(jīng)過團(tuán)隊(duì)的一些努力恕齐,項(xiàng)目中部分頁面已經(jīng)使用 Web 技術(shù)實(shí)現(xiàn)乞娄,并取得了不錯(cuò)的效果。工程師使用 Web 技術(shù)可以以更快的速度完成產(chǎn)品需求显歧,并且將一份代碼部署到兩個(gè)平臺(tái)仪或。開發(fā)效率得到了實(shí)質(zhì)性提高。即使不提熱更新士骤,減少 Android 項(xiàng)目方法數(shù)這種附帶好處范删,我們也已喜歡上這項(xiàng)技術(shù),決定在豆瓣移動(dòng)開發(fā)中推動(dòng)混合開發(fā)技術(shù)的使用拷肌。
現(xiàn)在瓶逃,我們將這個(gè)過程的主要產(chǎn)出:Rexxar 這個(gè)項(xiàng)目開源束铭。一方面廓块,是為了給大家提供一些借鑒的方向厢绝;另一方面,是為了提高項(xiàng)目本身的質(zhì)量带猴。我們知道還存在不少問題昔汉。所以,會(huì)悉心接受大家的意見和建議拴清。
Rexxar 的介紹
Rexxar 是一個(gè)針對(duì)移動(dòng)端的混合開發(fā)框架“胁。現(xiàn)在支持 Android 和 iOS 平臺(tái)。并有一個(gè) Web 基礎(chǔ)庫口予。
團(tuán)隊(duì)中喜歡玩魔獸的同學(xué)將該項(xiàng)目命名為 Rexxar(《魔獸世界》中人物娄周,出生于卡利姆多大陸的菲拉斯,同時(shí)具有雷骨獸人和南部菲拉斯野生食人魔兩種血統(tǒng))沪停。
各平臺(tái)代碼倉庫地址如下:
Rexxar Web:https://github.com/douban/rexxar-web
Rexxar iOS:https://github.com/douban/rexxar-ios
Rexxar Android:https://github.com/douban/rexxar-android
Rexxar 主要由以下三部分組成:
Rexxar Route煤辨,我們使用 URL 來標(biāo)識(shí)每一個(gè)頁面。在 App 中通過指明 URL 跳轉(zhuǎn)到此頁面木张。所以众辨,需要一個(gè)路由表。通過路由表可以根據(jù) URL 找到一個(gè) Rexxar Web 的對(duì)應(yīng)資源來正確展示相應(yīng)頁面舷礼;
Rexxar Web鹃彻,前端代碼庫,由 HTML妻献、CSS蛛株、JavaScript、Image 等組成育拨,用來提供在移動(dòng)客戶端使用的用戶頁面谨履;
Rexxar Container,一個(gè)前端代碼的運(yùn)行容器至朗。它其實(shí)是一個(gè)內(nèi)嵌的瀏覽器(WebView)屉符,我們?yōu)閮?nèi)嵌瀏覽器提供了一些必要的原生端支持,包括 API 的 OAuth 授權(quán)锹引、圖片緩存矗钟、Native UI 組件的調(diào)用等;現(xiàn)在有 Android 和 iOS 兩個(gè)版本的實(shí)現(xiàn)嫌变。
在項(xiàng)目實(shí)踐中吨艇,Rexxar Web 和 Rexxar Route 由一個(gè)項(xiàng)目實(shí)現(xiàn),并部署于同一個(gè) Web 項(xiàng)目中腾啥。
Rexxar Route
Rexxar Route 比較簡(jiǎn)單东涡,只需要表達(dá)一個(gè)路由表即可冯吓。我們使用了一個(gè) json 文件來表達(dá)路由表。給出一個(gè)路由表的例子:
{
count: 4,
items: [{
remote_file: "https://img1.doubanio.com/dae/rexxar/files/orders/orders-70dbdbcb1c.html",
uri: "douban://douban.com/orders[/]?.*"
}, {
remote_file: "https://img1.doubanio.com/dae/rexxar/files/related_doulists/related_doulists-1d7d99e1fb.html",
uri: "douban://douban.com/(tag|tv|movie|book|music)/(\w+)/related_doulists[/]?.*"
} ],
deploy_time: "Fri, 04 Mar 2016 11:12:29 GMT"
}
我們發(fā)布的每個(gè)版本的 App 安裝包都會(huì)包含最新版本的 routes.json 文件疮跑。在 App 啟動(dòng)時(shí)组贺,都會(huì)嘗試下載最新版本的 routes.json。在遇到無法解析的 URL 時(shí)祖娘,也會(huì)去下載新版 routes.json失尖。
Rexxar Web
Rexxar Web 是 Rexxar 前端實(shí)現(xiàn)。Rexxar Container 的實(shí)現(xiàn)和 Rexxar Web 的實(shí)現(xiàn)是分離的渐苏。Rexxar Container 對(duì) Rexxar Web 使用何種技術(shù)實(shí)現(xiàn)并不關(guān)心掀潮。所以,你可以選擇自己的前端技術(shù)和 Rexxar Container 進(jìn)行組合琼富。比如仪吧,我們?cè)跇I(yè)務(wù)層選擇了 React 作為前端開發(fā)框架。
Rexxar Web 包括了三部分內(nèi)容:
工具
一套開發(fā) Rexxar Web 所需的打包鞠眉,調(diào)試薯鼠,發(fā)布工具。
公共的前端組件
通用的錯(cuò)誤處理凡蚜、Loading等效果
頁面點(diǎn)擊反饋效果
List 的支持
對(duì) Rexxar Container 實(shí)現(xiàn)的 Widget 的調(diào)用
ActionBar 的 title 定制
ActionBar 的 button 定制
Dialog
下拉刷新
Toast
有了這些組件人断,我們?nèi)粘.a(chǎn)品開發(fā)的難度就降低了。普通移動(dòng)開發(fā)工程師經(jīng)過一段時(shí)間的學(xué)習(xí)朝蜘,也可以像前端工程師一樣恶迈,以 Rexxar 為工具為 App 做一些產(chǎn)品開發(fā)了。這部分可以視為一個(gè)純粹的前端項(xiàng)目谱醇。
Rexxar Container
我們使用混合開發(fā)技術(shù)提高開發(fā)效率的一個(gè)前提是暇仲,不損傷 App 的使用體驗(yàn)「笨剩基于這個(gè)前提奈附,在 Native 和 Web 如何分工方面我們做了一些嘗試。首先煮剧,為了保證使用體驗(yàn)斥滤,我們把 App 里頁面切換留給了 Native。這樣勉盅,每個(gè)頁面(Controller 或者 Activity)都是一個(gè) Container佑颇。Container 內(nèi)嵌一個(gè)瀏覽器內(nèi)核。頁面內(nèi)的功能和邏輯在 Native 和 Web 之間如何分工呢草娜?我們嘗試過有幾種策略:
純?yōu)g覽器方案:也就是 Native 除了扔給內(nèi)嵌瀏覽器一個(gè) URL 地址之外挑胸,就不做任何事情了,剩余的事情都由 Web 完成宰闰。這和用 Safari 或 Chrome 等普通瀏覽器打開一個(gè)網(wǎng)頁并沒有太多區(qū)別茬贵。只是我們固定了訪問的地址簿透。
前端模板渲染容器方案:這種方案大部分事情由 Native 完成,Web 部分只是負(fù)責(zé)頁面元素的呈現(xiàn)解藻,不參與頁面界面之外的其他部分老充。我們?cè)诳蛻舳舜鎯?chǔ)了一個(gè) HTML 作為 UI 模板。Native 代碼負(fù)責(zé)獲取數(shù)據(jù)舆逃,向 HTML 文件模板中填入動(dòng)態(tài)數(shù)據(jù)蚂维,得到一個(gè)可以在內(nèi)嵌瀏覽器渲染的 HTML 文件。這個(gè)過程有點(diǎn)類似于 Web 框架里模板渲染庫(例如路狮,Jinja2)的作用。
Rexxar Container 方案:Rexxar 采用的方案介于上述兩種方案之間蔚约。Rexxar Container 同樣提供了一個(gè)運(yùn)行前端代碼的容器奄妨。它也是一個(gè)內(nèi)嵌的瀏覽器(WebView)。只是苹祟,我們并不是只扔給內(nèi)嵌瀏覽器一個(gè) URL 地址就放手不管了砸抛,還對(duì)對(duì)內(nèi)嵌的瀏覽器做了很多開發(fā),為其包裝了很多附加功能树枫。
Rexxar Container 方案中直焙,Container 需要實(shí)現(xiàn)以下功能:
Rexxar Route 路由表的更新,已經(jīng)在客戶端的保存砂轻;
為 Rexxar Web 前端代碼發(fā)出的 API 請(qǐng)求提供包裝奔誓。帶上必要的 OAuth 參數(shù);
緩存 Rexxar Web 前端代碼所需要的靜態(tài)文件搔涝,包括 HTML厨喂、CSS、JavaScript庄呈、Image(圖片素材)等蜕煌;
緩存 Rexxar Web 中所需要加載的資源文件,例如圖片等诬留;
通過協(xié)議為 Rexxar Web 提供一些原生支持的功能:包括 Native UI 組件調(diào)用斜纪,獲取 Native 的計(jì)算結(jié)果。
這種實(shí)現(xiàn)方案文兑,是基于保證使用體驗(yàn)的前提下盒刚,盡量讓 Web 技術(shù)多做一些事情的考慮。
Rexxar Container 和 Rexxar Web 之間的交互
混合開發(fā)實(shí)踐中彩届,一般都會(huì)涉及到 Native 和 Web 如何通信的問題伪冰。這是因?yàn)槲覀儼岩患虑榻唤o兩種技術(shù)完成,那么它們之間便會(huì)存在有一些通信和協(xié)調(diào)樟蠕。很多混合開發(fā)方案會(huì)使用 JSBridge(Android: JsBridge贮聂,iOS:WebViewJavascriptBridge) 來實(shí)現(xiàn) Native 和 Web 的相互調(diào)用靠柑。
但在 Rexxar 中,我沒有使用類似 JSBridge 這樣的方案吓懈。而是通過從 Rexxar Web 發(fā)出 HTTP 請(qǐng)求的方式歼冰,由 Rexxar Container 截獲的方式進(jìn)行通信。Native 和 Web 之間協(xié)議是由 URL 定義的耻警。Rexxar Web 訪問某個(gè)特定的 URL, Rexxar Container 截獲這些 URL 請(qǐng)求隔嫡,調(diào)用 Native 代碼完成相應(yīng)的功能。
例如甘穿,Rexxar 中 UI 相關(guān)的功能的協(xié)議如下:
請(qǐng)求 douban://rexxar.douban.com/widget/nav_title腮恩,可以定義 Navigation Bar Title毅往。
請(qǐng)求 douban://rexxar.douban.com/widget/nav_menu寥裂,可以定義 Navigation Bar Button呛梆。
請(qǐng)求 douban://rexxar.douban.com/widget/toast全谤,可以出現(xiàn)一個(gè)消息通知 toast吠谢。
Rexxar Web 具體前端實(shí)現(xiàn)是在 DOM 中加入一個(gè) iframe 來加載此 URL合是,以來完成對(duì) Rexxar Container 的通知火窒。
將 Native 和 Web 的通信以協(xié)議的形式規(guī)范起來甚垦,是因?yàn)槲覀兿M?Native 和 Web 之間的通信是可定義的届垫,可控的释液。有這種期望的原因是,我們以 Rexxar 完成的頁面装处,不僅僅在 App 內(nèi)使用误债,還會(huì)在移動(dòng) Web 頁面上使用。我們的移動(dòng)站點(diǎn)符衔,特別是分享到外部(如微信找前,微博)的頁面希望復(fù)用 Rexxar 在 App 內(nèi)的工作成果。如果判族,任由開發(fā)者自由地定義接口隨意的依賴于原生實(shí)現(xiàn)的功能躺盛,那么我們就無法順利地遷移到移動(dòng) Web 上去。標(biāo)準(zhǔn)瀏覽器并不支持 JSBridge 的大部分功能形帮。但可以看到我們已經(jīng)實(shí)現(xiàn)的協(xié)議槽惫,大部分在移動(dòng) Web 是被可以自動(dòng)被忽略(比如,nav_title, nav_menu)辩撑,或者我們也可以較容易地以移動(dòng) Web 支持的形式再實(shí)現(xiàn)一次(比如界斜,toast)。這樣合冀,Rexxar 中的前端業(yè)務(wù)代碼無需太多改動(dòng)各薇,即可遷移到移動(dòng) Web 和桌面 Web 端。
Rexxar Container 的技術(shù)實(shí)現(xiàn)
Rexxar Container 主要的工作是截獲 Rexxar Web 的數(shù)據(jù)請(qǐng)求和原生功能請(qǐng)求。Rexxar Container 截獲請(qǐng)求之后峭判,做相應(yīng)的反應(yīng)开缎。這種 Native 和 Web 的交互被抽象成三種接口:
Decorator:修改數(shù)據(jù)請(qǐng)求。例如林螃,數(shù)據(jù)請(qǐng)求加上 OAuth 認(rèn)證信息奕删。
Widget: 調(diào)用某些 Native UI 組件。例如疗认,調(diào)起一個(gè) Toast完残。
ContainerAPI:給 Web 一個(gè) Native 的計(jì)算結(jié)果。例如横漏,給出當(dāng)前位置信息谨设。
這三種接口都是由 Rexxar Web 發(fā)起某種形式的 URL 調(diào)用的。Rexxar Web 的業(yè)務(wù)代碼在 App 的 Rexxar Container 內(nèi)工作方式绊茧,和在普通瀏覽器里差別不大铝宵。我們只是在 Web 技術(shù)的基礎(chǔ)上做了一些拓展,保留了大部分 Web 原有的編寫和運(yùn)行方式华畏。代碼都是標(biāo)準(zhǔn) Web 式的,沒有為原生移動(dòng)開發(fā)做太多定制尊蚁。因此亡笑,移植到 Web 平臺(tái),在各種瀏覽器中横朋,代碼無需做太多修改就可以正確運(yùn)行仑乌。以 URL 作為協(xié)議,也為 Web 和 Native 劃定了清晰的邊界和數(shù)據(jù)傳遞方式琴锭。
我們?yōu)?iOS 和 Android 各開發(fā)了一個(gè) Rexxar Container晰甚。iOS 和 Android 平臺(tái)截獲請(qǐng)求的方式由于平臺(tái)差異,并不完全相同决帖。但本質(zhì)上都是在 Web 和 Native 之間實(shí)現(xiàn)了一個(gè) Proxy厕九。Web 發(fā)出的請(qǐng)求會(huì)被 Proxy 預(yù)先處理。要么是修改后再發(fā)出去地回,要么是由 Rexxar Container 自己處理扁远。
具體的實(shí)現(xiàn)可以參看兩個(gè)平臺(tái)的項(xiàng)目代碼。
Rexxar 頁面執(zhí)行過程
例如刻像,客戶端接到一個(gè)頁面請(qǐng)求畅买,要打開一個(gè) URL:douban://douban.com/movie/1292052。Rexxar 的工作流如下:
根據(jù) URL 查詢本機(jī)緩存的路由表 routes.json细睡,看是否能夠找到對(duì)應(yīng)的資源記錄(一般是一個(gè) HTML 文件)谷羞。如果找到不到,請(qǐng)求 Rexxar Route 服務(wù)溜徙,獲得最新的全量路由表 routes.json湃缎,更新本地緩存犀填,找到對(duì)應(yīng)的資源記錄;
根據(jù)路由表指示的 HTML 文件的路徑雁歌,看本地是否找到對(duì)應(yīng)的文件宏浩。如果找不到,請(qǐng)求 Rexxar Web 資源服務(wù)器靠瞎,更新本地緩存比庄;在 Rexxar Container 里展示該 HTML 文件;如有需要乏盐,會(huì)在 Container 中請(qǐng)求圖片資源佳窑,圖片資源也有緩存,Rexxar Container 會(huì)先檢查本地緩存父能。如不存在神凑,會(huì)請(qǐng)求 CDN 的圖片或者圖片服務(wù)器;
Rexxar Web 前端代碼在 Container 里繼續(xù)執(zhí)行何吝,發(fā)出 API 請(qǐng)求溉委。Rexxar Container 代理這些請(qǐng)求,為 API 請(qǐng)求添加 OAuth 驗(yàn)證爱榕,或增加某些參數(shù)瓣喊;
Rexxar Web 前端代碼繼續(xù)執(zhí)行,根據(jù) API 返回的結(jié)果黔酥,展示響應(yīng)的頁面藻三,可能會(huì)請(qǐng)求 CDN 的圖片或者圖片服務(wù)器等;
Rexxar Web 前端代碼繼續(xù)執(zhí)行跪者,如果需要修改 NavigationBar 等原生界面棵帽,可能通過定義好的協(xié)議請(qǐng)求 URL: douban://rexxar.douban.com;
Rexxar Container 攔截請(qǐng)求渣玲,按定義好的協(xié)議作出反應(yīng)逗概。例如,修改 NavigationBar 上的按鈕柜蜈。如果需要仗谆,會(huì)向 Rexxar Web 回調(diào)約定好的 Javascript 函數(shù)。
Rexxar 的問題
性能
混合開發(fā)的問題在于現(xiàn)階段淑履,Web 的性能沒法和 Native 相比隶垮。這種狀況可能會(huì)長期存在。因?yàn)槊卦耄岸舜a運(yùn)行于內(nèi)嵌瀏覽器之上狸吞,和直接調(diào)用原生系統(tǒng)相比,理論上總會(huì)存在性能上的差距。我們現(xiàn)在基本是以規(guī)避的方式面對(duì)性能問題:即性能問題會(huì)明顯影響到用戶體驗(yàn)時(shí)蹋偏,我們就不使用 Rexxar 來做便斥,而是使用傳統(tǒng) Native 老老實(shí)實(shí)寫兩份代碼,一份 iOS威始,一份 Android枢纠。當(dāng)然,這就限縮了 Rexxar 的使用范圍黎棠。
在 Rexxar iOS 中晋渺,我們做了使用 WKWebView 替代 UIWebView 的嘗試。但是現(xiàn)在看起來這會(huì)是一個(gè)長遠(yuǎn)目標(biāo)脓斩。WKWebView 在速度和內(nèi)存消耗上都優(yōu)于 UIWebView木西。但 WKWebView 并不完善。對(duì)于 Rexxar iOS 而言随静,最重要的缺陷是不支持使用 NSURLProtocol 截獲 WKWebView 中發(fā)出的網(wǎng)絡(luò)請(qǐng)求八千。所以在現(xiàn)有的 Rexxar 的實(shí)現(xiàn)中,并沒有使用 WKWebView燎猛。但是恋捆,我們會(huì)持續(xù)努力,以尋找切換至 WKWebView 的可能性重绷。
錯(cuò)誤報(bào)告
我們?cè)趹?yīng)用中使用 Rexxar 之后鸠信,在收集到的 Crash Report 中,JavaScript 的相關(guān)錯(cuò)誤论寨,和瀏覽器相關(guān)的錯(cuò)誤開始增加。而對(duì)這類錯(cuò)誤爽茴,由于移動(dòng)應(yīng)用的使用環(huán)境更為復(fù)雜葬凳,錯(cuò)誤報(bào)告經(jīng)過了 JavaScript 引擎,原生系統(tǒng)兩層之后室奏,給出的錯(cuò)誤信息并不夠明確火焰。我們?cè)谶@方面的經(jīng)驗(yàn)也并不多,導(dǎo)致我們還沒有很好的辦法降低這類錯(cuò)誤胧沫。這對(duì)提高 App 的穩(wěn)定性帶來了問題昌简。
總結(jié)
Rexxar 這個(gè)混合開發(fā)框架在豆瓣移動(dòng)開發(fā)中使用,確實(shí)在一定程度上提高了我們的開發(fā)效率绒怨。以前一個(gè)頁面需要 iOS 和 Android 兩位工程師各開發(fā)一遍纯赎,現(xiàn)在只需要一位工程師寫一次前端代碼,甚至還可以應(yīng)用到移動(dòng) Web 上去南蹂。雖然 Rexxar 仍然存在一些問題犬金,和使用上的限制。但是在有限的使用中,我們?nèi)匀皇斋@不少晚顷。所以峰伙,在未來我們應(yīng)該會(huì)持續(xù)推動(dòng) Rexxar 在豆瓣移動(dòng)開發(fā)中的使用。
希望這個(gè) Rexxar 這個(gè)開源項(xiàng)目對(duì)大家能起到一點(diǎn)啟示效果该默。并得到大家的反饋和建議瞳氓,幫助我們提高。
全文完栓袖。感謝豆瓣團(tuán)隊(duì)的投稿匣摘。