如何将APP崩溃率降低到万分之一以下

当然崩溃率和日活是有关系的,我只能说我的APP肯定不是只有几万日活的APP。程序的稳定性不用我多说,其重要性是不言而喻的。如果APP动不动就崩溃,那就不用说什么交互什么用户体验了,用户的第一反应就是直接把APP删掉或者找替代你的APP。

如何降低崩溃率呢,先分一下一下崩溃的原因:

内存管理问题;
容错处理不完善;
webview与其他崩溃。
一、内存管理问题;

首先内存问题,我们不得不回顾以下历史,在很久很久以前的蛮荒时代,这个时代里,手机内存最小的只有128Mb,一个APP可用的内存更少,还要手动管理内存,程序猿们苦不堪言。

这个时代程序猿的内功心法是:谁创建,谁是释放;需要时申请,不需要时释放。但是实际写代码时,我们小心翼翼,也很难避免不出现问题。典型问题如

应该释放的内存忘了释放导致内存驻留;不该释放的你释放了,直接crash;内存已经释放了,指针不去置空,出现野指针。

庆幸的是13年以后,随着设备可用内存的增加,苹果强制使用自动内存管理(ARC)了。这个时候我们很不情愿的使用了ARC,虽然大家还在抨击苹果这个ARC真垃圾,清理内存不及时,不能有效控制内存的峰值,不如我们自己管理内存。但是我们要说这个ARC的确减轻了我们的负担,让我们编程更加的高效。在这个是时代里,原则就是只要有强指针指向这块内存,这块内存就会驻留,不会被释放;一旦没有强指针指向这块内存,这块内存就在系统方便的时候被回收了。这样程序猿就不用关心引用计数,不用release对象,野指针消失不见,崩溃明显减少。

ARC解决了大部分问题,但是我们要记住两点,一是使用cf对象的时候要记得自己创建的对象,自己记得release;而是避免循环引用。我遇到的问题是团队对于这种循环引用认识不足,因为即使有这种循环引用,APP照常运行,感觉不到什么问题,问题是感觉不到问题才是问题。我会问从内存管理方面APP为何会崩溃,回答是内存过大。其实问题在于内存峰值过高,系统出于保护自己的目的,shut了我们的APP。而导致内存峰值过高的罪魁祸首很大一部分来自于我们的内存泄漏。不断的内存泄漏,使得我们的APP占用内存越来越大,同时系统有不能及时清除,到达一定程度,APP运行开始缓慢甚至崩溃就不可避免了。delegate循环引用问题不大,基本上是block循环引用造成的问题。其实典型问题

1
2
__block TestModel*tModel = self.testModel; self.testModel.bClick = ^{
[tModel.array addObject:@”1”]; [self pushNext]; };
这段实例代码大家很熟悉,如果只能发现一处循环引用,就需要注意了。一方面self持有testModel对象,testModel持有bClick的block,block又调用self,持有了self导致循环引用。另一方面testModel不需要用block来修饰,同时testModel对象的block持有了testModel自身,造成循环引用。更多block内存问题,请自行谷哥。

二、容错处理不完善;

再者就是容错处理问题,这个问题从两方面来考虑:

一方面,我们需要增强我们代码的健壮性,该容错的地方进行必要的容错处理,举个例子,我们常见的崩溃引发问题,如数组越界,setObject:forKey:空值,initWithString:空值,数据类型不匹配。如果不进行有效的容错判断,裸奔的效果就像内存泄漏一样,测试没问题,到了线上崩溃就出现了,特别是APP的体积越来越大,后台逻辑越来越复杂,用户越来越多的情况下。比如字符串,建议进行如下判断:

1
2
if (string && [string isKindOfClass:[NSString class]] && string.length > 0) {
}
当然我使用了类别来拓展常用类型的判断使用时比较方便。类似这样:

1
2
3
4
5
NSStirng+Extension
- (BOOL)isValid{ if (string && [string isKindOfClass:[NSString class]] && string.length > 0) { return YES;
}else{ return NO;
}
}
使用时

1
2
if ([temp isValid]){
}
另一方面,我们如果处处都加这种判断,固然是好,但是总有漏网之鱼。另外我们不知道什么时候服务器就把字典传成了数组,不做处理的话就坑了,服务器出问题了我们客户端跟着崩,还说我们的代码不健壮。这个之后就需要利用运行时动态的替换方法来规避这种问题。例如setObject:forKey:问题,我们load的时候,进行方法替换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
+ (void)load{
Class dictCls = NSClassFromString(@”__NSDictionaryM”);
Method originalMethod = class_getInstanceMethod(dictCls, @selector(setObject:forKey:));
Method swizzledMethod = class_getInstanceMethod(dictCls, @selector(lcx_setObject:forKey:)); if (!originalMethod || !swizzledMethod) { return;}
method_exchangeImplementations(originalMethod, swizzledMethod);
}

  • (void)lcx_setObject:(id)anObject forKey:(id)aKey{ if (anObject == nil) {
    NSLog(@”crash—NSMutableDictionary set nil object”); return;
    } if (aKey == nil) {
    NSLog(@”crash—NSMutableDictionary set nil key”); return;
    }
    [self lcx_setObject:anObject forKey:aKey];
    }
    进一步引申,这个时候在崩溃的地方,我们是可以获取到堆栈信息的,我们可以把这些存起来,自建崩溃监控系统,来发现APP隐藏的crash。另外要注意的是这样hook目前只能拦截特定方法还不能拦截类型不匹配造成的unrecognized selector send to instance问题。

三、webview与其他崩溃。

最后是webview与其他崩溃。webview占很大内存特别是UIWebView,所以单独和大家说一下,一定不要在webview的vc里面出现循环引用,这样可能会导致大量内存无法释放。另外,webview记得在dealloc中将delegate置为nil,同时删除缓存数据,减少其所占的内存。webview内的h5页面如果本身不注意内存管理或者一些bug也会造成崩溃,只能让前段多注意了。有时我们在观测崩溃的时候,发现一些webcore的崩溃,崩溃率出现峰值后来又趋于正常,很可能就是h5页面上线了一些bug页面后来修复了。大家可以设置接收APP崩溃的邮件,及时反馈,及时解决。(崩溃监控建议大家使用bugly,自动上传dysm,堆栈信息都解析出来了,不用自己手动解析堆栈信息)。

我们还遇到一些常见的崩溃,主要是大家的代码习惯问题了,如观察者或者通知中心忘移除、观察者移除崩溃、多次push同一个控制器、NSTimer等。这些问题大家自己多注意就OK了。最后一定要注意多线程读取数据的问题以及避免非主线程操作UI。

面对这些问题,我们好好做了,崩溃率自然会下降,但是依赖自查还是不能完全避免问题。这就要每次提测前用工具走一遍,查看是否还遗留有问题。我的习惯:

1.Analyze进行静态检查

2.Instruments的leaks进行动态分析

3.Instruments的Allocations分析一下是否有大内存占用

4.MLeakFinder查一下循环引用

原文:http://www.cocoachina.com/ios/20170516/19272.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值