作为一名写了⑦年代码的程序员,目前我最擅长的领域是IOS的客户端开发,在移动领域的开发时间2年。 ⑦年前,我刚入行的时候,曾经认为自己将会永远做一个LINUX 服务端C++程序员,于是花了大量时间在C++上。现在C++也是我工作所用的主力语言之一,工作之外也会偶尔写点什么娱乐一下。 写了一些年程序后,终于意识到了之前定位的狭隘,于是开始广泛的学习各种技术,各种各样的语言也学了很多,值得庆幸的是,几年折腾下来,我一直也没有对写代码这件事感到厌倦,于是我又认为自己将会永远把开发做下去。 现在,我也觉得开发是一个可以终身做下去的事业,不过除了事业我还想追求更多的东西,从这些年的经历来看,其中贯穿始终的就是在不停的学习,想明白这一点后,给自己的定位也变成了在今后作为一名终身学习者。

一 iOS hybrid App 简单介绍

大家应该多少都知道,iOS 设备上有两种入口,一是通过 App Strore 下载一个个的 App,另一个是用系统浏览器去访问网页。前者我们一般称为原生应用,后者就是传统意义上的网页。两者各有特点,开发一个原生应用,一般是使用 Apple 给我们提供的开发工具和 Cocoa 框架。优势就是可以利用到系统的所有特性,做出很酷的特性而不损失任何的性能,而缺点就是每次 App 提供新功能都必须重新打包 App,提交给 Apple 进行审核,通过以后再上架 App Store,最后用户再升级,平均需要两周的时间。相反,写一个网页则完全没有这个限制,服务器做一次升级,用户通过浏览器再访问,就是最新的了,而写网页的缺点则是受到很大的限制,很多系统特性是无法访问的,而且性能往往不高,以至于很难实现一些很酷的效果。

鉴于原生应用和网页各有优势,所以就衍生出了一种介于两者之间的开发方式--混合应用(hybrid App)。其特点是在原生应用中嵌入一个浏览器组件,然后通过某种方式,让原生代码和网页能够双向通讯,结果就是可以在需要原生功能的时候使用原生功能,而适合放在网页端的部分就放在服务器上。某种程度上利用到了两者的优势。另一个优势就是,由于网页技术在 iOS 和 Android 上是一样的,所以网页的这部分也就天然可以跨平台了。

二 如何实现 hybrid App

实现一个 hybrid App 最简单的方法就是使用 Apache Cordova 开源框架。Cordova 已经帮你做好了所有的网页和原生应用之间的桥接工作,你需要做的就是根据他的文档去写对应的网页代码和原生代码就行了。具体请参考官方网站

可惜的是,我们总有些场景无法使用 Cordava,比如我曾经的一个项目,项目主要是要提供一个 SDK ,SDK 本身要使用 hybrid 的技术。但是 SDK 的用户可能也会用到 Cordova,有些情况下,两者用的 Cordova 为不同版本,正好无法兼容。于是就需要自己去实现 hybrid App 的底层了。

三 iOS hybrid App 的底层实现

1. 原生代码调用网页中的 JavaScript 函数

假设我们的网页中有如下代码

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];
    NSString *protocolPrefix = @"bridge-js://invoke?";
    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在移动端的发展也非常迅速,也许会有更好的未来也说不定。总之掌握这个技术是不会错的。另外由于网页端很可能会成为我们性能瓶颈,所以要时时注意测试相关部分的性能表现,也建议使用一些应用性能监测的第三方服务。这样能够更好的定位产品环境的问题。

iOS和hybird移动端性能的更多相关文章

  1. Lottie在手,动画我有:ios/Android/Web三端复杂帧动画解决方案

      为什么需要Lottie 在相对复杂的移动端应用中,我们可能会需要使用到复杂的帧动画.例如: 刚进入APP时候可能会看到的入场小动画,带来愉悦的视觉享受 许多Icon的互动变化比较复杂多变的时候,研 ...

  2. 移动端性能监控方案Hertz

    移动端性能监控方案Hertz 吴凯 瑞利 富强 徐宏 ·2016-12-19 16:10 性能问题是造成App用户流失的罪魁祸首之一.App的性能问题包括崩溃.网络请求错误或超时.响应速度慢.列表滚动 ...

  3. web移动端性能调优及16ms优化

    本文只是一个索引,收集了网络上大部分关于调试及优化方面的文章,从中挑选了一些比较好的文章分享给大家. 移动端性能不及桌面浏览器性能的10分之1,特别是在android设备良莠不齐的情况下,性能显得尤为 ...

  4. chrome debug 服务端性能

    设置 http header 在 chrome 查看服务端性能 \Yii::$app->getResponse()->headers->set('Server-Timing', 'c ...

  5. Web服务端性能提升实践

    随着互联网的不断发展,日常生活中越来越多的需求通过网络来实现,从衣食住行到金融教育,从口袋到身份,人们无时无刻不依赖着网络,而且越来越多的人通过网络来完成自己的需求. 作为直接面对来自客户请求的Web ...

  6. iOS网络_优化请求性能

    iOS网络_优化请求性能 一,度量网络性能 1,网络带宽 用于描述无线网络性能的最常见度量指标就是带宽.在数字无线通信中,网络带宽可以 描述为两个端点之间的通信通道每秒钟可以传输的位数.现代无线网络所 ...

  7. Unity iOS 项目的一种性能评测方法

    [Unity iOS 项目的一种性能评测方法]

  8. 有谁知道什么工具测试IOS手机上APP的性能软件啊?

    有谁知道什么工具测试IOS手机上APP的性能软件啊?

  9. 解决ios、微信移动端的position: fixed; 支持性不好的问题 && 禁用下拉暴露黑底的功能

    解决ios.微信移动端的position: fixed; 支持性不好的问题 在chrome中的多个部分使用了position: fixed之后,都可以正常的布局,但是放在微信上却出现了不能正常显示的问 ...

随机推荐

  1. The Suspects(简单的并查集)

    Description Severe acute respiratory syndrome (SARS), an atypical pneumonia of unknown aetiology, wa ...

  2. 禁用窗体关闭按钮(使用GetWindowLong修改GWL_STYLE)

    一般我们不想让窗体能够关闭, 首先想到的是在OnCloseQuery事件里设置CanClose := False, 不过在某些情况下这个会和程序关闭窗体的业务逻辑产生冲突 所以写了下面这个函数, 可以 ...

  3. 详解Spring中的CharacterEncodingFilter

    在项目中有很多让人头疼的问题,其中,编码问题位列其一,那么在Spring框架中是如何解决从页面传来的字符串的编码问题的呢?下面我们来看看Spring框架给我们提供过滤器CharacterEncodin ...

  4. ZOJ-1508Intervals(差分约束)

    题意: 有一个序列,题目用n个整数组合 [ai,bi,ci]来描述它,[ai,bi,ci]表示在该序列中处于[ai,bi]这个区间的整数至少有ci个.如果存在这样的序列,请求出满足题目要求的最短的序列 ...

  5. Linux下的定时器:alarm()与setitimer()

    Linux下的定时器有两种,以下分别介绍: 1.alarm 如果不要求很精确的话,用alarm()和signal()就够了 unsigned int alarm(unsigned int second ...

  6. 数据结构(树链剖分):COGS 2109. [NOIP2015] 运输计划

    2109. [NOIP2015] 运输计划 ★★★   输入文件:transport.in   输出文件:transport.out   简单对比时间限制:1 s   内存限制:256 MB [题目描 ...

  7. 部署ASP.Net项目 遇到总是启用目录浏览或者报HTTP 错误 403.14 - Forbidden 的原因

    部署Asp.Net 网站总是报下面的问题 原因: 没有为网站指定默认文档,增加默认文档 1.选中“默认文档” 2.点击右边“打开功能” 点击右边“添加”按钮,把你想作为的默认页面添加就可以了,重启服务 ...

  8. Introduction to Glide, Image Loader Library for Android, recommended by Google

    In the passed Google Developer Summit Thailand, Google introduced us an Image Loader Library for And ...

  9. shell timeout

    写脚本的时候,经常需要用到超时控制.看<shell专家编程>时看到一个好例:修改了一下, 1.超过timeout时间还没执行完,则kill进程,发邮件告警: set-xmailSend() ...

  10. P - The Shortest Path in Nya Graph-hdu4725(双端队列+拆点)

    题意:有N个点和N层..一层有X个点(0<=X<=N).两邻两层间有一条路花费C.还有M条小路在两个点之间.问从第一个点走到第N个点最短路是多少... 可以考虑在每一层增加一个点,这个点到 ...