热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

phonegap原理分析-ios版

phonegap的主要亮点就是它能通过js与native进行通信,一个基本的phonegap插件的执行过程一般是这样:js接口-->native代码-->js回调

phonegap的主要亮点就是它能通过js与native进行通信,一个基本的phonegap插件的执行过程一般是这样:
js接口 --> native代码 --> js回调
所以要明白phonegap的原理,就要弄明白两件事:(1)native如何调用js,(2)js如何调用native

1. native调用js


native调用js非常简洁方便,只需要
[webView stringByEvaluatingJavascriptFromString:@"alert('hello world!')"];
并且该方法是同步的。所以phonegap解决的主要是js调用native的问题。

2. js调用native


phonegap原理的主要难点就是js如何调用native,下面我以项目中用到的一个插件为例,通过设置断点的方法一步步理解:
(1).页面上调用插件的js代码

navigator.fixedInput.showAndFocus(function(content){
alert(content);
}, 'hello world', '发送');

第一个参数为回调函数

(2).通过断点我们可以知道,js后面执行了这个方法

exec(sendCallback, null, "FixedInput", "showAndFocus", [defaultVal, btnText]);

跳入exec函数,继续执行
这里写图片描述

(3).断点执行到了cordova.js的iOSExec函数,该函数主要是获取调用参数,进队列,选择调用模式。重要的是这段代码:

switch (bridgeMode) {
case jsToNativeModes.XHR_NO_PAYLOAD:
case jsToNativeModes.XHR_WITH_PAYLOAD:
case jsToNativeModes.XHR_OPTIONAL_PAYLOAD:
pokeNativeViaXhr();
break;
default: // iframe-based.
pokeNativeViaIframe();
}

从上面的代码,我们知道cordova会采用以下2种方式的一种,来与ios native交互
通过iframe
cordova.exec往当前的html中插入一个不可见的iframe,从而向UIWebView请求加载一个特殊的URL,这个URL里包含了要调用的native plugin的类名,方法名,参数,回调函数等信息。接下来,由于被请求加载URL,于是UIWebViewDelegate的这个方法被调用:

- (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType

这样就进入了native侧代码,通过request参数就拿到了js端传过来的信息,然后调用到native plugin
通过XHR
cordova.exec里直接发起一个XHR请求,被native侧的NSURLProtocol拦截,于是调用native的这个方法:

+ (BOOL)canInitWithRequest:(NSURLRequest*)theRequest

也进入了native侧代码,然后以同样的方式调用到native plugin

在2种方式中,cordova会优先选择XHR方式,只有当XHR方式不可用时,才会使用iframe的方式。

native代码


下面以iframe方式为例,看native的执行过程
我们知道js会创建一个iframe并发送gap://ready这个指令来告诉native开始执行操作。首先是CDVViewController的shouldStartLoadWithRequest方法

CDVViewController

- (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
{
NSURL* url = [request URL];
/*
* 判断url的协议以"gap"开头
* 执行在js端调用cordova.exec()的command队列
* 注:这里的command表示js调用native
*/

if ([[url scheme] isEqualToString:@"gap"]) {
/**
* 从js端拉取command,即存储在js端commandQueue数组中的数据
*/

[_commandQueue fetchCommandsFromJs];
[_commandQueue executePending];
return NO;
}
...
}

看下CDVCommandQueue中的fetchCommandsFromJs方法与executePending方法中做的事。

CDVCommandQueue

- (void)fetchCommandsFromJs 
{
// 获取js端存储的command,并在native暂存
NSString* queuedCommandsJSON = [_viewController.webView stringByEvaluatingJavascriptFromString:
@"cordova.require('cordova/exec').nativeFetchMessages()"];
[self enqueueCommandBatch:queuedCommandsJSON];
}

etchCommandsFromJs方法比较简单,就是从js端队列取出一条command,并转成cordova格式的对象。

executePending方法稍微复杂些,因为js是单线程的,而iOS是典型的多线程,所以executePending方法做的工作主要是让command一个一个执行,防止线程问题。

executePending方法其实与之后的execute方法紧密相连,这里一起列出,只保留关键代码:

- (void)executePending 
{
...
//_queue即command队列,依次执行
while ([_queue count] > 0) {
...
//取出从js中获取的command字符串,解析为native端的CDVInvokedUrlCommand类
CDVInvokedUrlCommand* command = [CDVInvokedUrlCommand commandFromJson:jsonEntry];
...
//执行command
[self execute:command])
...
}
}

- (BOOL)execute:(CDVInvokedUrlCommand*)command
{
...
BOOL retVal = YES;
//获取plugin对应的实例
CDVPlugin* obj = [_viewController.commandDelegate getCommandInstance:command.className];
//调用plugin实例的方法名
NSString* methodName = [NSString stringWithFormat:@"%@:", command.methodName];
SEL normalSelector = NSSelectorFromString(methodName);
if ([obj respondsToSelector:normalSelector]) {
//消息发送,执行plugin实例对应的方法,并传递参数
objc_msgSend(obj, normalSelector, command);
} else {
// There's no method to call, so throw an error.
NSLog(@"ERROR: Method '
%@' not defined in Plugin '%@'", methodName, command.className);
retVal = NO;
}
...
return retVal;
}

可以看到js调用native plugin最终执行的是objc_msgSend(obj, normalSelector, command);这块代码,设置断点看下参数

这里写图片描述

可以看到里面的参数都是都是与插件有关的,代码接着会执行具体的插件代码,路由过程我们就不去看了,代码最终执行了FixedInput.m里的showAndFocus方法

这里写图片描述

最后的运行效果:

这里写图片描述

还有一个问题,就是我们设置了回调函数,那么我们点击发送的时候,怎么将内容通过回调函数传给js呢?
看这段代码:

-(void) sendContent {
NSString *inputText = textField.text;
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString : inputText];
[pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
[self.commandDelegate sendPluginResult:pluginResult callbackId:showCommand.callbackId];
[textField resignFirstResponder];
textField.text = @"";
}

我们给按钮绑定了这个函数,我们可以看到,这个方法通过下面的代码调用回调函数

[self.commandDelegate sendPluginResult:pluginResult callbackId:showCommand.callbackId];

继续跟踪代码:

- (void)sendPluginResult:(CDVPluginResult*)result callbackId:(NSString*)callbackId
{
CDV_EXEC_LOG(@"Exec(%@): Sending result. Status=%@", callbackId, result.status);
// This occurs when there is are no win/fail callbacks for the call.
if ([@"INVALID" isEqualToString : callbackId]) {
return;
}
// This occurs when the callback id is malformed.
if (![self isValidCallbackId:callbackId]) {
NSLog(@"Invalid callback id received by sendPluginResult");
return;
}
int status = [result.status intValue];
BOOL keepCallback = [result.keepCallback boolValue];
NSString* argumentsAsJSON = [result argumentsAsJSON];

NSString* js = [NSString stringWithFormat:@"cordova.require('cordova/exec').nativeCallback('%@',%d,%@,%d)", callbackId, status, argumentsAsJSON, keepCallback];

[self evalJsHelper:js];
}

这段代码整合参数拼凑出一句js代码,最后通过evalJsHelper:js执行,设置断点我们可以知道最后js变量的内容为:

cordova.require('cordova/exec').nativeCallback('FixedInput158086966',1,\n  \"hello world\"\n,1)

所以最后native会调用cordova.require(‘cordova/exec’).nativeCallback()执行我们设置的回调函数。

phonegap ios版的原理分析就到此,有理解不到位的地方欢迎指出,一起探讨。^_^

参考文章:
http://itindex.net/detail/50630-cordova-ios-native
http://www.cocoachina.com/industry/20140623/8919.html


推荐阅读
  • 前一篇文章中,我们大致的讲述了一下JavaScriptCore这个库在iOS开发中的应用。在文中最后的阶段,我们提到了简单的来说,在最开始的UIWebView时,原生跟JS之间的交互一般是两种方式:当然这个其实也就是 ... [详细]
  • 本文介绍了Java后台Jsonp处理方法及其应用场景。首先解释了Jsonp是一个非官方的协议,它允许在服务器端通过Script tags返回至客户端,并通过javascript callback的形式实现跨域访问。然后介绍了JSON系统开发方法,它是一种面向数据结构的分析和设计方法,以活动为中心,将一连串的活动顺序组合成一个完整的工作进程。接着给出了一个客户端示例代码,使用了jQuery的ajax方法请求一个Jsonp数据。 ... [详细]
  • 本文总结了在编写JS代码时,不同浏览器间的兼容性差异,并提供了相应的解决方法。其中包括阻止默认事件的代码示例和猎取兄弟节点的函数。这些方法可以帮助开发者在不同浏览器上实现一致的功能。 ... [详细]
  • 本文是一篇翻译文章,介绍了async/await的用法和特点。async关键字被放置在函数前面,意味着该函数总是返回一个promise。文章还提到了可以显式返回一个promise的方法。该特性使得async/await更易于理解和使用。本文还提到了一些可能的错误,并希望读者能够指正。 ... [详细]
  • javascript二叉树基本功能实现
    都是常用的功能。删除是最复杂的。。test ... [详细]
  • vue 地图使用navigator_weex踩坑之旅第五弹 ~ 使用navigator内置模块实现导航
    目前,我个人认为在weex中实现页面跳转的方式有两种,一种是通过weex提供的navigator模块,一种是通过vue-router之类的 ... [详细]
  • 在使用react-native-scrollable-tab-view这个组件。每一个tab嵌套了一个webview地图。安卓debug下每一个tab都可以都正常显示,打包apk之后有一点问题进 ... [详细]
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • 生成对抗式网络GAN及其衍生CGAN、DCGAN、WGAN、LSGAN、BEGAN介绍
    一、GAN原理介绍学习GAN的第一篇论文当然由是IanGoodfellow于2014年发表的GenerativeAdversarialNetworks(论文下载链接arxiv:[h ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • jQuery实现简单的动画效果及用法详解
    本文详细介绍了使用jQuery实现简单动画效果的方法,包括显示/隐藏、向上收缩/向下展开、淡入/淡出、自定义动画等。同时提供了具体的用法示例,并解释了参数的含义和使用技巧。通过本文的学习,读者可以掌握如何使用jQuery实现各种动画效果,为网页增添生动和互动性。 ... [详细]
  • Jquery 跨域问题
    为什么80%的码农都做不了架构师?JQuery1.2后getJSON方法支持跨域读取json数据,原理是利用一个叫做jsonp的概念。当然 ... [详细]
  • 本文概述了JNI的原理以及常用方法。JNI提供了一种Java字节码调用C/C++的解决方案,但引用类型不能直接在Native层使用,需要进行类型转化。多维数组(包括二维数组)都是引用类型,需要使用jobjectArray类型来存取其值。此外,由于Java支持函数重载,根据函数名无法找到对应的JNI函数,因此介绍了JNI函数签名信息的解决方案。 ... [详细]
  • 由于同源策略的限制,满足同源的脚本才可以获取资源。虽然这样有助于保障网络安全,但另一方面也限制了资源的使用。那么如何实现跨域呢,以下是实现跨域的一些方法。 ... [详细]
  • remote 移除_remote模块的使用(四)
    微信公众号:[猫十二的日常],欢迎留言和指出问题a在electron中的一些模块,它是区分进程的,有些模块只能是主进程可以使 ... [详细]
author-avatar
mobiledu2502897157
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有