(轉)PhoneGap原理及插件擴展

這是我看到的最詳細的介紹phonegap 原理的中文文章, 所以轉了過來。
PhoneGap 是利用 HTML 來開發移動應用的一個開放源代碼的框架,利用它就可以僅僅使用 HTML 和 Javascript 實現一次編寫,多個移動平臺運行的應用。目前已經可以支持 Android、iOS、BlackBerry 等 6 個平臺。在完成這篇短文的過程中,PhoneGap 正式發佈了 1.0.0 版本。因而短文中的版本使用的是 1.0.0rc2 版本的代碼。
PhoneGap 在 Android 平臺上的實現的框架是利用了在本地代碼和瀏覽器間建立中間層來實現對 Java 代碼的隔離。利用瀏覽器的接口和網絡 socket 接口實現數據的通訊。因此在對 PhoneGap 框架的理解上,需要從 Java 端和瀏覽器端兩部分來分別學習。
關於 Android 開發環境和 PhoneGap 開發環境的搭建,請參考參考資料中的開發者網站實施。

Java 端介紹
Java 端作爲後臺調用 Android 本地 SDK 的接口,主要實現瞭如下的功能:
  1. 建立通訊機制,提供接口給瀏覽器端,方便 JavaScript 進行調用。
  2. 數據隊列的維護,以保證瀏覽器端的調用後產生的數據可以回送。
  3. 插件體系的建立,提供整個框架的可擴展性。
而這三部分的功能對應到代碼中則是如下的幾個重要的 Java 類:
  1. DroidGap、App
  2. CallbackServer
  3. Plugin、PluginManager
因此我們需要依次來了解這幾個重要的 Java 類的具體實現,這樣纔可以對 PhoneGap 在 Android 上的體系有一個很好的瞭解。
DroidGap 的實現
  當我們完成一個基本的 PhoneGap 的示例後,我們就會發現,在使用 PhoneGap 進行開發的手機應用中,第一步就是將繼承關係 extends Activity 修改爲 extends DroidGap。因此,DroidGap 是整個應用開始的地點,首先需要了解 DroidGap 的內容。
在源碼中可以看到 DroidGap 繼承自 PhonegapActivity,而 PhonegapActivity 是一個抽象類,繼承自 Activity,但是具體的實現都是集中在 DroidGap 類中。
因此,我們繼續回到 DroidGap 類中,當我們知道 DroidGap 類也是一個 Acitvity 後,就會明白,DroidGap 在 onCreate 方法中實現了整個類最初的一些操作,代碼的實現則集中在 onCreate() 和 init() 這兩個方法中,具體的流程如下:
 1.設置 WebView
設置 WebView 的代碼都集中在 init() 方法中,其中一個值得注意的工作是設置 WebChromClient。代碼中提供了一個繼承自 WebChromClient 的類,重寫了其中的 onJsAlert,onJsConfirm,onJsPrompt 等方法。而在 onJsPrompt() 方法中,實現了 PhoneGap 中 Java 端和瀏覽器端通訊的關鍵一步。因此,值得我們專門的關注其代碼,具體的功能實現代碼如下,我們已經過濾了一些錯誤處理、安全驗證等等可能會妨礙我們對重要的功能實現理解的代碼:
代碼清單 1. WebView 代碼
if (reqOk && defaultValue != null && defaultValue.length() > 3
&& defaultValue.substring(0, 4).equals("gap:")) {
     JSONArray array;
     try {
array = new JSONArray(defaultValue.substring(4));
String service = array.getString(0);
String action = array.getString(1);
String callbackId = array.getString(2);
boolean async = array.getBoolean(3);
String r = pluginManager.exec(service, action, callbackId, message, async);
result.confirm(r);
} catch (JSONException e) {
e.printStackTrace();
}
}
// Polling for JavaScript messages
else if ......
在此,我們就可以明白,實現 JavaScript 與 Java 端通訊的原理是 JavaScript 利用 prompt 來傳遞調用信息的數據,在 onJsPrompt 中,重寫的方法截獲了這些數據,在完成了對數據格式等等分析後,按照要求進行具體的調用。而具體的調用利用是的 PluginManager 類,這將在隨後說明。
1.綁定 js
在完成 WebView 的設置後,再將 PhoneGap 和 js 綁定,實現 js 與 PhoneGap 的通訊。在這一步中主要的內容就是實例化了 CallbackServer 和 PluginManager 兩個類。因此,更多的內容會在隨後對這兩個類的介紹中再說明。
2.載入 URL
LoadUrl 方法也是實現一個 PhoneGap 示例後很熟悉的方法,在 loadUrl 中,完成基本的字符串處理後,主要利用 runOnUiThread 開始處理,其中第一步檢測是否需要提供載入的顯示,之後使用 WebView 的 loadUrl 載入內容,並且設置超時時間。
至此,Java 端的準備工作也就完成,實現了 UI 的載入。可以看到,在 DroidGap 類中最重要的一個部分就是截獲 JavaScript 的 prompt 數據。這是實現瀏覽器端和 Java 端通訊的基礎。
3.Plugin 的實現
Plugin 是一個抽象類,實現了 IPlugin 接口,PhoneGap 中利用 Android SDK 實現邏輯代碼的途徑就是通過繼承 Plugin 來實現。應此 Plugin 本身非常簡單,最主要的部分是提供一個 execute 方法,如下:
public abstract PluginResult execute(String action, JSONArray args, String callbackId);

Plugin 的實現中的重要邏輯在其中來實現,它返回的是一個 PluginResult 對象,這個對象主要負責傳遞數據信息,包括的成員主要如下:
    private final int status;

用於返回狀態,status 與一個 enum 類型相關,用於標註插件的執行結果如何。
private final String message;
用於返回數據信息。

PluginManager 的實現
PluginManager 事實上是 DroidGap 類和具體的繼承自 Plugin 的插件的聯繫紐帶,由它來尋找和載入插件,並且調用。
首先,PluginManager 在構造函數中,調用了 loadPlugins 方法,該方法負責解析 xml 配置文件,對應每個 plugin,調用一次 addService 用於註冊插件,具體的註冊地點,是通過維護一個 HashMap<String, String> 來實現,分別保存 serviceType, className。在成員中,有另外一個 HashMap<String, IPlugin> 用於實現 className 和 IPlugin 類的綁定。這樣就實現了 serviceType 和 IPlugin 的對應。
在 PluginManager 中,執行插件的方法 exec 是主要功能,我們將會詳細說明一下這個方法的過程:
  1. 利用查詢之前提到的 HashMap<String, String> 將 service 和 className 對應起來,這樣我們就獲得了提供功能的插件類的位置。
  2. 通過 getClassByName 來獲得插件類。
  3. 完成類型檢測,確定對應的類是一個插件類。
  4. 執行 addPlugin 方法,其中則將第 2 步中獲得插件類的 className 和具體的 Plugin 接口的實現綁定,具體的實現就是上文中提到的 HashMap<Strig,IPlugin>。
  5. 判斷 Plugin 是否需要異步執行,再根據情況,選擇直接執行或者是建立新的線程來執行。
  6. 完成執行後,得到的數據會根據情況交給 CallbackServer 來處理,PluginManager 會調用 DroidGap 中的 sendJavaScript 來將數據交給 CallbackServer,事實上,DroidGap 中的 sendJavaScript 不過是對 CallbackServer 中的 sendJavaScript 包裝,實際調用的是 CallbackServer 中的 sendJavaScript 方法,這涉及到了 CallbackServer 類,隨後就會說明。
執行結果的轉換則是利用 PluginResult 中的一些 to***String 方法。這些方法返回了一個 String 類型,其中包括了具體的 JSON 對象和 callbackId,具體的形式則是一個 js 代碼,用於前端的調用。

CallbackServer 的實現
CallbackServer 實現了 Runnable 接口,具體的功能就是維護一個數據的隊列,並且建立一個服務器,用於 XHR 的數據傳遞,對數據的隊列的維護利用的是 LinkedList<String>。
由於實現的是 Runnable 接口,在 CallbackServer 中,最主要的方法就是 run() 方法,run() 方法的具體內容簡介如下:
  1. 首先利用 ServerSocket 監聽端口,具體端口則自由分配。
  2. 在 accept 後則是對 HTTP 協議的解析,和對應的返回 status code。
  3. 在驗證正確後,利用 getJavascript 方法得到維護的 LinkedList<String>() 中的保存的 js 代碼,如果爲空則返回 null。
  4. 這些具體的 string 類型的 js 代碼則利用 socket 作爲 response 返回給前端。
之後就是對隊列維護的方法,這時理解之前的 sendJavaScript 則很簡單,該方法與 getJavaScript 相反,一個是從 LinkedList 中取出 js 代碼,一個則是加入。
綜上,CallbackServer 實現的是兩個功能,一個是 XHR 的 SocketServer,一個是對隊列的維護。而完成 CallbackServer 類的說明後,Java 端的主要功能也都說明完畢。隨後會說明瀏覽器端的說明,在完成瀏覽器端說明後,我們就會明白整個 PhoneGap 框架運行的原理。

瀏覽器端介紹
瀏覽器端的 JavaScript 代碼較多,但是實現其中核心功能的代碼都在 PhoneGap 類中,其他的都是 PhoneGap 框架中自帶的一些 Plugin 的 JavaScript 代碼。
在瀏覽器端,其中一個最重要的函數就是 PhoneGap.exec 函數,因爲大部分的函數調用,最終都會在這裏實現與本地 Java 端的數據通訊,轉而去調用 Java 端的 Plugin 來實現具體的執行。主要的內容如下,我們已經去除一些非功能的錯誤處理代碼段,集中於具體的功能實現:

代碼清單 2. PhonGap.exec 代碼
var callbackId = service + PhoneGap.callbackId++;
if (success || fail) {
        PhoneGap.callbacks[callbackId] = {success:success, fail:fail};
}
var r = prompt(PhoneGap.stringify(args), "gap:"+
PhoneGap.stringify([service, action, callbackId, true]));
// If a result was returned
if (r.length > 0) {
        eval("var v="+r+";");
        // If status is OK, then return value back to caller
        if (v.status === PhoneGap.callbackStatus.OK) {
             ......
        }else if ......
}
在以上標紅加粗的代碼段上,我們可以看到,調用的是 prompt 方法 , 我們在之前特別提到了,PhoneGap 在 DroidGap 類中,繼承了一個 WebChromeClient,其中重寫了方法 onJsPrompt,這樣在 Java 端,就截獲了瀏覽器端的調用。現在可以回到之前的 Java 端的說明,查看之前列出的 onJsPrompt 代碼,聯繫之前的 Java 端的介紹,就會覺得豁然開朗了,剩下了流程也就順理成章了。通過 r 的返回值,就可以輕鬆的返回數據給瀏覽器端了。
這時我們會有一個好奇,那就是 CallbackServer 的 SocketServer 的作用在哪裏,因爲至此我們已經看到了整個數據在瀏覽器端和 Java 端的通訊,那麼這樣的一個 SocketServer 又是提供的什麼數據通訊呢?
顯然,在 PluginManager 中的 exec中,我們會返回執行獲得的數據,但是,在使用異步調用的時候,立即返回的則是一個空字符串,也即沒有數據返回,之後插件會在一個新的線程中執行,等到異步的插件執行的結果返回數據後,會放入到隊列中,等待 XHR 的獲得。而 XHR 的調用是在 JavaScript 代碼初始化中開始調用的,其中由於 JsCallBack 函數有使用輪詢保證自己循環執行。這樣就可以不斷的獲得異步數據


Hello 插件的實現
掌握了上文中對 PhoneGap 框架的分析,我們就可以通過自己實現相關的插件來完成自己希望實現的功能,這樣就可以不斷豐富自己應用的功能。我們假設開發者已經熟悉了 PhoneGap 和 Android 的基本開發知識,Android 和 PhoneGap 開發環境搭建已經完成。
在具體的 Plugin 實現中,要分兩個部分來實現,Java 代碼和 JavaScript 代碼,就此流程,我們來通過一個簡單的例子來看一下如何具體的完成 Plugin 的實現。
在 Java 端,我們要繼承一個 Plugin 類,實現其中的 execute 方法,在這裏,我們省略具體的功能邏輯。
在 JavaScript 端,我們要利用現有 PhoneGap.exec 來調用具體的 Java 端,而且需要注意的是要在 PhoneGap 中註冊這一個插件,PhoneGap.addConstructor就在 JavaScript 端調用 Java 端代碼,完成添加,當然直接修改 XML 文件來實現也可以。
首先,完成 Java 端的代碼實現。
我們實現一個簡單的字符串回射程序,來模擬具體的邏輯,代碼如下:

清單 3. 插件實例 Java 端代碼
@Override
public PluginResult execute(String action, JSONArray args, String callbackId) {
PluginResult.Status status = PluginResult.Status.OK;
PluginResult r=null;
if (action.equals("sayHello")){
r = new PluginResult(status,"Hello");
}else if (action.equals("saySth")){
r = new PluginResult(status,args);
}
return r;
}

實現的功能如下:
  1. 如果 action 的參數是 sayHello,則返回字符串“Hello”。
  2. 如果是 saySth,則返回 args 中的內容。
之後,完成 JavaScript 代碼
請注意 JavaScipt 代碼是需要 HTML 代碼包含後才能使用的:

清單 4. 插件實例 JavaScript 端代碼
var Hello=function(){
         }
         Hello.prototype.saySth = function(msg, callback, fail) {
return PhoneGap.exec(function(args) {
         callback(args);
}, function(args) {
if(typeof fail == 'function') {
         fail(args);
}
}, 'Hello', 'saySth', [msg]);
         }
         Hello.prototype.sayHello = function(callback, fail) {
return PhoneGap.exec(function(args) {
    callback(args);
}, function(args) {
if(typeof fail == 'function') {
fail(args);
}
}, 'Hello', 'sayHello',['sayHello']);
         }
         PhoneGap.addConstructor(function() {
        PhoneGap.addPlugin('hello', new Hello());
        //PluginManager.addService("Hello","qj.Hello");
         })

在這裏我們需要有一個特別的說明:
在上述 JavaScript 代碼中,我們註釋了 addConstructor 函數中的一行,因爲我們在開發過程中發現這條代碼的執行在 Javascript 端沒有被實現。
因此這段代碼具體的執行則需要我們在 Java 端實現代碼,那麼爲了實現這段 JavaScript 代碼所做的補償性的 Java 代碼要在自己實現的 Java 端的 onCreate 函數中實現,內容是很簡單的一句(當然,也可以通過修改 xml 文件來實現這一功能,上文提到過,PluginManager 類會掃描 xml 文件來依次以對應 xml 文件中的插件名作爲參數執行這一調用):
 
this.pluginManager.addService("Hello", "qj.Hello");

注意第二個參數是實現的插件的代碼的位置,Hello 插件的位置是“src.qj.Hello.java”,對應的位置信息就是去除 .java 後綴名後的位置信息,整個項目的組織結構如圖。

圖 1. 項目組織結構圖示例

至此,Hello 插件已經實現,如果需要調用,則 JavaScript 代碼如下:

清單 5. 插件調用代碼
window.plugins.hello.sayHello(function(arg){alert(arg);},
function(arg){alert(arg);});
window.plugins.hello.saySth("Bingo",function(arg){alert(arg);},
function(arg){alert(arg);});
我們簡單的將返回的數據通過 alert() 展示出來,插件的執行結果如下:
點擊 SayHello,

圖 2. 點擊 SayHello 示例

點擊 SayBingo

圖 3. 點擊 SayBingo 示例

結束語
至此插件的開發已經完成,其中很大的篇幅都是通過對 PhoneGap 的框架的說明,讓我們可以對插件的運行過程有一個更加底層的認識,我想這是比實現一個簡單的插件更加重要的東西。如果讀者需要了解更多的信息,請瀏覽參考資料中的開發者網站,獲得更加實時的信息。

參考資料
學習
原文來自:

補充:
現在phoengap在擴展插件的時候更加方便了,在plugin.xml,添加自己的包名和類,不需要下面的這行代碼:
this.pluginManager.addService("Hello""qj.Hello");

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章