1. 前言
在《用PhoneGap+jQueryMobile開發Android應用執行個體》中,我們講到PhoneGap(以下稱Cordova)開發環境的搭建,以及如何整合出一個基本的Android應用程式框架(並給出了範例代碼)。於是乎,我們便開始日夜兼程,披星戴月的炮製我們的第一個手機應用了。
但實際上,除了常見的API調用規範(有且僅有自查手冊一途)引起的問題之外,我們仍然會遇到其他形形色色的各種問題。那麼在這篇文章中,我們談談java與js之間的互動問題(哦,目前僅關注Android,所以只能談java了)。當然,二者之間的互動目的,原因會有種種不同,但應該還是以發揮語言各自的優勢,提供介面給對方調用的意圖居多。
我們知道,在Android平台下,Cordova是通過內建WebKit核心的方式來實現介面容器的(事實上,在其他平台也是如此)。我們也同樣知道,Cordova是一個橋接架構,其目的就是為原生API和js建立橋接,互連有無的。為了便於我們擴充自己的應用,Cordova還提供了外掛程式(PlugIn)機制(當然,我們還可以直接修改Cordova開原始碼)。只要遵循一定的規則(恩恩,事實上這個規則很簡單),就可以擴充出豐富的功能特效來。
2. Cordova外掛程式與 WebView.addJavascriptInterface
恩恩,本文的主題是java與js的互動(差點跑了)。剛提到的,Cordova有外掛程式機制,可以通過外掛程式的形式,實現java與js互動,為什麼還要提到addJavascriptInterface?
Cordova外掛程式確實可以實現二者的互動,而且是非同步,非常方便。但基於一些特殊的原因,例如:一個回調需要被多次調用(啊哈,或許是我太菜?使用PlugIn註冊的回調都只能被調用一次)。又或者不想寫外掛程式,想直接點。
總之,外掛程式也並不是時時處處都符合我們的需求(我們的慾望無窮大啊),總是要找辦法解決,尋點不同的路出來(個人不是特別認同Cordova的Hack方式,遑論其外掛程式;而且Cordova目前的狀態有點怪異,版本更新是很快,但文檔更新不同步)。要真正成熟,還是有一段路要走的。
addJavascriptInterface則是WebKit的原生API,屬於WebView對象的公用方法,用於暴露一個java對象給js,使得js可以直接調用java方法。當然,我們要實現java與js的雙向互動,還需要另一個方法loadUrl(同屬於WebView對象,Cordova也是採用的這個方法調用js的)的配合。
當然,這兩種方式互有優劣(只有實踐時,才會明白啊)。Cordova外掛程式的不足剛才已經提過;而addJavascriptInterface也有些問題,一是Android平台封裝WebKit核心時,不同的版本中有些許不一致;其次,直接使用loadUrl載入js實在是讓人頭疼。
其實應該有更好的方法,比如擴充js引擎(我更喜歡這種方式),但這種方式相對而言,涉及的內容繁雜,暫時不納入這次的話題。
3. Cordova外掛程式的實現
Cordova外掛程式分為兩個部分(額,Cordova本身也是分為兩個部分的,彆扭不?),一部分由java實現,另一部分由js實現。
1) java部分
Cordova外掛程式的java部分很簡單,繼承Cordova.Plugin,實現execute方法就可以了:
public classNotificationClient extends Plugin {
private static final String TAG ="NotificationClient";
private String callbackId ="";
public PluginResult execute(Stringaction, JSONArray args, String callbackId) {
PluginResult.Status status =PluginResult.Status.OK;
if(action.equals("register")){
try {
register(args.getString(0),args.getString(1));
} catch(JSONException e) {
status =PluginResult.Status.JSON_EXCEPTION;
}
} elseif(action.equals("watch")) {
this.callbackId =callbackId;
PluginResult r = newPluginResult(PluginResult.Status.NO_RESULT);
r.setKeepCallback(true);
return r;
} else {
status =PluginResult.Status.INVALID_ACTION;
}
return newPluginResult(status);
}
public Object onMessage(String id,Object data) {
Log.d(TAG,"onMessage(" + id + ").");
if(id.equals("onClientNotification")){
if(!callbackId.equals("")){
this.success("true",callbackId);
}
}
return data;
}
private void register(String username,String phone) {
Log.d(TAG,"register(" + username + ", " + phone + ").");
}
}
嗯,就這樣,作為一個Cordova外掛程式java部分的範例,他已經完成了使命(原諒我為了節省篇幅,刪掉了注釋和空行;不必太多介懷,參考資源裡有很多範例工程可以學習)。
不得不說,Cordova還是做了很多工作的,為了減輕外掛程式開發的工作量,對js的調用進行了很多的封裝(回頭看看loadUrl是多麼的貧瘠的時候,才會有如此感慨吧)。
2) js部分
唉,讓我渾身彆扭的部分來了。說到js部分,我接觸過的版本裡(當然,我也僅僅接觸過3個版本而已:1.0、2.0、2.1)已經有兩種寫法。嗯,從執行效果上來說,2.0是相容1.0的寫法的(哦哦,前提是我做了一些改動,雖然改動很小);美中不足的是,跟蹤指令碼時還是會報錯,雖然不影響指令碼的繼續載入。
先來看看第一種寫法吧(1.0的寫法):
functionNotificationClient() { }
NotificationClient.prototype.register= function(userName, phone) {
PhoneGap.exec(null, null,"NotificationClient", "register", [ userName, phone ]);
};
NotificationClient.prototype.watch= function(fn) {
PhoneGap.exec(fn, null,"NotificationClient", "watch", []);
};
PhoneGap.addConstructor(function(){
if(typeof navigator.notificationClient== "undefined")
navigator.notificationClient= new NotificationClient();
});
網上的教程都是這麼弄的,事實上運行時會報錯:找不到PhoneGap對象;更嚴重的是navigator.notificationClient在運行時根本無法訪問。
當然,如果你改成這樣:
// PhoneGap.addConstructor(function(){
if(typeof navigator.notificationClient== "undefined")
navigator.notificationClient= new NotificationClient();
// });
程式是可以正常啟動並執行,雖然仍然會報錯。
OK,再來看看第二種(2.0的寫法):
cordova.define("cordova/plugin/notificationClient",function(require, exports, module){
var exec = require('cordova/exec');
var NotificationClient = function() {};
NotificationClient.prototype.register =function(userName, phone) {
exec(null, null,"NotificationClient", "register", [ userName, phone ]);
};
NotificationClient.prototype.watch =function(fn) {
exec(fn, null,"NotificationClient", "watch", []);
};
var notificationClient = newNotificationClient();
module.exports = notificationClient;
});
if(!window.plugins) {
window.plugins = { };
}
if(!window.plugins.notificationClient) {
window.plugins.notificationClient =cordova.require("cordova/plugin/notificationClient");
}
恩,這種寫法沒有任何錯誤了,而且能正常運行,開心。
3) 註冊外掛程式
把外掛程式寫完之後,還需要註冊,外掛程式才能在Cordova下使用。找到工程目錄下的res\xml目錄,1.0開啟plugins.xml檔案,2.0開啟config.xml檔案,在plugins節點下加入:
<pluginname="NotificationClient"value="cn.yofang.mobile.NotificationClient"/>
至此,NotificationClient外掛程式就可以在js中調用了。
1.0的用法:
navigator.notificationClient.register("azhi","15810108888");
2.0的用法:
window.plugins.notificationClient.register("azhi","15810108888");
4. WebView.addJavascriptInterface實現
啊,終於到addJavascriptInterface了,每次文檔寫到一半左右都手酸呐(看文檔的人是不是也暗歎了一聲:終於來了)。
addJavascriptInterface比起Cordova外掛程式來更加的簡單,首先我們來定義一個類:
public classNotificationClient {
private static final String TAG ="NotificationClient";
private Context context = null;
private CordovaWebView view = null;
private String callback = "";
public NotificationClient(Contextcontext, CordovaWebView view) {
this.context = context;
this.view = view;
}
public void register(String user,String mobile, String callback) {
Log.d(TAG, "register(user: " + user + ", mobile: " + mobile + ", callback:" + callback + " )");
this.callback = callback;
checkMessage();
}
public void checkMessage() {
SharedPreferences sp =context.getSharedPreferences("NotificationClient", 0);
int message =Integer.valueOf(sp.getInt("Message", 0));
Log.d(TAG,"checkMessage(): " + message);
if(message > 0) {
Editor editor =sp.edit();
editor.putInt("Message",0);
editor.commit();
newHandler().post(new Runnable() {
public voidrun() {
view.sendJavascript(callback);
}
});
}
}
};
這個類的意圖很簡單(嗯,跟上面Cordova外掛程式的NotificationClient外掛程式很相似對不對?):提供一個register方法供js調用,傳入相應的參數增加了一個callback,在合適的時機(通過SharedPreferences檢查Message標誌,大於0則認為是合適的時機了),從java端調用這個callback(當然,代碼裡使用了SharedPreferences、Handler等其他的Android原生對象,大家暫時忽略就是)。
謔,我是不是沒有用loadUrl,而是用的sendJavascript?sendJavascript是Cordova對WebView封裝後提供的方法,其實把那一句改成:
view.loadUrl("javascript:"+ this.callback);
效果是一樣的(當然,如果你不是用Cordova,而是自己寫的Activity,那麼你就必須得這麼寫了)。
好了,類寫完了,下面就應該把這個類暴露給js了:
appView.getSettings().setJavaScriptEnabled(true); // 暴露之前,先開啟javascript
appView.addJavascriptInterface(newNotificationClient(this, appView), "notificationClient");
嗯,這裡用到了appView(DroidGap的成員變數),我們使用的Cordova嘛,所以用這個沒有罪過的。如果是直接實現的Activity,就要自己內嵌WebView了,把appView改成自己的WebView對象即可。
再就是js裡的用法了:
window.notificationClient.register("azhi","15810108888","OnMessage();");
大家看到了,在js中調用時,還是比較方便的,不需要預先建立js類對象,通過addJavascriptInterface添加的對象直接就附加在window對象上了。但弊端也是很明顯,看看我們的callback,是以代碼形式傳入的(當然了,其實是可以改良的,但今天就不聊這個了)。
呵呵,稍微來點結束語:就這樣吧,希望大家都有所收穫。