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个线程