前言
因為工作原因,最近需要研究Cordova框架,看了其中的源碼和實現方式,當場在看的時候馬上能理解,但是事后再回去看相關源碼時候卻發現之前理解的內容又忘記了,又不得不重新開始看,所以總覺得需要記錄下來,這樣也表明之前也是學習過,俗話說「好記性不如爛筆頭 」,想必也是體現了筆記的重要性。
目錄
-
為何要用Cordova
-
什么是Cordova
-
Cordova中UML類圖
-
Cordova實現機制
-
小結
為何要用Cordova
隨着移動互聯網的發展,現在基本是APP滿天飛,不知在大家印象中,如果我去下載一個APP,那么基本都能看到有兩種選擇,一種是Android版本,一種是IOS版本。不管我的手機是哪種操作系統,安裝完一個APP之后,后續如果有新的版本發布的時候,我還必須去更新,才能享用新版本里的功能,比如我裝了“京東”這個APP,前幾天正好碰到“618”活動,那么之前一個月APP Store就提醒我要去更新最新的APP版本,以免錯過“618”活動中新的功能使用。相對來說IOS系統更新APP比起Android系統用戶體驗會好一點,但是還是稍顯麻煩點。
那么有沒有一種方式,我只需要開發一個APP版本,就能去適配通用的操作系統呢,不僅可以適配Android、IOS,還可以適配其他系統,比如Windows Phone、 Palm WebOS、Blackberry等等。有,Cordova就能提供這種能力,代碼寫一次,就能到處運行,跟我們日常開發網站效果一樣,基於寫Web APP,根據輸出平台要求不同,就能提供不同類型的安裝包。Cordova其設計初衷是希望用戶群體能夠通過跨平台開發的方法降低原生開發的成本,為此,開發人員需要安裝原生開發環境,配置工程,使用HTML5、CSS3、JS和原生SDK生成應用。
什么是Cordova
官網定義如下:
Apache Cordova是一個開源的移動開發框架。允許你用標准的web技術-HTML5,CSS3和JavaScript做跨平台開發。 應用在每個平台的具體執行被封裝了起來,並依靠符合標准的API綁定去訪問每個設備的功能,比如說:傳感器、數據、網絡狀態等。
使用Apache Cordova的人群:
-
移動應用開發者,想擴展一個應用的使用平台,而不通過每個平台的語言和工具集重新實現。
-
web開發者,想包裝部署自己的web App將其分發到各個應用商店門戶。
-
移動應用開發者,有興趣混合原生應用組建和一個WebView(一個特別的瀏覽器窗口) 可以接觸設備A級PI,或者你想開發一個原生和WebView組件之間的插件接口。
架構圖
從圖中,我們可以看到它提供了Web APP、WebView、Cordova Plugins。
Web APP
這是存放應用程序代碼的地方,體現是你的具體業務邏輯模塊。應用的實現是通過web頁面,默認的本地文件名稱是是index.html,這個本地文件應用CSS,JavaScript,圖片,媒體文件和其他運行需要的資源。應用執行在原生應用包裝的WebView中,這個原生應用是你分發到app stores中的。
WebView
Cordova啟用的WebView可以給應用提供完整用戶訪問界面。在一些平台中,他也可以作為一個組件給大的、混合應用,這些應用混合和Webview和原生的應用組件。
Cordova Plugins
插件是Cordova生態系統的重要組成部分。他提供了Cordova和原生組件相互通信的接口並綁定到了標准的設備API上,這使你能夠通過JavaScript調用原生代碼。
Cordova中UML類圖
其實Cordova通過命令來添加項目的,但是可以選擇哪個平台去編譯,比如我們添加Android平台,在Android默認mainActivity類,我們可以看到它其實繼承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);
}
}
進而得到以下UML類圖
簡單分析下,CordovaActivity內依賴一個WebView類,一個Preferences類,一個CordovaInterface接口,並同時初始化一些配置信息。WebView具體實現是由CordovaWebViewImpl類,CordovaInterface接口具體實現是由CordovaInterfaceImpl類實現。
CordovaWebViewImpl是核心類,里面會把一些插件能力初始化,用一個PluginManager進行管理,包含一個引擎類—CordovaWebViewEngine,這個引擎是通過反射的方式創建,自身初始化的時候把NativeToJsMessageQueue關聯起來,里面包含着以Js字符串為主的雙向鏈表,把每次從前端通過JS代碼存儲起來,然后通過綁定的橋接方式Pop出到相應的Native代碼中去。
最終實現由SystemWebViewEngine類來對Android系統中WebView控件進行二次包裝,這個類的初始化是在CordovaWebViewImpl類反射創建,相關插件和消息傳遞也是通過SystemWebViewEngine進行綁定。
Cordova實現機制
當Cordova框架啟動時候,CordovaActivity類中的onCreate方法調用loadUrl方法即可啟動,最終在SystemWebViewEngine類的init方法中,會調用webView的addJavascriptInterface方法,看到這個方法是不是很熟悉,我們常規讓webView支持開啟JavaScript調用接口也是使用此特性。
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出來的對象就等同拋出“_cordovaNative”對象給JS端調用,進去看下SystemExposedJsApi類包含哪些內容,
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);
}
}
其中最關鍵是exec方法,其中bridgeSecret代表選擇哪個橋接方式,service一般對應着你本地Java文件類名,action代表java文件中方法名,callbackId代表回調函數的Id,也就是句柄,arguments代表傳遞的參數。看出其中設計思想了沒,service往往是本地能力集的類名,比如web端想調用相機,一般起個Camera類代表這個相機服務類,然后在這個類中定義方法,也就是action參數,這個action名稱可擴展,因為方法名稱可各種各樣,適合自定義功能擴展。
SystemExposedJsApi對象初始化
在創建SystemExposedJsApi時需要CordovaBridge類,CordovaBridge類初始化需要CordovaWebView的PluginManager對象和NativeToJsMessageQueue對象。因為所有的JS端與Android native代碼交互都是通過SystemExposedJsApi對象的exec方法。在exec方法中執行PluginManager的exec方法,PluginManager去查找具體的Plugin並實例化然后再執行Plugin的execute方法,並根據同步標識判斷是同步返回給JS消息還是異步。由NativeToJsMessageQueue統一管理返回給JS的消息。
何時加載Plugin,如何加載
Cordova中很重要的部分是插件,Cordova在啟動每個Activity的時候都會將配置文件中的所有plugin加載到PluginManager,在第一次loadUrl方法時,就會去初始化PluginManager並加載plugin,PluginManager在加載plugin的時候並不是馬上實例化plugin對象,而是只是將plugin的Class名字保存到一個hashmap中,用service名字作為key值。當JS端通過JavascriptInterface接口的SystemExposedJsApi對象請求Android時,PluginManager會從hashmap中查找到plugin,如果該plugin還未實例化,利用java反射機制實例化該plugin,並執行plugin的execute方法。
Cordova的數據返回
Cordova中通過exec()函數請求android插件,數據的返回可同步也可以異步於exec()函數的請求。在開發android插件的時候可以重寫public boolean isSynch(String action)方法來決定是同步還是異步。Cordova在android端使用了一個隊列(NativeToJsMessageQueue)來專門管理返回給JS的數據。
1,同步
Cordova在執行完exec()后,android會馬上返回數據,但不一定就是該次請求的數據,可能是前面某次請求的數據;因為當exec()請求的插件是允許同步返回數據的情況下,Cordova也是從NativeToJsMessageQueue隊列頭pop頭數據並返回。然后再根據callbackID反向查找某個JS請求,並將數據返回給該請求的success函數。
2,異步
Cordova在執行完exec()后並不會同步得到一個返回數據。Cordova在執行exec()的同時啟動了一個XMLHttpRequest對象方式或者prompt()函數方式的循環函數來不停的去獲取NativeToJsMessageQueue隊列中的數據,並根據callbackID反向查找到相對應的JS請求,並將該數據交給success函數。
webView.sendJavascript 發送到js隊列,onNativeToJsMessageAvailable 負責執行js.
Native 調用 JS 執行方式有三種實現 LoadUrlBridgeMode、 OnlineEventsBridgeMode、PrivateApiBridgeMode
1、webView.sendJavascript 發送js方法到JS隊列
2、onJsPrompt 方法攔截,獲取調用方式
- 如果是gap_bridge_mode,則執行 appView.exposedJsApi.setNativeToJsBridgeMode(Integer.parseInt(message));
- 如果是gap_poll, 則執行 appView.exposedJsApi.retrieveJsMessages("1".equals(message));
3、調用setBridgeMode 方法調用onNativeToJsMessageAvailable 執行javascript調用
小結
總的來說,使用Cordova框架開發優缺點很明顯。
優點:
- 跨平台,開發簡單,學習成本低
- 框架多,插件多,可自定義插件
- 發展最早,社區資源豐富,
缺點:
- WebView性能低下時,用戶體驗差,反應慢
- 畢竟是老外的框架,中文文檔資源少
- 調試不方便,既不像原生那么好調試,也不像純web那種調試
最后想說一句,無論是選擇原生模式開發還是Hybrid混合模式,一定是要基於具體業務場景去選擇,而不是盲目和絕對化覺得哪種模式好就不做分析想當然的去選擇,還是有選擇的結合,要知道應用之美在於葯到病除。
閱讀擴展
源於對掌握的Android開發基礎點進行整理,羅列下已經總結的文章,從中可以看到技術積累的過程。
1,Android系統簡介
2,ProGuard代碼混淆
3,講講Handler+Looper+MessageQueue關系
4,Android圖片加載庫理解
5,談談Android運行時權限理解
6,EventBus初理解
7,Android 常見工具類
8,對於Fragment的一些理解
9,Android 四大組件之 " Activity "
10,Android 四大組件之" Service "
11,Android 四大組件之“ BroadcastReceiver "
12,Android 四大組件之" ContentProvider "
13,講講 Android 事件攔截機制
14,Android 動畫的理解
15,Android 生命周期和啟動模式
16,Android IPC 機制
17,View 的事件體系
18,View 的工作原理
19,理解 Window 和 WindowManager
20,Activity 啟動過程分析
21,Service 啟動過程分析
22,Android 性能優化
23,Android 消息機制
24,Android Bitmap相關
25,Android 線程和線程池
26,Android 中的 Drawable 和動畫
27,RecylerView 中的裝飾者模式
28,Android 觸摸事件機制
29,Android 事件機制應用
30,Cordova 框架的一些理解
31,有關 Android 插件化思考
32,開發人員必備技能——單元測試