nativeToJsModes = {
// Polls for messages using the JS->Native bridge.
POLLING: 0,
// For LOAD_URL to be viable, it would need to have a work-around for
// the bug where the soft-keyboard gets dismissed when a message is sent.
LOAD_URL: 1,
// For the ONLINE_EVENT to be viable, it would need to intercept all event
// listeners (both through addEventListener and window.ononline) as well
// as set the navigator property itself.
ONLINE_EVENT: 2
},
jsToNativeBridgeMode, // Set lazily.
nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT,
可以解决loadUrl 隐藏键盘的问题:当你的焦点在输入,如果这时loadUrl调用js,会导致键盘隐藏 see
Cordova
官网文档
常用命令
npm install -g cordova
安装cordovacordova create <PATH> [ID [NAME [CONFIG]]] [options]
创建一个项目如cordova create hello com.example.hello HelloWorld
cordova platform add android
添加Android 平台cordova build
编译,cordova buld android
只编译Android 平台cordova help <command>
查看某个命令的用法以下命令在项目根目录执行
cordova plugin
显示当前项目中使用了哪些插件cordova plugin add {plugin name}
添加插件一个混合框架要有的
MainActivity(CordovaActivity)的作用
onConfigurationChanged
),需要传递到Plugin 中去onRequestPermissionsResult
),需要传递到Plugin 中去JS 和Native间的交互
cordova.js
中的SystemWebViewEngine
中暴露接口的操作SystemWebChromeClient
中的onJsPrompt
方法重写来实现的。这样前端的JS和原生连接便建立起来,接下来看原生如何向前端返回数据:
题外话:流程图的制作参看这里,下方是该流程图的代码
插件是如何集成进来的
以
camera
插件为例,为测试的工程添加了camer插件,于是项目的结构变成了如下这样子:首先看官网上关于
camera
插件的使用是这样子的navigator.camera.getPicture(cameraSuccess, cameraError, cameraOptions);
,于是我们想知道navigator
和camera
分别是什么是什么时候初始化的,为什么可以直接用。然后从cordova.js
中找到了navigator
的定义。从cordova_plugin.js
中看到了这么一段代码前段代码没有做太多深究,但估计应该是在这里把camera对象挂载到
navigator
上。当执行navigator.camera.getPicture
方法的时候,会调用Camera.js
中的cameraExport.getPicture = function(successCallback, errorCallback, options)
, 最终调用exec(successCallback, errorCallback, "Camera", "takePicture", args);
。再看一下exec
方法到底是什么,还是倒推。首先看到Camera.js
中有exec = require('cordova/exec')
, 再发现在cordova.js
中有define("cordova/exec", function(require, exports, module)
, 最终发现其流程是exec --> androidExec ----> 获取原生对象,并执行其 exec
。原生对象对应的是SystemExposedJsApi
, 其exec
方法会调用CordovaBridge
对象的jsExec
方法,在CordovaBridge
对象的jsExec
方法中会去调用PluginManager
的exec
方法,最终由交给具体插件的execute
方法去执行。插件何时加载 在
CordovaActivity
中的loadConfig
会取加载配置文件,然后获得插件的信息,最终会把这些信息保存到PluginManager
实例的一个Map中,此时插件对象并没有被初始化。当前端调用到ExposedJsApi
对象的方法时,PluginManager会去从那个Map查找对应的这plugin, 此时如果插件还未实例化,就利用反射实例化。Cordova应用的启动
Cordova应用会首先启动主Activity, 在主Activity的
onCreate
方法中会去做两件事:CordovaActivity
)的onCreate
方法loadUrl(launchUrl);
先看第一步做的操作:
loadconfig()
:解析res/xml/config.xml
文件,读取相关的配置config.xml
中的配置对window进行相应的配置。具体有:getWindow().requestFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags()
cordovaInterface
cordovaInterface
中做数据恢复。 关于CordovaInterface我们可以理解成是一个针对于Cordova
插件的帮助类,CordovaPlugin
中会持有一个CordovaInterface
实例再看loadUrl做的操作(直接看loadUrl中的init方法):
makeWebView
生成CordovaWebView
:CordovaWebViewImpl
的构造方法生成一个CordovaWebView
, 在调用CordovaWebViewImpl
的构造方法时会调用makeWebViewEngine
, 然后调用CordovaWebViewImpl.createEngine
方法生成一个CordovaWebViewEngine
对象,该对象的具体实现类是SystemWebViewEngine
。所以CordovaWebView
中会持有一个CordovaWebViewEngine
实例。CordovaWebView
和Android中的WebView
没有什么关系,它只是一个接口。createViews
为CordovaWebView
中的WebView
视图设置相应的UI属性,同时调用setContentView
将WebView
设置到屏幕上。appView.init(cordovaInterface, pluginEntries, preferences)
: 初始化CordovaWebView
PluginManager
CordovaResourceApi
NativeToJsMessageQueue
nativeToJsMessageQueue
添加BridgeMode
: 当前定义的只有两个BridgeMode
, 分别是NativeToJsMessageQueue.NoOpBridgeMode
,NativeToJsMessageQueue.LoadUrlBridgeMode
但是NoOpBridgeMode
中的onNativeToJsMessageAvailable
实现为空, 没有对消息进行处理,是因为在这种模式下会依赖前端js的轮询。CordovaWebViewEngine
的初始化方法init
: 在其实现类SystemWebViewEngine
中往nativeToJsMessageQueue
添加一个OnlineEventsBridgeMode
, 至此共有三种不同的BridgeMode。PluginManager
的addService
方法PluginManager
的init
方法cordovaInterface.onCordovaInit(appView.getPluginManager())
: 发送插件CoreAndroid
resume事件。 到此处Cordova相关的组件已经准备就绪相关类的理解
CordovaBridge
在初始化SystemWebViewEngine
的时候会构造一个CordovaBridge
实例。前端js中的对象_cordovaNative
对应的原生类是SystemExposedJsApi
, 当前端在调用exec
,setNativeToJsBridgeMode
等方法时,通过CordovaBridge
的jsExec
,setNativeToJsBridgeMode
, 再通过PluginManager
定位到具体的插件上。NativeToJsMessageQueue
用来保存将会发送到WebView的消息 每一个插件执行完后会调用CallbackContext
中的sendPluginResult
,success
,error
方法,最终都会调用CordovaWebView
中的sendPluginResult
, 该方法中会调用nativeToJsMessageQueue.addPluginResult(cr, callbackId);
, 将执行后的结果放到MessageQueue中。在NativeToJsMessageQueue
的enqueueMessage
方法,其实现如下:当向队列中加一条消息的同时,也会触发
activeBridgeMode.onNativeToJsMessageAvailable(this);
操作。在onNativeToJsMessageAvailable
中会有取消息,并向webView传递的操作。如LoadUrlBridgeMode
中关于BridgeMode
BridgeMode分两种,一种是NativeToJsBridgeMode,另一种是JsToNativeBridgeMode.
NativeToJsBridgeMode
默认情况下是ONLINE_EVENT模式,在
cordova.js
中有这么一段代码:可以解决loadUrl 隐藏键盘的问题:当你的焦点在输入,如果这时loadUrl调用js,会导致键盘隐藏 see
JsToNative
这部份内容参看这篇博文
prompt
方法NativeToJs
从Java到JS通信有4种方式, 经常使用的是Polling方法和ONLINE_EVENT
webView.loadUrl("javascript:")
的方法、关于安全性的问题;
webView的
addJavascriptInterface
可能会导致安全漏洞,因为js可能会包含恶意代码。解决方案@JavascriptInterface
addJavascriptInterface
,Cordova
中利用的是onJsPrompt
接口。removeJavascriptInterface
方法移除操作系统开放的searchBoxJavaBridge_
、accessibility
、accessibilityTraversal
接口, 但这几个方法都在API Level >= 11才有的。在AppCan
的引擎中也是采用这种方式一篇和这个相关的文章
Cordova中的线程
Cordova中存在着主线程和
WebCore
两种类别线程,WebCore
指的是子线程,插件的execute
方法都执行在子线程中。我们来验证一下:MainActivity
中加入打印线程id的方法:Camera
插件,在其CameraLauncher.java
中的execute
中加入打印线程的方法SystemExposedJsApi
的线程, 在其exec
方法中加入java System.out.println("[Thread]: SystemExposedJsApi " + Thread.currentThread().getName() + " " + Thread.currentThread().getId());
SystemWebViewEngine
的loadUrl方法中加入打印线程的语句最终运行时,发现打印的结果如下:
由此可以看到前端发一个请求到原生这边来是在子线程接收,
Camera
插件的execute方法也是执行在子线程中,执行完成后会调用loadUrl
方法,而该方法是执行在主线程中的。此处的线程切换是在NativeToJsMessageQueue
来做。在BridgeMode中的onNativeToJsMessageAvailable
都会调用runOnUiThread
将相关的操作放到主线程中去做。