Cordova(PhoneGap)體系結構(Android)

做者:tonyley
出處:http://my.oschina.net/tonywolf/blog 
轉載請註明出處javascript

說明java

本文檔只爭對Cordova(PhoneGap)Android端,基於Cordova2.1.0版本。android

一.整體結構web

Cordova的目標是用HTML,JS,來完成手機客戶端的開發,而且是隻開發一次能夠在各類手機平臺上跑,因此理想狀態是用JS去控制全部事件。Cordova基於WebView組件。每一個繼承自DroidGap的Activity對應一個獨立的CordovaWebView。Cordova提供了一些列的JS接口來訪問Android的native(詳細參見http://docs.phonegap.com/en/2.1.0/)。以插件(Plugin)的形式提供自定義接口給JS端訪問。app


二.一些疑問框架

1. Cordova框架是如何啓動的?異步

2. 插件是怎麼回事?如何工做的。函數

3. Cordova的官方文檔都是說JS如何訪問Android的native,那麼在Android的native中是否能夠訪問JS的函數?如何訪問?佈局

三.結構解剖
1.Cordova的啓動
1) 如何啓動
Cordova提供了一個Class(DroidGap)和一個interface(CordovaInterface)來讓Android開發者開發Cordova。通常狀況下實現DroidGap便可,由於DroidGap類已經作了不少準備工做,能夠說DroidGap類是Cordova框架的一個重要部分;若是在必要的狀況下實現CordovaInterface接口,那麼這個類中不少DroidGap的功能須要本身去實現。
繼承了DroidGap或者CordovaInterface的Activity就是一個獨立的Cordova模塊,獨立的Cordova模塊指的是每一個實現了DroidGap或者CordovaInterface接口的Activity都對應一套獨立的WebView,Plugin,PluginManager,沒有共享的。(我以爲這樣是很不爽的,沒有共享,若是plugin和Activity不少的狀況下這樣是至關的耗資源)
當在實現了DroidGap或者CordovaInterface接口的Activity的onCreate方法中調用DroidGap的loadUrl方法即啓動了Cordova框架。

2)啓動過程
public class A extends DroidGap{}

a. 在A的onCreate方法中首先調用super. onCreate(),
這一步中建立了一個LinearLayout(LinearLayoutSoftKeyboardDetect.class)佈局,以及計算這個Layout的大小及顯示的一些方式。在後面的過程當中會將一個WebView加到這個LinearLayout中。

b. 再次調用DroidGap 的loadUrl()方法
DroidGap. loadUrl()中首先判斷A的CordovaWebView是否實例化,若是沒有回實例化一個CordovaWebView對象並將該對象添加到父LinearLayout中去,同時將該LinearLayout添加的ContentView。見如下代碼
public void onCreate(Bundle savedInstanceState) {
   …
   root = new LinearLayoutSoftKeyboardDetect(this, width, height);
   …
}

public void init() {
    CordovaWebView webView = new CordovaWebView(DroidGap.this);
    this.init(webView, 
    new CordovaWebViewClient(this, webView),
    new CordovaChromeClient(this, webView));
}

public void init(CordovaWebView webView, CordovaWebViewClient webViewClient, CordovaChromeClient webChromeClient) {
    this.appView = webView;
    this.appView.setId(100);
    …
    this.root.addView(this.appView);
    setContentView(this.root);
}

public void loadUrl(String url) {
   if (this.appView == null) {
      this.init();
   }
   …
}

這樣就完成了咱們在通常開發中使用的模式:
this

public void onCreate(Bundle savedInstanceState) {
   super. onCreate(Bundle savedInstanceState);
   setContentView(resID);
   …
}

在初始化完CordovaWebView後調用CordovaWebView.loadUrl()。此時完成Cordova的啓動。

c. 在實例化CordovaWebView的時候, CordovaWebView對象會去建立一個屬於當前CordovaWebView對象的插件管理器PluginManager對象,一個消息隊列NativeToJsMessageQueue對象,一個JavascriptInterface對象ExposedJsApi,並將ExposedJsApi對象添加到CordovaWebView中,JavascriptInterface名字爲:_cordovaNative。

d. Cordova的JavascriptInterface

在建立ExposedJsApi時須要CordovaWebView的PluginManager對象和NativeToJsMessageQueue對象。由於全部的JS端與Android native代碼交互都是經過ExposedJsApi對象的exec方法。在exec方法中執行PluginManager的exec方法,PluginManager去查找具體的Plugin並實例化而後再執行Plugin的execute方法,並根據同步標識判斷是同步返回給JS消息仍是異步。由NativeToJsMessageQueue統一管理返回給JS的消息。

e. 什麼時候加載Plugin,如何加載

 Cordova在啓動每一個Activity的時候都會將配置文件中的全部plugin加載到PluginManager。那麼是何時將這些plugin加載到PluginManager的呢?在b中說了最後會調用CordovaWebView.loadUrl(),對,就在這個時候會去初始化PluginManager並加載plugin。PluginManager在加載plugin的時候並非立刻實例化plugin對象,而是隻是將plugin的Class名字保存到一個hashmap中,用service名字做爲key值。

當JS端經過JavascriptInterface接口的ExposedJsApi對象請求Android時,PluginManager會從hashmap中查找到plugin,若是該plugin還未實例化,利用java反射機制實例化該plugin,並執行plugin的execute方法。


2.Cordova插件

在『1』中已經接觸了些Cordova插件的東西,這裏總結下Cordova的插件

Cordova插件只是一個普通的java類,沒有什麼特殊之處。插件中的execute方法只是Cordova框架的一個規定。

Cordova爲了方便於插件的管理,因此引進了一個PluginManager來管理插件。在ExposedJsApi中,PluginManager起一個代理做用。

在原生態的WebView開發中,咱們能夠給WebView添加一個JavascriptInterface對象來使JS能夠訪問android的代碼;在Cordova也有一個這樣的對象ExposedJsApi。

在ExposedJsApi中,它將請求轉發給咱們的PluginManager,這個時候PluginManager會根據給的service的名字來查找具體的Plugin並執行。

因此,我認爲Cordova插件是android端的API,提供給JS。

在Cordova雖然有JavascriptInterface對象ExposedJsApi,但在JS端並非真正經過android提供的window.JavascriptInterface.request這種方式來請求。在JS端,Cordova是經過JS的prompt()函數觸發ChromeClient中的onJsPrompt方法,經過onJsPrompt去獲取到請求,再將請求轉發給ExposedJsApi。

注:在Cordova中,Java端和JavaScript端都準備了代碼來使用特殊URL('http://cdv_exec/' + service + '#' + action + '#' + callbackId + '#' + argsJson;)的方式交互請求,但並無真正使用。


3.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函數。

   注:Cordova對本地的HTML文件(file:// 開頭的URL)或者手機設置有代理的狀況下使用XMLHttpRequest方式獲取返回數據,其餘則使用prompt()函數方式獲取返回數據。


4.Android代碼訪問JS

翻了Cordova的官方文檔和Cordova代碼,發現Cordova並未提供一種方式來讓咱們在Android中去訪問JS。如今想來多是這樣的道理(我的觀點),由於Cordova框架的性質就是一個用HTML+JS來開發APP的,至關於java用的虛擬機層(比喻不是很恰當),因此Cordova未提供Android訪問JS的方式。

若是須要Android訪問JS,只有使用原生態WebView開發的方式

loadUrl(「javascript:xxx」)

由於在Cordova框架中須要訪問JS的時候也是使用的這種方式,以下(Activity的onResume事件):

public void handleResume(boolean keepRunning, boolean activityResultKeepRunning)
{

        // Send resume event to JavaScript
        this.loadUrl("javascript:try{cordova.fireDocumentEvent('resume');}catch(e){console.log('exception firing resume event from native');};");

        // Forward to plugins
        if (this.pluginManager != null) {
            this.pluginManager.onResume(keepRunning);
        }

        // Resume JavaScript timers (including setInterval)
        this.resumeTimers();
        paused = false;
}
不正確之處請各位指正。
相關文章
相關標籤/搜索