在 Android 上運(yùn)行 ClojureScript
在過(guò)去的幾天里,我有了開(kāi)發(fā)生涯中最有意義的經(jīng)歷之一, 想在這里跟大家分享。
現(xiàn)在我們已經(jīng)讓 ClojureScript 可以在 Android 上運(yùn)行了。不是在一個(gè) WebView 里面,也不是利用像 Cordova 這樣的東西,而是實(shí)實(shí)在在的運(yùn)行在一個(gè)嵌入式的 JavaScript 引擎中。到底發(fā)生了什么?
最近,我們做了一些工作創(chuàng)建出了一個(gè) iOS 的 ClojureScript REPL 應(yīng)用,Replete。整個(gè)看上去很酷,而對(duì)應(yīng)的 Android 應(yīng)用就只算勉強(qiáng)能創(chuàng)建出來(lái)。不幸的是,我的 Android 技術(shù)平平,也不能立馬就上手。
不過(guò),這方面 Tahmid Sadik 的技術(shù)還能上得了臺(tái)面。
Tahmid 可以把UI都串起來(lái),也能將 Rhino 實(shí)例化并對(duì)JavaScript語(yǔ)句進(jìn)行計(jì)算,但是接下來(lái)讓引導(dǎo)式的 ClojureScript 運(yùn)行起來(lái)對(duì)他而言可謂是一次挑戰(zhàn)。
不過(guò)在此之前,他必須在他的 app 里將 ClojureScript 引導(dǎo)起來(lái)。引導(dǎo)這個(gè)詞被我特意標(biāo)明,指出他需要用 Google 的 Closure 依賴(lài)管理系統(tǒng)把 ClojureScript 運(yùn)行時(shí)啟動(dòng)起來(lái),沒(méi)有 JavaScript,也沒(méi)有其他的優(yōu)化(例如 :none 模式),根據(jù)需要,可以定義 CLOSURE_IMPORT_SCRIPT 環(huán)境變量。如果想包含一個(gè) REPL,用這種方式來(lái)引入運(yùn)行時(shí)是很重要的。這也為在你的 REPL 包含源碼級(jí)的名字空間提供了支持。
實(shí)際上,Replete 也需要做這些,用到的是 Ambly 的一個(gè)功能。我當(dāng)然知道,Replete 是一個(gè)獨(dú)立的 REPL,并不需要 Ambly。不過(guò)我使用了一個(gè)小花招,可以復(fù)用這個(gè)功能,讓 ClojureScript 啟動(dòng)在 Replete 里運(yùn)行。
順便說(shuō)一下,我最初是打算用 React Native 來(lái)實(shí)現(xiàn) Replete 的。結(jié)果發(fā)現(xiàn),使用 React Native 的 ClojureScript 支持還為時(shí)尚早,而可以引導(dǎo)的 ClojureScript 確實(shí)只是初期的功能,要想能夠?qū)嶋H工作還需要做很多努力。所以,對(duì)于 Replete,我還是保持簡(jiǎn)單吧,就算是 Goby 也沒(méi)有用到。
對(duì)于上述的結(jié)果,好的一點(diǎn)是不需要 React Native 的依賴(lài)(Android 版還沒(méi)有正式公開(kāi)發(fā)布),也沒(méi)有 Goby 的依賴(lài)(只支持 iOS)。Replete 內(nèi)置的 ClojureScript 非常簡(jiǎn)潔,使用傳統(tǒng)的 iOS 的 UI,事實(shí)證明這對(duì) Tahmid 去完成同樣功能的 Android 版很有幫助。
讓我們回到故事的開(kāi)始:本質(zhì)上 Tahmid 復(fù)制了 Ambly 的 bootstrap 邏輯,按照順序逐一的執(zhí)行了在 Rhino 里的 JavaScript 語(yǔ)句。不過(guò)時(shí)不時(shí)的他會(huì)遇到一些奇怪的問(wèn)題,我的記憶中幾個(gè)月前也會(huì)遇到類(lèi)似的問(wèn)題,在研究過(guò) Ambly 的代碼后,我給了一點(diǎn)建議。
之后,他基本上可以引導(dǎo)成功 ClosureScriptle。
- cljs.core.apply.call(null,cljs.core.inc,new cljs.core.PersistentVector(null, 1, 5, cljs.core.PersistentVector.EMPTY_NODE, [1], null))
這行就是(apply inc [1])需要綁定的JavaScript代碼
然后Tahmid終于有了2.0。真棒!這應(yīng)該是有史以來(lái)的***次,在基于Android的嵌入式的Rhino上跑起來(lái)ClojureScript。
接下來(lái),需要嘗試用起來(lái)reader, analyzer, 和compiler。現(xiàn)在我們來(lái)試一下,只需要簡(jiǎn)單的使用Replete里的JavaScript,讓Android的app執(zhí)行Replete的read_eval_print函數(shù),參數(shù)為字符串 (+ 1 2),如果工作正常,那么恭喜,ClojureScript已經(jīng)成功啟動(dòng)了。
- replete.core.read_eval_print.call(null,'(+ 1 2)')
且慢,Transit 里執(zhí)行 goog.require('replete.core');的時(shí)候出了問(wèn)題,應(yīng)該是跟 randomUUID 有關(guān)。悲劇了,看上去顯然還有很多地方需要處理。
不過(guò)沒(méi)關(guān)系,Replete 之前一直嘗試用不同的方式加載 analysis 緩存,感謝 Karl Mikkelsen,我們有了一個(gè)可用的版本只使用純 JavaScript,沒(méi)有任何依賴(lài)。把這個(gè)用起來(lái)之后,在把print回調(diào)弄好(這樣類(lèi)似 println 這樣的方法就可以工作了),接下來(lái) Tahmid 就通過(guò)Slack通知我:
- I got 3
- (+ 1 2) = 3
... 然后不斷煩我,還會(huì)擅自發(fā)博客。就這樣,引導(dǎo)的 ClojureScript REPL 在 Android 上誕生了!
Tahmid 在界面上封裝了一些東西,修復(fù)了 JavaScript/ClojureScript 集成的一些小問(wèn)題之后就發(fā)布了Replicator。
簡(jiǎn)直就是一場(chǎng)暴風(fēng)雨!
現(xiàn)在,Tahmid 正在用 JavaScriptCore 替換 Rhino,這就沒(méi)那么快了。
我認(rèn)為這將使速度提升。這為我們?cè)?Android 上使用 JavaScriptCore 進(jìn)行本地交換的功能提供一些重要的基礎(chǔ)。
從大圖片來(lái)看,我真的認(rèn)為 ClojureScript 在 Android 上運(yùn)行很快。特別是使用 JavaScriptCore。對(duì)于這個(gè)觀點(diǎn)的問(wèn)題,可以看看 Bocko 對(duì) Android 的 Vladimir Iakovlev 的端口在啟動(dòng)速度上的差異。
- Clojure ~14 秒
- Clojure / Skummet ~11 秒
- ClojureScript ~2 秒
以上是在模擬器上運(yùn)行的結(jié)果,但是,我仍然認(rèn)為它顯示了 ClojureScript 真正的實(shí)現(xiàn)了在移動(dòng)設(shè)備上減少計(jì)算延時(shí)的承諾。我認(rèn)為是時(shí)候讓 ClojureScript 活躍起來(lái),用于為移動(dòng)設(shè)備開(kāi)發(fā)應(yīng)用!