前言
因?yàn)楣ぷ髟蚰模罱枰芯緾ordova框架潘拱,看了其中的源碼和實(shí)現(xiàn)方式,當(dāng)場(chǎng)在看的時(shí)候馬上能理解拧略,但是事后再回去看相關(guān)源碼時(shí)候卻發(fā)現(xiàn)之前理解的內(nèi)容又忘記了芦岂,又不得不重新開始看,所以總覺得需要記錄下來(lái)垫蛆,這樣也表明之前也是學(xué)習(xí)過(guò)禽最,俗話說(shuō)「好記性不如爛筆頭 」,想必也是體現(xiàn)了筆記的重要性月褥。
目錄
為何要用Cordova
什么是Cordova
Cordova中UML類圖
Cordova實(shí)現(xiàn)機(jī)制
-
小結(jié)
?
為何要用Cordova
隨著移動(dòng)互聯(lián)網(wǎng)的發(fā)展弛随,現(xiàn)在基本是APP滿天飛,不知在大家印象中宁赤,如果我去下載一個(gè)APP,那么基本都能看到有兩種選擇栓票,一種是Android版本决左,一種是IOS版本愕够。不管我的手機(jī)是哪種操作系統(tǒng),安裝完一個(gè)APP之后佛猛,后續(xù)如果有新的版本發(fā)布的時(shí)候惑芭,我還必須去更新,才能享用新版本里的功能继找,比如我裝了“京東”這個(gè)APP遂跟,前幾天正好碰到“618”活動(dòng),那么之前一個(gè)月APP Store就提醒我要去更新最新的APP版本婴渡,以免錯(cuò)過(guò)“618”活動(dòng)中新的功能使用幻锁。相對(duì)來(lái)說(shuō)IOS系統(tǒng)更新APP比起Android系統(tǒng)用戶體驗(yàn)會(huì)好一點(diǎn),但是還是稍顯麻煩點(diǎn)边臼。
那么有沒有一種方式哄尔,我只需要開發(fā)一個(gè)APP版本,就能去適配通用的操作系統(tǒng)呢柠并,不僅可以適配Android岭接、IOS,還可以適配其他系統(tǒng)臼予,比如Windows Phone鸣戴、 Palm WebOS、Blackberry等等粘拾。有窄锅,Cordova就能提供這種能力,代碼寫一次半哟,就能到處運(yùn)行酬滤,跟我們?nèi)粘i_發(fā)網(wǎng)站效果一樣,基于寫Web APP寓涨,根據(jù)輸出平臺(tái)要求不同盯串,就能提供不同類型的安裝包。Cordova其設(shè)計(jì)初衷是希望用戶群體能夠通過(guò)跨平臺(tái)開發(fā)的方法降低原生開發(fā)的成本戒良,為此体捏,開發(fā)人員需要安裝原生開發(fā)環(huán)境,配置工程糯崎,使用HTML5几缭、CSS3、JS和原生SDK生成應(yīng)用沃呢。
什么是Cordova
官網(wǎng)定義如下:
Apache Cordova是一個(gè)開源的移動(dòng)開發(fā)框架年栓。允許你用標(biāo)準(zhǔn)的web技術(shù)-HTML5,CSS3和JavaScript做跨平臺(tái)開發(fā)。 應(yīng)用在每個(gè)平臺(tái)的具體執(zhí)行被封裝了起來(lái)薄霜,并依靠符合標(biāo)準(zhǔn)的API綁定去訪問(wèn)每個(gè)設(shè)備的功能某抓,比如說(shuō):傳感器纸兔、數(shù)據(jù)、網(wǎng)絡(luò)狀態(tài)等否副。
使用Apache Cordova的人群:
移動(dòng)應(yīng)用開發(fā)者汉矿,想擴(kuò)展一個(gè)應(yīng)用的使用平臺(tái),而不通過(guò)每個(gè)平臺(tái)的語(yǔ)言和工具集重新實(shí)現(xiàn)备禀。
web開發(fā)者洲拇,想包裝部署自己的web App將其分發(fā)到各個(gè)應(yīng)用商店門戶。
-
移動(dòng)應(yīng)用開發(fā)者曲尸,有興趣混合原生應(yīng)用組建和一個(gè)WebView(一個(gè)特別的瀏覽器窗口) 可以接觸設(shè)備A級(jí)PI赋续,或者你想開發(fā)一個(gè)原生和WebView組件之間的插件接口。
?
架構(gòu)圖
從圖中队腐,我們可以看到它提供了Web APP蚕捉、WebView、Cordova Plugins柴淘。
Web APP
這是存放應(yīng)用程序代碼的地方迫淹,體現(xiàn)是你的具體業(yè)務(wù)邏輯模塊。應(yīng)用的實(shí)現(xiàn)是通過(guò)web頁(yè)面为严,默認(rèn)的本地文件名稱是是index.html敛熬,這個(gè)本地文件應(yīng)用CSS,JavaScript,圖片,媒體文件和其他運(yùn)行需要的資源第股。應(yīng)用執(zhí)行在原生應(yīng)用包裝的WebView中应民,這個(gè)原生應(yīng)用是你分發(fā)到app stores中的。
WebView
Cordova啟用的WebView可以給應(yīng)用提供完整用戶訪問(wèn)界面夕吻。在一些平臺(tái)中诲锹,他也可以作為一個(gè)組件給大的、混合應(yīng)用涉馅,這些應(yīng)用混合和Webview和原生的應(yīng)用組件归园。
Cordova Plugins
插件是Cordova生態(tài)系統(tǒng)的重要組成部分。他提供了Cordova和原生組件相互通信的接口并綁定到了標(biāo)準(zhǔn)的設(shè)備API上稚矿,這使你能夠通過(guò)JavaScript調(diào)用原生代碼庸诱。
Cordova中UML類圖
其實(shí)Cordova通過(guò)命令來(lái)添加項(xiàng)目的,但是可以選擇哪個(gè)平臺(tái)去編譯晤揣,比如我們添加Android平臺(tái)桥爽,在Android默認(rèn)mainActivity類,我們可以看到它其實(shí)繼承CordovaActivity類昧识,一切初始化條件是從loadUrl方法開始钠四。
package com.example.hello;
import android.os.Bundle;
import org.apache.cordova.*;
public class MainActivity extends CordovaActivity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// enable Cordova apps to be started in the background
Bundle extras = getIntent().getExtras();
if (extras != null && extras.getBoolean("cdvStartInBackground", false)) {
moveTaskToBack(true);
}
// Set by <content src="index.html" /> in config.xml
loadUrl(launchUrl);
}
}
進(jìn)而得到以下UML類圖
簡(jiǎn)單分析下,CordovaActivity內(nèi)依賴一個(gè)WebView類跪楞,一個(gè)Preferences類形导,一個(gè)CordovaInterface接口环疼,并同時(shí)初始化一些配置信息习霹。WebView具體實(shí)現(xiàn)是由CordovaWebViewImpl類朵耕,CordovaInterface接口具體實(shí)現(xiàn)是由CordovaInterfaceImpl類實(shí)現(xiàn)。
CordovaWebViewImpl是核心類淋叶,里面會(huì)把一些插件能力初始化阎曹,用一個(gè)PluginManager進(jìn)行管理,包含一個(gè)引擎類—CordovaWebViewEngine煞檩,這個(gè)引擎是通過(guò)反射的方式創(chuàng)建处嫌,自身初始化的時(shí)候把NativeToJsMessageQueue關(guān)聯(lián)起來(lái),里面包含著以Js字符串為主的雙向鏈表斟湃,把每次從前端通過(guò)JS代碼存儲(chǔ)起來(lái)熏迹,然后通過(guò)綁定的橋接方式Pop出到相應(yīng)的Native代碼中去。
最終實(shí)現(xiàn)由SystemWebViewEngine類來(lái)對(duì)Android系統(tǒng)中WebView控件進(jìn)行二次包裝凝赛,這個(gè)類的初始化是在CordovaWebViewImpl類反射創(chuàng)建注暗,相關(guān)插件和消息傳遞也是通過(guò)SystemWebViewEngine進(jìn)行綁定。
Cordova實(shí)現(xiàn)機(jī)制
當(dāng)Cordova框架啟動(dòng)時(shí)候墓猎,CordovaActivity類中的onCreate方法調(diào)用loadUrl方法即可啟動(dòng)捆昏,最終在SystemWebViewEngine類的init方法中,會(huì)調(diào)用webView的addJavascriptInterface方法毙沾,看到這個(gè)方法是不是很熟悉骗卜,我們常規(guī)讓webView支持開啟JavaScript調(diào)用接口也是使用此特性。
private static void exposeJsInterface(WebView webView, CordovaBridge bridge) {
if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)) {
LOG.i(TAG, "Disabled addJavascriptInterface() bridge since Android version is old.");
// Bug being that Java Strings do not get converted to JS strings automatically.
// This isn't hard to work-around on the JS side, but it's easier to just
// use the prompt bridge instead.
return;
}
SystemExposedJsApi exposedJsApi = new SystemExposedJsApi(bridge);
webView.addJavascriptInterface(exposedJsApi, "_cordovaNative");
}
那么SystemExposedJsApi類new出來(lái)的對(duì)象就等同拋出“_cordovaNative”對(duì)象給JS端調(diào)用左胞,進(jìn)去看下SystemExposedJsApi類包含哪些內(nèi)容寇仓,
class SystemExposedJsApi implements ExposedJsApi {
private final CordovaBridge bridge;
SystemExposedJsApi(CordovaBridge bridge) {
this.bridge = bridge;
}
@JavascriptInterface
public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
return bridge.jsExec(bridgeSecret, service, action, callbackId, arguments);
}
@JavascriptInterface
public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
bridge.jsSetNativeToJsBridgeMode(bridgeSecret, value);
}
@JavascriptInterface
public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
return bridge.jsRetrieveJsMessages(bridgeSecret, fromOnlineEvent);
}
}
其中最關(guān)鍵是exec方法,其中bridgeSecret代表選擇哪個(gè)橋接方式烤宙,service一般對(duì)應(yīng)著你本地Java文件類名遍烦,action代表java文件中方法名,callbackId代表回調(diào)函數(shù)的Id门烂,也就是句柄乳愉,arguments代表傳遞的參數(shù)⊥驮叮看出其中設(shè)計(jì)思想了沒蔓姚,service往往是本地能力集的類名,比如web端想調(diào)用相機(jī)慨丐,一般起個(gè)Camera類代表這個(gè)相機(jī)服務(wù)類坡脐,然后在這個(gè)類中定義方法,也就是action參數(shù)房揭,這個(gè)action名稱可擴(kuò)展备闲,因?yàn)榉椒Q可各種各樣晌端,適合自定義功能擴(kuò)展。
SystemExposedJsApi對(duì)象初始化
在創(chuàng)建SystemExposedJsApi時(shí)需要CordovaBridge類恬砂,CordovaBridge類初始化需要CordovaWebView的PluginManager對(duì)象和NativeToJsMessageQueue對(duì)象咧纠。因?yàn)樗械腏S端與Android native代碼交互都是通過(guò)SystemExposedJsApi對(duì)象的exec方法。在exec方法中執(zhí)行PluginManager的exec方法泻骤,PluginManager去查找具體的Plugin并實(shí)例化然后再執(zhí)行Plugin的execute方法漆羔,并根據(jù)同步標(biāo)識(shí)判斷是同步返回給JS消息還是異步。由NativeToJsMessageQueue統(tǒng)一管理返回給JS的消息狱掂。
何時(shí)加載Plugin演痒,如何加載
Cordova中很重要的部分是插件,Cordova在啟動(dòng)每個(gè)Activity的時(shí)候都會(huì)將配置文件中的所有plugin加載到PluginManager趋惨,在第一次loadUrl方法時(shí)鸟顺,就會(huì)去初始化PluginManager并加載plugin,PluginManager在加載plugin的時(shí)候并不是馬上實(shí)例化plugin對(duì)象器虾,而是只是將plugin的Class名字保存到一個(gè)hashmap中讯嫂,用service名字作為key值。當(dāng)JS端通過(guò)JavascriptInterface接口的SystemExposedJsApi對(duì)象請(qǐng)求Android時(shí)曾撤,PluginManager會(huì)從hashmap中查找到plugin端姚,如果該plugin還未實(shí)例化,利用java反射機(jī)制實(shí)例化該plugin挤悉,并執(zhí)行plugin的execute方法渐裸。
Cordova的數(shù)據(jù)返回
Cordova中通過(guò)exec()函數(shù)請(qǐng)求android插件,數(shù)據(jù)的返回可同步也可以異步于exec()函數(shù)的請(qǐng)求装悲。在開發(fā)android插件的時(shí)候可以重寫public boolean isSynch(String action)方法來(lái)決定是同步還是異步昏鹃。Cordova在android端使用了一個(gè)隊(duì)列(NativeToJsMessageQueue)來(lái)專門管理返回給JS的數(shù)據(jù)。
1诀诊,同步
Cordova在執(zhí)行完exec()后洞渤,android會(huì)馬上返回?cái)?shù)據(jù),但不一定就是該次請(qǐng)求的數(shù)據(jù)属瓣,可能是前面某次請(qǐng)求的數(shù)據(jù)载迄;因?yàn)楫?dāng)exec()請(qǐng)求的插件是允許同步返回?cái)?shù)據(jù)的情況下,Cordova也是從NativeToJsMessageQueue隊(duì)列頭pop頭數(shù)據(jù)并返回抡蛙。然后再根據(jù)callbackID反向查找某個(gè)JS請(qǐng)求护昧,并將數(shù)據(jù)返回給該請(qǐng)求的success函數(shù)。
2粗截,異步
Cordova在執(zhí)行完exec()后并不會(huì)同步得到一個(gè)返回?cái)?shù)據(jù)惋耙。Cordova在執(zhí)行exec()的同時(shí)啟動(dòng)了一個(gè)XMLHttpRequest對(duì)象方式或者prompt()函數(shù)方式的循環(huán)函數(shù)來(lái)不停的去獲取NativeToJsMessageQueue隊(duì)列中的數(shù)據(jù),并根據(jù)callbackID反向查找到相對(duì)應(yīng)的JS請(qǐng)求,并將該數(shù)據(jù)交給success函數(shù)绽榛。
webView.sendJavascript 發(fā)送到j(luò)s隊(duì)列湿酸,onNativeToJsMessageAvailable 負(fù)責(zé)執(zhí)行js.
Native 調(diào)用 JS 執(zhí)行方式有三種實(shí)現(xiàn) LoadUrlBridgeMode、 OnlineEventsBridgeMode灭美、PrivateApiBridgeMode
1推溃、webView.sendJavascript 發(fā)送js方法到JS隊(duì)列
2、onJsPrompt 方法攔截冲粤,獲取調(diào)用方式
- 如果是gap_bridge_mode美莫,則執(zhí)行 appView.exposedJsApi.setNativeToJsBridgeMode(Integer.parseInt(message));
- 如果是gap_poll, 則執(zhí)行 appView.exposedJsApi.retrieveJsMessages("1".equals(message));
3、調(diào)用setBridgeMode 方法調(diào)用onNativeToJsMessageAvailable 執(zhí)行javascript調(diào)用
小結(jié)
總的來(lái)說(shuō)梯捕,使用Cordova框架開發(fā)優(yōu)缺點(diǎn)很明顯。
優(yōu)點(diǎn):
- 跨平臺(tái)窝撵,開發(fā)簡(jiǎn)單傀顾,學(xué)習(xí)成本低
- 框架多,插件多碌奉,可自定義插件
- 發(fā)展最早短曾,社區(qū)資源豐富,
缺點(diǎn):
- WebView性能低下時(shí)赐劣,用戶體驗(yàn)差嫉拐,反應(yīng)慢
- 畢竟是老外的框架,中文文檔資源少
- 調(diào)試不方便魁兼,既不像原生那么好調(diào)試婉徘,也不像純web那種調(diào)試
最后想說(shuō)一句,無(wú)論是選擇原生模式開發(fā)還是Hybrid混合模式咐汞,一定是要基于具體業(yè)務(wù)場(chǎng)景去選擇盖呼,而不是盲目和絕對(duì)化覺得哪種模式好就不做分析想當(dāng)然的去選擇,還是有選擇的結(jié)合化撕,要知道應(yīng)用之美在于藥到病除几晤。