[轉] Android PhoneGap Cordova 體系結構

雨知發表於2013-12-25

說明:

本文件只針對Cordova(PhoneGap)的Android端,基於Cordova2.1.0版本。

一.總體結構

Cordova的目標是用HTML,JS,來完成手機客戶端的開發,並且是隻開發一次可以在各種手機平臺上跑,所以理想狀態是用JS去控制所有事件。Cordova基於WebView元件。每個繼承自DroidGap的Activity對應一個獨立的CordovaWebView。Cordova提供了一些列的JS介面來訪問Android的native(詳細參見http://docs.phonegap.com/en/2.1.0/)。以外掛(Plugin)的形式提供自定義介面給JS端訪問。

二.一些疑問

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();
   }
   …
}

這樣就完成了我們在一般開發中使用的模式:

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;
}

不正確之處請各位指正。

 

轉自:

http://my.oschina.net/tonywolf/blog

相關文章