Android Html5開發(二)-Cordova
本文介紹Android Html的開發框架Cordova
目錄
一.Cordova簡介
二.Cordova開發環境搭建
三.添加Cordova插件
四.添加自定義插件
五.JavaScript調用Android代碼的過程
六.Android 和 Html5混合開發的性能
一.Cordova簡介
介紹Cordova前需要先介紹下PhoneGap.
PhoneGap是一個用基於HTML,CSS和JavaScript的,創建移動跨平台移動應用程序
的快速開發平台。它使開發者能夠利用iPhone,Android,Palm,Symbian,WP7,
WP8,Bada和Blackberry智能手機的核心功能——包括地理定位,加速器,聯系人,聲
音和振動等,此外PhoneGap擁有豐富的插件,可以調用。
業界很多主流的移動開發框架均源於PhoneGap。較著名的有Worklight、appMobi、
WeX5等;其中WeX5為國內打造,完全Apache開源,在融合Phonegap的基礎上,
做了深度優化,具備接近Native app的性能,同時開發便捷性也較好。
PhoneGap和Cordova的關系:
在2011年10月,Adobe收購了Nitobi Software和它的PhoneGap產品,然后宣布這個移動Web開發框架將會繼續開源,並把它提交到Apache Incubator,以便完全接受ASF的管治。當然,由於Adobe擁有了PhoneGap商標,所以開源組織的這個PhoneGap v2.0版產品就更名為Apache Cordova.Cordova是PhoneGap貢獻給Apache后的開源項目,是從PhoneGap中抽離出的核心代碼,是驅動PhoneGap的核心引擎.
也就是說PhoneGap是Adobe的商業產品,Cordova是Apache的開源項目,我們在使用時應當使用Cordova.
二.安裝Cordova
Cordova 官網
安裝Cordova開發環境
http://cordova.apache.org/#getstarted
在安裝Cordova前需要先安裝Node.js 和NPM
安裝命令
npm install -g cordova
創建你的第一個應用
http://cordova.apache.org/docs/en/6.x/guide/cli/index.html
1. cordova create hello com.example.hello HelloWorld
2. cd hello
3. cordova platform add android --save
添加Android platfrom之后可以用Eclipse 導入hello/platforms/android下的工程
一個是CordovaLib ,一個是MainActivity.
CordovaLib 時Cordova框架的源碼.
如圖:
CordovaLib 提供了JavaScript和Android之間相互調用的框架.
在第一個Cordova HelloWorld程序中,MainActivity代碼如下
加載的launchUrl,是assets/www/index.html.
Cordova 是基於插件開發的,Android提供給JavaScript調用的API應該已插件的形式提供.Cordova 本身已經擁有豐富的插件.下面介紹如果添加Cordova 插件
三.添加Cordova插件
這里以添加contacts
插件為例
執行
:cordova plugin add cordova-plugin-contacts
就可以添加
cordova-plugin-contacts
插件
Android
部分
添加之后會在
MainActiviy
里面增加如下代碼
:
這部分就是通訊錄插件Android端所有的代碼.其中ContactManager繼承了
CordovaPlugin,並重寫了下面方法.
publicboolean execute(String action, CordovaArgs args, CallbackContext callbackContext)
在/res/xml/config.xml文件中增加了
<featurename="Contacts">
<paramname="android-package"value="org.apache.cordova.contacts.ContactManager"/>
</feature>
用於注冊 Contacts插件
feature name="Contacts"是定義js通過下面方法調用Android接口時傳入的
The plugin's JavaScript interface uses the cordova
.
exec
method as follows:
exec
(<
successFunction
>,
<failFunction>
,
<service>
,
<action>
,
[<
args
>]);
service名
<paramname="android-package"value="org.apache.cordova.contacts.ContactManager"/>
是ContactManager類的全名
JavaScript部分:
在/assets/www/plugins目錄下增加了一個cordova-plugin-contacts文件夾存放Contacts插件所有的js代碼
在/assets/www/cordova_plugins.js 里面注冊該模塊
其中
"clobbers": [
"navigator.contacts"
]
聲明了
contacts.js 里面定義的contacts對象可以通過navigator.contacts來訪問.
使用示例:
在/assets/www/js/index.js 的 onDeviceReady 方法里面加入如下代碼,調用
查找所有聯系人的方法.
function onSuccess(contacts) {
for (var i = 0; i < contacts.length; i++) {
console.log(contacts[i].displayName);
}
};
function onError(contactError) {
alert('onError!');
};
// find all contacts
var options = new ContactFindOptions();
options.filter = "";
options.multiple = true;
var filter = ["displayName", "addresses"];
navigator.contacts.find(filter, onSuccess, onError, options);
參考:
http://cordova.apache.org/docs/en/latest/cordova-plugin-contacts/index.html
四.添加自定義插件
這里以添加一個EchoPlugin插件為例
Android端:
1.編寫 EchoPlugin繼承 CordovaPlugin並重寫execute方法
publicclass EchoPlugin extends CordovaPlugin {
privatestaticfinal String TAG = EchoPlugin.class.getSimpleName();
@Override
publicvoid initialize(CordovaInterface cordova, CordovaWebView webView) {
super.initialize(cordova, webView);
// your init code here
Log.e(TAG, "initialize ");
}
@Override
publicboolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
Log.e(TAG, "action == " + action);
Log.e(TAG, "args == " + args);
Log.e(TAG, "callbackContext == " + callbackContext);
if ("echo".equals(action)) {
callbackContext.success(args + " from android ");
returntrue;
}
returnfalse; // Returning false results in a "MethodNotFound" error.
}
}
2.在/res/xml/config.xml文件中增加:
<featurename="Echo">
<paramname="android-package"value="com.example.echo.EchoPlugin"/>
</feature>
注冊EchoPlugin插件,這樣就完成了Android端的插件開發
JavaScript端:
如果只想調用EchoPlugin提供的方法,執行下面代碼即可
cordova.exec(function(msg) {
console.log(msg);
}, function(err) {
console.log("Nothing to echo.'" + err);
}, "Echo", "echo", []);
service名為Echo,action 為echo
但實際使用中我們應該在js端也實現模塊化.
1.在/assets/www/plugins 目錄下新建
com-example-echo 目錄,並新建一個echo.js 文件
里面的代碼如下:
//定義一個id 為com-example-echo.echo的模塊
//模塊注冊,加載 參照 http://rensanning.iteye.com/blog/2047324
cordova.define("com-example-echo.echo", function(require, exports, module) {
//加載cordova/exec模塊
var exec = require('cordova/exec');
//定義一個Echo 對象
var Echo = {
//為Echo對象定義一個echo方法
echo: function(msg) {
console.log("ech0");
exec(function(msg) {
console.log(msg);
}, function(err) {
console.log("Nothing to echo.'" + err);
}, "Echo", "echo", [msg]);
}
};
//模塊的輸出為Echo對象
module.exports = Echo;
});
這段代碼的含義是定義一個id為”com-example-echo.echo”的模塊,模塊的輸出時一個Echo的對象.
然后在/assets/www/cordova_plugins.js 里面 注冊該模塊
{
"file": "plugins/com-example-echo/echo.js",
"id": "com-example-echo.echo",
"pluginId": "com-example-echo",
"clobbers": [
"navigator.Echo"
]
}
這樣就可以通過 navigator.Echo來調用該方法.
調用示例如下:
在/assets/www/js/index.js的onDeviceReady方法里面加入
navigator.Echo.echo('hello word');
即可.
參考
http://cordova.apache.org/docs/en/latest/guide/platforms/android/plugin.html
五.JavaScript調用Android API的內部執行過程
這里就以調用 navigator.Echo.echo('hello word')方法為例,首先對代碼進行一下剖析
1)調用navigator.Echo.echo('hello word')方法,實際執行的是下面代碼
cordova.exec(function(msg) {
console.log(msg);
}, function(err) {
console.log("Nothing to echo.'" + err);
}, "Echo", "echo", []);
查看cordova.js 源碼,可以看到
modulemapper.clobbers('cordova/exec', 'cordova.exec');
modulemapper.clobbers('cordova/exec', 'Cordova.exec');
cordova.exec的定義如下
define("cordova/exec", function(require, exports, module) {
…/
module.exports = androidExec;
});
也就是說cordova.exec指向的是 androidExec 方法,
androidExec的定義如下:
function androidExec(success, fail, service, action, args)
參數說明:
success:成功回調
fail:失敗回調
service:插件對應的 service,對應EchoPlugin中的Echo,用於從插件列表中找到EchoPlugin這個插件
action:執行的哪個方法,對應EchoPlugin中的echo;
args:參數,是一個JSONArray;
從該方法可以看出JS調用Android Native接口都是通過回調異步調用的.
androidExec執行時調用的是
var msgs = nativeApiProvider.get().exec(bridgeSecret, service, action, callbackId, argsJson);
nativeApiProvider.get()指向的是下面方法
define("cordova/android/promptbasednativeapi", function(require, exports, module) {
/**
* Implements the API of ExposedJsApi.java, but uses prompt() to communicate.
* This is used pre-JellyBean, where addJavascriptInterface() is disabled.
*/
module.exports = {
exec: function(bridgeSecret, service, action, callbackId, argsJson) {
return prompt(argsJson, 'gap:' + JSON.stringify([bridgeSecret, service, action, callbackId]));
},
setNativeToJsBridgeMode: function(bridgeSecret, value) {
prompt(value, 'gap_bridge_mode:' + bridgeSecret);
},
retrieveJsMessages: function(bridgeSecret, fromOnlineEvent) {
return prompt(+fromOnlineEvent, 'gap_poll:' + bridgeSecret);
}
};
});
最終調用的是:
prompt(argsJson, 'gap:' + JSON.stringify([bridgeSecret, service, action, callbackId]));
prompt是Window 對象的一個方法,用於顯示可提示用戶輸入的對話框。
參照:http://www.w3school.com.cn/jsref/dom_obj_window.asp
在Android端調用該方法會回調WebView的WebChromeClient的
publicboolean onJsPrompt(WebView view, String origin, String message, String defaultValue, final JsPromptResult result);
方法.
查看CordovaLib源碼,可以看到在SystemWebChromeClient里面對該方法進行了重寫
publicboolean onJsPrompt(WebView view, String origin, String message, String defaultValue, final JsPromptResult result) {
// Unlike the @JavascriptInterface bridge, this method is always called on the UI thread.
Log.d(TAG, "onJsPrompt " + origin + " message == " + message);
String handledRet = parentEngine.bridge.promptOnJsPrompt(origin, message, defaultValue);
if (handledRet != null) {
result.confirm(handledRet);
} else {
dialogsHelper.showPrompt(message, defaultValue, new CordovaDialogsHelper.Result() {
@Override
publicvoid gotResult(booleansuccess, String value) {
if (success) {
result.confirm(value);
} else {
result.cancel();
}
}
});
}
returntrue;
}
執行調用的關鍵代碼是
String handledRet = parentEngine.bridge.promptOnJsPrompt(origin, message, defaultValue);
promptOnJsPrompt中關鍵代碼是
String r = jsExec(bridgeSecret, service, action, callbackId, message);
jsExec中關鍵代碼是
pluginManager.exec(service, action, callbackId, arguments);
exec中
//根據service 獲取對應插件
CordovaPlugin plugin = getPlugin(service);
//執行對應插件的execute 方法
booleanwasValidAction = plugin.execute(action, rawArgs, callbackContext);
這也就是為什么編寫插件時需要繼承 CordovaPlugin,並重寫 execute方法.
@Override
publicboolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
Log.e(TAG, "action == " + action);
Log.e(TAG, "args == " + args);
Log.e(TAG, "callbackContext == " + callbackContext);
if ("echo".equals(action)) {
callbackContext.success(args + " from android ");
// if fail
callbackContext.error("err ");
returntrue;
}
returnfalse; // Returning false results in a "MethodNotFound" error.
}
在 execute 方法執行完后,通過 callbackContext.success()或者callbackContext.error()將結果回調給JS端.
查看源碼,可以知道, callbackContext最終是通過loadUrl的方式,將結果回調給JS的.
String js = queue.popAndEncodeAsJs();
if (js != null) {
engine.loadUrl("javascript:" + js, false);
}
六.Android 和 Html5混合開發的性能
這里只截取運行一個Android Hello word程序和Android +HTML5混合開發時,程序運行后的線程截圖
1.Android Hello word,程序運行后會有18個線程
2.不使用cordova,直接使用webiew時,程序允許后會有31個線程
3.使用cordova框架,程序允許后會有36個線程