編輯:關於Android編程
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,是以代碼形式傳入的(當然了,其實是可以改良的,但今天就不聊這個了)。
呵呵,稍微來點結束語:就這樣吧,希望大家都有所收獲。
1 背景不能只分析源碼呀,分析的同時也要整理歸納基礎知識,剛好有人微博私信讓全面說說Android的動畫,所以今天來一發Android應用的各種Animation大集合。
初看這個博文名,我都蒙蔽了,Activity的啟動模式居然能扯到內存問題,還有內存洩漏問題,WTF!!!不要方,小司機我帶你理解和稍微深入的探討一下Activity的四種
前言搞安卓的開發應該知道開發好的apk都是需要上傳到應用市場給用戶下載的,當然有些公司的產品是不用上傳到應用市場的(比如我們公司放七牛雲)但是也需要放在雲上面給用戶下載。
最近無意中看到釘釘這個App,發現聯系人詳情這個界面的效果還是蠻好看的。正好最近在看Material Design,所以想看看可不可用Android系統原生的來實現這種效