突然有一天,领导的Android手机中的程序运行停止了,这告诉了CTO,CTO立刻通知Android负责人,查询原因,并且询问当前的崩溃率是多少,得知3%左右,Android优化完之后,CTO又问iOS的负责人,iOS现在线上的崩溃率多少啊,‘2%左右’,‘这有点高啊,以后要降到0.2%,iOS要立刻发一个版本,降低崩溃率’,然后我们就开始分析Bugly上面的崩溃信息了。
目录
- 已解决的bug
- 暂时无解的bug
- 要添加保护的bug
- 总结
- 崩溃产生的原因
- 崩溃率的现状
- 我们离优秀还有多远
已解决的bug
- UIWebView加载网页中进入后台崩溃
网页中使用了OpenGL ES
绘制,根据Apple
的要求,这个不能在后台绘制的(An OpenGL ES application will be terminated if it attempts to execute OpenGL ES commands in the background.
),解决方案:
- 就是网页提供绘制开始和停止的接口供原生在进入后台和切换到前台时使用,但是我们使用的是其他厂家提供的链接,没有此接口。
- 在前后台切换时,停止和重新加载
UIWebView
,调用stopLoading
但是还是会在后台绘制,并不能停止OpenGL ES
的绘制,所以排除此方法。 - 改用WKWebView,因为这是
Apple
的bug,所以使用最新的API
去替换,但是其他厂家提供的链接不支持WKWebView
。 - 在Apple Forum中有开发者指出这是
Apple
的bug,并提供了解决方法,在进入后台时调用webView:enableGL
方法设置为NO
,进入前天后,调用webView:enableGL
方法设置为YES
。
typedef void (*CallFuc)(id, SEL, BOOL);
typedef BOOL (*GetFuc)(id, SEL);
-(BOOL)webView:(UIWebView*)view enableGL:(BOOL)bEnable
{
BOOL bRet = NO;
do
{
Ivar internalVar = class_getInstanceVariable([view class], "_internal");
if (!internalVar)
{
NSLog(@"enable GL _internal invalid!");
break;
}
UIWebViewInternal* internalObj = object_getIvar(view, internalVar);
Ivar browserVar = class_getInstanceVariable(object_getClass(internalObj), "browserView");
if (!browserVar)
{
NSLog(@"enable GL browserView invalid!");
break;
}
id webbrowser = object_getIvar(internalObj, browserVar);
Ivar webViewVar = class_getInstanceVariable(object_getClass(webbrowser), "_webView");
if (!webViewVar)
{
NSLog(@"enable GL _webView invalid!");
break;
}
id webView = object_getIvar(webbrowser, webViewVar);
if (!webView)
{
NSLog(@"enable GL webView obj nil!");
}
if(object_getClass(webView) != NSClassFromString(@"WebView"))
{
NSLog(@"enable GL webView not WebView!");
break;
}
SEL selector = NSSelectorFromString(@"_setWebGLEnabled:");
IMP impSet = [webView methodForSelector:selector];
CallFuc func = (CallFuc)impSet;
func(webView, selector, bEnable);
SEL selectorGet = NSSelectorFromString(@"_webGLEnabled");
IMP impGet = [webView methodForSelector:selectorGet];
GetFuc funcGet = (GetFuc)impGet;
BOOL val = funcGet(webView, selector);
bRet = (val == bEnable);
}while(NO);
return bRet;
}
- 上一个方法中能解决一定概率的崩溃问题,但是不能从根本上解决,还是存在一定概率的崩溃,所以改用进入后台时,加载空白页面,移除
UIWebView
,清除delegate
,销毁UIWebView
。为什么还需要销毁呢?
因为只加载空白页的话,回到前台在加载之前的URL,那么加载出来之后点击返回的时候,会多了一层空白页,而不是返回上一个视图
- 我的评论中方法
currentCalendar
在iOS 9
上崩溃的问题
崩溃是iOS 9
上的bug,替换为最新的方法[NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian]
,最新的方法支持到最低支持iOS 8
。
暂时无解的bug
之所以称为无解的bug是因为:
- 没有测试设备支持我们测试和调试,许多问题出现在
9.3.5
和8.1.3
中,但是没有设备测试。 - 程序启动直接崩溃,根据提供的信息无法判断是什么导致的,有可能跟极光推送的SDK有关系,但是咨询极光推送的技术支持说肯定不是他们SDK的问题,应该是程序中可能移动文件夹导致的崩溃。
- 真的一无所有的bug,对于此类bug,真的是欲哭无泪,任何信息没有无法根据已有的信息判断出来。
要添加保护的bug
有些bug需要添加保护,因为Objective-C给了用户太高的授权,导致产生各种不应该产生的bug
- 比如数组越界
- 字典里面添加nil。
- 对于KVO、KVC也需要添加保护,防止不配对的情况。
- 不同类型比较,比如
NSString
的方法isEqualToString
经常和NSNumber
类型的比较
虽然项目中有对字典或数组的保护,但是需要开发者主动去调用,比如safeObjectAtIndex
数组,有时候就容易忘记,所以需要添加保护,不论用户是否调用array[0]
,还是[array objectAtIndex:0]
,都可以自动去处理。
总结
崩溃产生的原因
由于网络环境、设备机型、开发环境等多样元素的存在,应用性能问题组合起来查过2亿种可能,连接超时、闪退、卡顿、崩溃、网络劫持、交互性差、CPU使用率问题、内存泄漏等对移动互联网的性能产生重大影响。
其中崩溃是最严重的问题,在运行的过程中强行关闭,打断用户正在进行的操作体验。
应用崩溃带来的后果:
- 关键业务终端
- 用户留存率下降
- 口碑变差
- 生命周期价值下降
现在崩溃率的现状
iOS
应用崩溃率远高于Android
,基本上是Android
应用平均崩溃率的5倍,这让人大跌眼镜,颠覆了我的认知观。Android
系统中,崩溃率最高的是航空行业,崩溃率为2.05%。iOS
系统中,O2O
、游戏行业崩溃率均在4%以上,其中游戏行业崩溃率高达5.83%。
我们离优秀还有多远
统计显示,当崩溃率超过8‰的时候,活跃用户明显下降态势。由此看来,移动开发者必须重视移动应用对最新操作系统版本的支持以及旧版本的兼容问题。
我们现在的崩溃率高达20‰,牢牢站住了
严重隐患
这一层,领导们给我们的要求是下降一个量级到达2‰,进入优秀
层级。
参考链接
How to fix OpenGL ES application crashes when moving to the background
《2016中国移动应用性能管理白皮书》重磅发布