今天参加了Adobe和CSDN组织的一个关于PhoneGap的开发讲座 ,而PhoneGap在iOS设备上的实现就是经过UIWebView控件来展现html内容,而且与native代码进行交互的。javascript
正好咱们在作有道云笔记的iPad版,由于咱们也是使用UIWebView来展现笔记内容,因此也须要作js与native代码相互调用的事情。因此在这儿顺便总结一下UIWebView在使用上的细节,以及谈谈我对PhoneGap的见解。html
首先咱们须要让UIWebView加载本地HTML。使用以下代码完成:前端
NSString * path = [[NSBundle mainBundle] bundlePath]; NSURL * baseURL = [NSURL fileURLWithPath:path]; NSString * htmlFile = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"]; NSString * htmlString = [NSString stringWithContentsOfFile:htmlFile encoding:(NSUTF8StringEncoding) error:nil]; [self.webView loadHTMLString:htmlString baseURL:baseURL];
如下是PhoneGap相关调用的示例代码:java
// Objective-C语言 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { NSURL * url = [request URL]; if ([[url scheme] isEqualToString:@"gap"]) { // 在这里作js调native的事情 // .... // 作完以后用以下方法调回js [webView stringByEvaluatingJavaScriptFromString:@"alert('done')"]; return NO; } return YES; }
具体让js通知native的方法是让js发起一次特殊的网络请求。这里,咱们和PhoneGap都是使用加载一个隐藏的iframe来实现的,经过将iframe的src指定为一个特殊的URL,实如今delegate方法中截获此次请求。node
如下是PhoneGap相关调用的示例代码:android
// Javascript语言 // 通知iPhone UIWebView 加载url对应的资源 // url的格式为: gap:something function loadURL(url) { var iFrame; iFrame = document.createElement("iframe"); iFrame.setAttribute("src", url); iFrame.setAttribute("style", "display:none;"); iFrame.setAttribute("height", "0px"); iFrame.setAttribute("width", "0px"); iFrame.setAttribute("frameborder", "0"); document.body.appendChild(iFrame); // 发起请求后这个iFrame就没用了,因此把它从dom上移除掉 iFrame.parentNode.removeChild(iFrame); iFrame = null; }
在这里,可能有些人说,经过改document.location也能够达到相同的效果。关于这个,我和zyc专门试过,通常状况下,改document.location是能够,可是改document.location有一个很严重的问题,就是若是咱们连续2个js调native,连续2次改document.location的话,在native的delegate方法中,只能截获后面那次请求,前一次请求因为很快被替换掉,因此被忽略掉了。git
我也专门去Github上查找相关的开源代码,它们都是用过iframe来实现调用的,例如这个:https://github.com/marcuswestin/WebViewJavascriptBridgegithub
关于这个,我也作了一个Demo来简单示例,地址以下:https://github.com/tangqiaoboy/UIWebViewSampleweb
以上的示例代码为了讲清楚机制,因此只是示例了最简单的相互调用。但实际上js和native相互调用时,经常须要传递参数。编程
例如,有道云笔记iPad版用UIWebView显示笔记的内容,当用户点击了笔记中的附件,这个时候,js须要通知native到后台下载这个笔记附件,同时通知js当前的下载进度。对于这个需求,js层得到用户点击事件后,就须要把当前点击的附件的ID传递给native,这样native才能知道下载哪一个附件。
参数传递最简单的方式是将参数做为url的一部分,放到iFrame的src里面。这样UIWebView经过截取分析url后面的内容便可得到参数。可是这样的问题是,该方法只能传递简单的参数信息,若是参数是一个很复杂的对象,那么这个url的编解码将会很复杂。对此,咱们的有道云笔记和PhoneGap采用了不一样的技术方案。
相比之下,应该说PhoneGap的方案更加全面,适用于多种场景。而咱们的方案简洁高效,知足了咱们本身产品的需求。
由于iOS SDK没有天生支持js和native相互调用,你们的技术方案都是本身实现的一套调用机制,因此这里面有同步异步的问题。细心的同窗就能发现,js调用native是经过插入一个iframe,这个iframe插入完了就完了,执行的结果须要native另外用stringByEvaluatingJavaScriptFromString方法通知js,因此这是一个异步的调用。
而stringByEvaluatingJavaScriptFromString方法自己会直接返回一个NSString类型的执行结果,因此这显然是一个同步调用。
因此js call native是异步,native call js是同步。在处理一些逻辑的时候,不可避免须要考虑这个特色。
这里顺便说一个android,其实在android开发中,js调native是同步的,可是PhoneGap为了将本身作成一个跨平台的框架,因此在android的js call native的native端,用 new Thread新建了一个执行线程,这样把android的js call native也变成了异步调用。
咱们在开发中发现,当在native层调用stringByEvaluatingJavaScriptFromString方法时,可能因为javascript是单线程的缘由,会阻塞原有js代码的执行。这里咱们的解决办法是在js端用defer将iframe的插入延后执行。
UIWebView的stringByEvaluatingJavaScriptFromString方法必须是主线程中执行,而主线程的执行时间过长就会block UI的更新。因此咱们应该尽可能让stringByEvaluatingJavaScriptFromString方法执行的时间短。
有道云笔记在保存的时候,须要调用js得到笔记的完整html内容,这个时候若是笔记内容很复杂,就会执行很长一段时间,而由于这个操做必须是主线程执行,因此咱们显示“正在保存”的UIAlertView彻底没法正常显示,整个UI界面彻底卡住了。在新的编辑器里,咱们更新了得到html内容的代码,才将这个问题解决。
作iOS开发的都知道,当咱们须要键盘显示在某个控件上时,能够调用[obj becomeFirstResponder]方法来让键盘出来,而且光标输入焦点出如今该控件上。
可是这个方法对于UIWebView并不可用。也就是说,咱们没法经过程序控制让光标输入焦点出如今UIWebView上。 关于这个问题,我在stackoverflow上专门问了一下,仍是没有获得很好的解决办法。
commonJS是一个模块块加载的规范。而AMD是该规范的一个草案,CommonJS AMD规范描述了模块化的定义,依赖关系,引用关系以及加载机制,其规范原文在这里 。它被requireJS,NodeJs,Dojo,jQuery等开源框架普遍使用。这里还有一篇不错的中文介绍文章。
AMD规范须要用目录层级看成包层次,这一点就象java同样。以前我觉得iOS打包后的ipa资源文件中不能有资源目录层级关系,今天在会上问了一下,原来是我本身弄错了。若是须要将目录层级带入ipa资源文件中,只须要将该目录拖入工程中,而后选择“Create groups for any added folders”。以下图所示,这样目录层级可以打包到ipa文件中。
在iOS设备中调试javascript是一件至关苦逼的事情,拿pw的话来讲:“一会儿回到了ie6时代”。固然,业界也有一些调试工具能够用的。
咱们在开发时主要采用的是weinre这个框架。用这个框架,能够作一些基本的调试工做,可是它如今功能尚未象pc上的js调试器那么强大,例如它不能下断点,另外若是有js执行错误,它也没法正确的将错误信息报出。它还有一些bug,例如在mac机下,若是你同时链接了有线网和无线网,那么weinre将没法正确地链接到调试页面。
但终究,它是如今业界现存的惟一相对可用的调试工具了。本次的PhoneGap讲座的第一位演讲者董龙飞有一篇博客很好地介绍了weinre的使用,地址是这里,推荐感兴趣的同窗看看。即便不用PhoneGap,weinre也能给你在移动设备上设计网页带来方便。
(2013年10月22日更新):关于调试这一起,从WWDC2012开始,苹果已经支持用safari来链接iPhone模拟器里面的UIWebView进行调试了,因此调试上已经方便了不少。详细的教程能够查看: WWDC2012 Session 600《Debuging UIWebViews and Websites on iOS》
今天的大会上,2位演讲者把PhoneGap吹得至关牛。可是其实真正用过的人才能知道,PhoneGap仍是有至关多的问题的。至少我知道在网易就有一个使用PhoneGap而失败的项目,因此我认为PhoneGap仍是有它至关大的局限性的。
我认为PhoneGap有如下3大问题:
首先,PhoneGap的编程语言实际上是javascript,这对于非前端工做者来讲,其实学习起来和学习原生的objective-C或Java编程语言难度差很少,并且因为历史缘由,javascript语言自己的问题比其它语言都多。要想精通javascript,至关不易。
而后,PhoneGap的目标是方便地建立跨平台的应用。可是其实苹果和google都发布了本身的人机交互指南。有些状况下,苹果的程序和android程序有着不一样的交互原则的。象有道云笔记的iPhone版和android版,就有着彻底不一样的界面和交互。使用PhoneGap就意味着你的程序在UI和交互上,既不象原生的iphone程序,又不象原生的android程序。
最后,性能问题。Javascript终究没法和原生的程序比运行效率,这一点在当你要作一些动画效果的时候,就能显现得很明显。
固然,PhoneGap的优点也很明显,若是你是作图书类,查询类,小工具类应用的话,这些应用UI交互不复杂,也不占用很高的cpu资源,PhoneGap将很好地发挥出它的优点。对于这类应用:
你只须要编写一次,则能够同时完成iOS, android, windows phone等版本的开发。
若是改动不大,只是内容升级,那它升级时只须要更新相应的js文件,而不须要提交审核,而通常正常提交苹果的app store审核的话,经常须要一周时间。
因此PhoneGap不是万能的,但也不是没有用,它有它擅长的领域,一切都看你是否合理地使用它。
最后,推荐PhoneGap中国网站 ,在这里,你能够找到为数很少的中文资料。
如今前端工程师至关牛逼啊。前端工程师不但能够写前端网页,还能够用Flex写桌面端程序,能够用nodejs写server端程序,能够用PhoneGap写移动端程序,这一切,都是基于javascript语言的,还有最新出的windows 8,原生支持用js来写Metro程序,世界已经没法阻止前端工程师了。