做为一名写了⑦年代码的程序员,目前我最擅长的领域是IOS的客户端开发,在移动领域的开发时间2年。 ⑦年前,我刚入行的时候,曾经认为本身将会永远作一个LINUX 服务端C++程序员,因而花了大量时间在C++上。如今C++也是我工做所用的主力语言之一,工做以外也会偶尔写点什么娱乐一下。 写了一些年程序后,终于意识到了以前定位的狭隘,因而开始普遍的学习各类技术,各类各样的语言也学了不少,值得庆幸的是,几年折腾下来,我一直也没有对写代码这件事感到厌倦,因而我又认为本身将会永远把开发作下去。 如今,我也以为开发是一个能够终身作下去的事业,不过除了事业我还想追求更多的东西,从这些年的经从来看,其中贯穿始终的就是在不停的学习,想明白这一点后,给本身的定位也变成了在从此做为一名终身学习者。javascript
一 iOS hybrid App 简单介绍html
你们应该多少都知道,iOS 设备上有两种入口,一是经过 App Strore 下载一个个的 App,另外一个是用系统浏览器去访问网页。前者咱们通常称为原生应用,后者就是传统意义上的网页。二者各有特色,开发一个原生应用,通常是使用 Apple 给咱们提供的开发工具和 Cocoa 框架。优点就是能够利用到系统的全部特性,作出很酷的特性而不损失任何的性能,而缺点就是每次 App 提供新功能都必须从新打包 App,提交给 Apple 进行审核,经过之后再上架 App Store,最后用户再升级,平均须要两周的时间。相反,写一个网页则彻底没有这个限制,服务器作一次升级,用户经过浏览器再访问,就是最新的了,而写网页的缺点则是受到很大的限制,不少系统特性是没法访问的,并且性能每每不高,以致于很难实现一些很酷的效果。前端
鉴于原生应用和网页各有优点,因此就衍生出了一种介于二者之间的开发方式--混合应用(hybrid App)。其特色是在原生应用中嵌入一个浏览器组件,而后经过某种方式,让原生代码和网页可以双向通信,结果就是能够在须要原生功能的时候使用原生功能,而适合放在网页端的部分就放在服务器上。某种程度上利用到了二者的优点。另外一个优点就是,因为网页技术在 iOS 和 Android 上是同样的,因此网页的这部分也就自然能够跨平台了。html5
二 如何实现 hybrid Appjava
实现一个 hybrid App 最简单的方法就是使用 Apache Cordova 开源框架。Cordova 已经帮你作好了全部的网页和原生应用之间的桥接工做,你须要作的就是根据他的文档去写对应的网页代码和原生代码就好了。具体请参考官方网站程序员
惋惜的是,咱们总有些场景没法使用 Cordava,好比我曾经的一个项目,项目主要是要提供一个 SDK ,SDK 自己要使用 hybrid 的技术。可是 SDK 的用户可能也会用到 Cordova,有些状况下,二者用的 Cordova 为不一样版本,正好没法兼容。因而就须要本身去实现 hybrid App 的底层了。web
三 iOS hybrid App 的底层实现apache
1. 原生代码调用网页中的 JavaScript 函数json
假设咱们的网页中有以下代码浏览器
1
2
3
4
5
|
[script type=
"text/javascript"
]
function
myFunc() {
return
"Text from web"
}
[/script]
|
原生代码能够用以下方式调用 myFunc()
1
|
NSString * result = [self.webView stringByEvaluatingJavaScriptFromString:@
"myFunc()"
];
|
在这里 result 就等于 Text from web
2. 网页中的 JavaScript 调用系统的原生代码
这一步比上边的要复杂一些,iOS 不像 Android 能够直接给网页中的 JavaScript 函数注入一个原生代码的接口。这里咱们会用一个比较曲折的方式来实现。
假设 Objective-C 的类里有一个方法
1
2
|
-(void)nativeFunction:(NSString*)args {
}
|
JavaScript 里咱们用下边的方法来最终调用到上边这个方法
1
|
window.JSBridge.callFunction(
"callNativeFunction"
,
"some data"
);
|
在咱们的页面里,是没有 JSBridge.callFunction 存在的,这一步咱们要在原生代码端注入。
在 webView 的 delegate 的 - (void)webViewDidFinishLoad:(UIWebView *)webView 里咱们用下边的方式注入 JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
NSString *js = @"(
function
() {\
window.JSBridge = {};\
window.JSBridge.callFunction =
function
(functionName, args){\
var
url = \"bridge-js:
//invoke?\";\
var
callInfo = {};\
callInfo.functionname = functionName;\
if
(args)\
{\
callInfo.args = args;\
}\
url += JSON.stringify(callInfo);\
var
rootElm = document.documentElement;\
var
iFrame = document.createElement(\"IFRAME\");\
iFrame.setAttribute(\"src\",url);\
rootElm.appendChild(iFrame);\
iFrame.parentNode.removeChild(iFrame);\
};\
return
true
;\
})();";
[webView stringByEvaluatingJavaScriptFromString:js];
|
简单解释一下,首先咱们在 window 里建立一个叫 JSBridge 的对象,而后在里边定义一个方法 callFunction,这个方法的做用是把两个参数打包为 JSON 字符串,而后附带到咱们自定义的 URL bridge-js://invoke? 后边,最后用 IFRAME 的方式来加载这个 URL
这么作的缘由是,当加载 IFRAME 的时候,就会调用 webView 的 delegate 的 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType 方法,其中 request 就是咱们刚才自定义的那个 URL,在这个方法里咱们作以下处理
1
2
3
|
NSURL *url = [request URL];
NSString *urlStr = url.absoluteString;
return
[self processURL:urlStr];
|
processURL 函数以下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
-(BOOL) processURL:(NSString *) url
{
NSString *urlStr = [NSString stringWithString:url];
if
([[urlStr lowercaseString] hasPrefix:protocolPrefix])
{
urlStr = [urlStr substringFromIndex:protocolPrefix.length];
urlStr = [urlStr stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSError *jsonError;
NSDictionary *callInfo = [NSJSONSerialization
JSONObjectWithData:[urlStr dataUsingEncoding:NSUTF8StringEncoding]
options:kNilOptions
error:&jsonError];
NSString *functionName = [callInfo objectForKey:@
"functionname"
];
NSString * args = [callInfo objectForKey:@
"args"
];
if
([functionName isEqualToString:@
"callNativeFunction"
]) {
[self nativeFunction:args];
}
return
NO;
}
return
YES;
}
|
从 bridge-js://invoke? 这个自定义的 URL 里边把附带在后边 JSON 字符串解析出来,而后判断 functionname key 的值若是是 callNativeFunction 那么就去调用原生方法 nativeFunction, 若是须要实现更多的方法调用,只要添加这个映射关系就好了。
至此,JavaScript 和 Objective-C 代码的双向调用就都实现了。
四 性能监测
Hybrid 和原生应用之间的争论一直以来都很多,其核心问题其实就是如何平衡开发成本和用户体验之间的关系。Hybrid的开发成本通常来讲要低于原生应用,而后其体验老是要差一些。为了让 Hybrid 的用户体验能更可能的接近原生应用,性能监测就显的更为重要了。
影响 App 使用体验通常来说有两个主要方面
第一方面是 UI 的响应速度,UI 的流畅与否给用户的体验是很是不同的。对这方面的性能监测,通常的作法就是在主要的交互函数里打上时间戳,而对于系统的 View,也能够采用 Method Swizzle 的方法对全部的系统函数的调用时间进行统计。
二是网络,而因为如今的大部分 App 都多少有了网络请求,因此网络的请求速度也会很大程度上影响用户体验。网络问题在 Hybrid App 就体现的更明显。Hybrid App 老是会去加载服务器端的页面,在页面加载出来以前,极可能整个手机屏幕是空白的,若是空白时间太长,将是一个很糟糕的事情,因此实时的监测请求网页的时间,以及页面的加载速度就很是有必要了。针对 webView,建议在它的 delegate 的几个方法里打上时间戳,以此来统计页面请求和加载的时间。
总之实现起来,并非一个很是复杂的工做。然而性能监测的工做,实现只是其中的一个方面,因为用户的使用习惯,实际的网络环境各类问题,性能监测并非在开发阶段监测一下就算完了的,通常来讲,老是得把监测工做部署到最终用户的手机上去的,若是是一个用户量不小的 App,那么如何把收集到的大量数据很好的统计显示出来,这彻底是另外一回事了,把这事作好,要牵扯到不少的数据组织,前端展现的工做,实际的实施绝对不是个简单的工做。
所幸,如今已经有不少公司帮咱们完成了这个工做,好比 New Relic,App dynamics,Compuware,听云等。此次咱们就以听云为例,看看他们是怎么来作性能监测这件事的。
五 听云探针(iOS App版)的使用
听云对 App 的性能监测使用起来仍是比较简单的,简单步骤以下
申请完听云的帐户后,在添加 App 的地方填写相关信息
以后就会获得一个惟一的 App Key
而后下载听云的 iOS SDK 的 Framework,拷贝到项目中,注意添加如下 4 个额外的系统库
CoreTelephony.framework
Security.framework
SystemConfiguration.framework
libz.dylib
而后在 App 的 pch 文件中包含听云 App 探针的头文件
1
|
#import
|
最后将 main.m 中加入
1
|
[NBSAppAgent startWithAppID:App_Key];
|
代码通常为下边的样子
1
2
3
4
5
6
|
int main(int argc, char * argv[]) {
@autoreleasepool {
[NBSAppAgent startWithAppID:App_Key];
return
UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
|
这样整个集成工做就完成了。启动 App,若是在 Log 日志中有以下内容显示就表示代码集成成功
1
2
3
|
NBSAppAgent 2.2.2.1
---->start!
Success to connect to NBSSERVER
|
六 听云监测数据观察
1. 汇总数据
登陆到听云的后台管理页面,首先咱们能够看到汇总的监测数据,图表的效果仍是不错的,鼠标放到每一个数据点上会显示详细的数据。
整体看来,分为两大类,一类是应用交互性能,这类主要是监测 UI 响应状况,会给出 view 加载,以及 layout 的时间汇总。若是发现某一项参数出现异常,那也许就是须要重构 UI 的信号了。另外一类是网络性能,包含网络请求的响应时间等。
2. Web View
重要的东西最早讲,这个部分是听云目前最有特点的部分(好像是首家这么作的,目前还没在其余的相似服务里看到这个功能)。一般咱们进行网络性能监测的时候,给出的是整个网络请求的状况,这在浏览器里边来讲,整个网络请求其实也就是页面的请求,二者没有区别。而到了 App 里,一样是 http 请求,有多是来自 web service 的调用,也多是来自 web view 加载页面。然后者正好是咱们讲的 Hybrid App 的主要实现方式。听云的这个条目就是彻底只给出 web view 所进行的请求状况,换句话说,这是咱们用来监测 Hybrid App 网络性能的最好数据。
(1)HTTP请求
这里有全部 web view 所加载的页面的汇总数据。
(2)页面加载
听云除了给出网络性能的数据,这里还很贴心的给出了页面加载的汇总数据,要知道如今的网页是有可能很是复杂,包含不少页面元素的,在桌面端问题也许不明显,但在移动端,太复杂的效果也许会大大的拖慢加载速度,影响用户体验。根据这里给出的页面加载数据,就能够有针对性的去优化网页了。
3. 网络
这个条目主要是 App 的全部网络请求的数据。
(1)拓补图
这主要是一个分类汇总的数据,能够分别查看是本身的 App 因此及第三方服务所发送的网络请求的汇总数据。
(2)HTTP请求
顾名思义,这里是全部 http 请求的详细数据,分别显示了响应最慢的主机,以及吞吐量最高的主机。
(3)地域
这个条目比较有意思,用颜色的方式标示出了世界各国的平均响应时间,点击对应的国家还能够继续进入到下一级,最后能够进入到详细的网络数据分析页面。
(4)组合分析
这个页面在中国的网络环境下是很是有用的,它能够根据运营商,地域,接入方式来给出汇总数据。要知道中国的网络环境很是复杂,不一样运营商之间,甚至同一运营商在不一样的地区网络互通状况会相差很是大。有了这里的数据,就能够有针对性的去部署服务器,优化网络体验。
4. 交互
进入交互分析具体项
在这里咱们能够详细的看到 ViewController 以及 View 的每个系统函数的调用时间,经过这个数据就能够很是好的分析是哪一个一个 ViewController 出了问题,对应的去重构就能够了。
交互分析下边的几项是经过必定的条件来看 UI 交互的具体数据,能够经过版本进行过滤,这样就能够方便的过滤掉已经把问题修改掉了的版本。还能够经过操做系统(iOS/Android)和设备来看各自的交互响应的数据。
5. 其余
除了性能分析,听云的数据里也有常见的崩溃数据,活跃数以及事件监测等。这里就不详细展开了
七 总结
Hybrid App 在某些特定场景是很是有用的,然而也确实有它的局限性,特别是对交互要求很高的地方,使用它是不太合适,毕竟它仍是基于网页技术。不过html5在移动端的发展也很是迅速,也许会有更好的将来也说不定。总之掌握这个技术是不会错的。另外因为网页端极可能会成为咱们性能瓶颈,因此要时时注意测试相关部分的性能表现,也建议使用一些应用性能监测的第三方服务。这样可以更好的定位产品环境的问题。