cordova封装了一套js和OC通信的代码,cordova.js下的iOSExex是关键的js去调原生的发起点。
function iOSExec() {
var successCallback, failCallback, service, action, actionArgs;
var callbackId = null;
if (typeof arguments[0] !== 'string') {
// FORMAT ONE
successCallback = arguments[0];
failCallback = arguments[1];
service = arguments[2];
action = arguments[3];
actionArgs = arguments[4];
// Since we need to maintain backwards compatibility, we have to pass
// an invalid callbackId even if no callback was provided since plugins
// will be expecting it. The Cordova.exec() implementation allocates
// an invalid callbackId and passes it even if no callbacks were given.
callbackId = 'INVALID';
} else {
throw new Error('The old format of this exec call has been removed (deprecated since 2.1). Change to: ' +
'cordova.exec(null, null, \'Service\', \'action\', [ arg1, arg2 ]);'
);
}
// If actionArgs is not provided, default to an empty array
actionArgs = actionArgs || [];
// Register the callbacks and add the callbackId to the positional
// arguments if given.
if (successCallback || failCallback) {
callbackId = service + cordova.callbackId++;
cordova.callbacks[callbackId] =
{success:successCallback, fail:failCallback};
}
actionArgs = massageArgsJsToNative(actionArgs);
var command = [callbackId, service, action, actionArgs];
// Stringify and queue the command. We stringify to command now to
// effectively clone the command arguments in case they are mutated before
// the command is executed.
commandQueue.push(JSON.stringify(command));
// If we're in the context of a stringByEvaluatingJavaScriptFromString call,
// then the queue will be flushed when it returns; no need for a poke.
// Also, if there is already a command in the queue, then we've already
// poked the native side, so there is no reason to do so again.
if (!isInContextOfEvalJs && commandQueue.length == 1) {
pokeNative();
}
}
OC里执行js很简单,就是evaluateJavaScript:js方法,执行js的地方最后都走到evalJsHelper2这里
- (void)evalJsHelper2:(NSString*)js
{
CDV_EXEC_LOG(@"Exec: evalling: %@", [js substringToIndex:MIN([js length], 160)]);
[_viewController.webViewEngine evaluateJavaScript:js completionHandler:^(id obj, NSError* error) {
// TODO: obj can be something other than string
if ([obj isKindOfClass:[NSString class]]) {
NSString* commandsJSON = (NSString*)obj;
if ([commandsJSON length] > 0) {
CDV_EXEC_LOG(@"Exec: Retrieved new exec messages by chaining.");
}
[_commandQueue enqueueCommandBatch:commandsJSON];
[_commandQueue executePending];
}
}];
}
可以看到就是evaluateJavaScript方法去执行一段js。
下面具体来看一下:
1.首先一个插件肯定在js里去调用,其实都是走exec这个方法,具体是走iOSExec(),还是androidExec(),对应各自平台。
iOSExec在上面已经粘贴了,最下面有个pokeNative(),看方法名也猜出来是去调native端的代码
function pokeNative() {
...
execIframe.contentWindow.location = 'gap://ready';
...
}
前面js通过创建一个iframe并发送gap://ready这个指令来告诉native开始执行操作。
(ios在7.0以前没有提供js直接调OC的代码,所以通过下面这种方式url截取。在7.0以后版本的JavaScriptCore就可以)
native中对应的操作在CDVUIWebViewNavigationDelegate.m(老的cordova好像再CDVViewController.m下面)文件中的webView:shouldStartLoadWithRequest:navigationType:方法
- (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
{
NSURL* url = [request URL];
CDVViewController* vc = (CDVViewController*)self.enginePlugin.viewController;
/*
* Execute any commands queued with cordova.exec() on the JS side.
* The part of the URL after gap:// is irrelevant.
*/
if ([[url scheme] isEqualToString:@"gap"]) {
[vc.commandQueue fetchCommandsFromJs];
// The delegate is called asynchronously in this case, so we don't have to use
// flushCommandQueueWithDelayedJs (setTimeout(0)) as we do with hash changes.
[vc.commandQueue executePending];
return NO;
}
/*
* Give plugins the chance to handle the url
*/
BOOL anyPluginsResponded = NO;
BOOL shouldAllowRequest = NO;
for (NSString* pluginName in vc.pluginObjects) {
CDVPlugin* plugin = [vc.pluginObjects objectForKey:pluginName];
SEL selector = NSSelectorFromString(@"shouldOverrideLoadWithRequest:navigationType:");
if ([plugin respondsToSelector:selector]) {
anyPluginsResponded = YES;
shouldAllowRequest = (((BOOL (*)(id, SEL, id, int))objc_msgSend)(plugin, selector, request, navigationType));
if (!shouldAllowRequest) {
break;
}
}
}
if (anyPluginsResponded) {
return shouldAllowRequest;
}
/*
* Handle all other types of urls (tel:, sms:), and requests to load a url in the main webview.
*/
BOOL shouldAllowNavigation = [self defaultResourcePolicyForURL:url];
if (shouldAllowNavigation) {
return YES;
} else {
[[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLNotification object:url]];
}
return NO;
}
到这就是js调用native的过程了。
另外:
CDVAvailability.h里有个宏
// Enable this to log all exec() calls.
#define CDV_ENABLE_EXEC_LOGGING 0
设置为1,把所有执行exec的log打出来,就看到所有执行js的地方