直播APP公屏优化
来源:互联网 发布:数据结构考研算法 编辑:程序博客网 时间:2024/04/30 19:47
直播APP频道公屏优化方案一些心得(未完)
做类似映客这种APP,频道性能问题是一个大问题。
现在在做直播APP,公屏上要的聊天记录,总是影响性能的一大部分原因,外加上 频道里面会有其他的操作,比如:倒计时,送礼物,视频本身,用户操作等等。下面记录一下iOS客户端本人的优化经历
公屏实现方案是UITableView
,然后自定义不同的UITableViewCell
子类,在需要的时候去加载。UITaleViewCell
继承如下图所示XXXBaseCell
做一些基础的样式设置 XXMessageCell
普通的聊天文本展示,XXGiftCell
送礼物的频道内部提醒。最开始使用的是自动布局的方式做UI
,
直奔主题,说优化
去掉自动布局的方案,原因是自动布局本身就是一个很复杂的算法。如果自动布局使用的不太好,还有可能造成离屏渲染,重复计算,像素重合的问题。
在数据Model中高度计算,并且缓存起来,横竖屏情况下,高度保证只计算一次。并且计算高度的任务放在后台。
/* *baseModel*/@interface XXXChannelChat : NSObject@property (nonatomic, assign) XXXChannelChatType chatType;@property (nonatomic, assign) CGFloat height;@property (nonatomic, assign) CGFloat fullScreenHeight;/** * 竖屏显示内容 横屏显示内容 */@property (nonatomic, strong) NSAttributedString *attributedString;@property (nonatomic, strong) NSAttributedString *fullScreenString;/** * 当前的高度 根据横竖屏 * * @return 高度 */- (CGFloat)currentHeight;@end
每个数据Model做一个计算Layout的Class.比如:
@interface XXModel : NSObject@property (nonatomic, strong) NSString *text;@property (nonatomic, strong) NSString *senderName;@end@interface XXXLayout : NSObject- (id)initWithModel:(XXModel *)model;//普通的Frame@property (nonatomic, readonly) CGRect textFrame;//全屏的frame@property (nonatomic, readonly) CGRect fullScreenFrame;@end- (void)layoutSubviews { [super layoutSubviews]; //设置Frame 记得加判断frame是否相等 self.label.frame = self.layout.labelFrame;}
这里的XXModel 应该从上面的BaseModel 继承。这里只是举个栗子。公屏消息 或者 送礼物, 或者 关注的消息过来的时候 先去初始化XXXLayout
,当然放在后台线程
然后在每个Cell的layoutSubviews
函数中去设置对应的Frame
TIPS:因为涉及到多线程,多以要防止一些在应该在主线程的操作放在后台,可以给UIView 加个分类,专门去做判断,比如:
使用runTime把系统的函数跟下面函数交换一下。很容易检测出来。- (void)XX_setNeedLayout {#ifdef DEBUG XXAssertMainThread();#endif [self lv_setNeedLayout];}- (void)XX_setNeedsDisplay {#ifdef DEBUG XXAssertMainThread();#endif [self XX_setNeedsDisplay];}- (void)XX_setNeedsDisplayInRect:(CGRect)rect {#ifdef DEBUG XXAssertMainThread();#endif [self XX_setNeedsDisplayInRect:rect];}
因为计算的Frame难免会有比如 50.669
这种数字 像素对齐问题会有,影响渲染效果:所以做一些像素对齐的处理很有必要,如下:每一次设置Frame之前都要先调用一下roundPixelRect
函数(ps:设置之前先调用CGRectEqualToRect
函数进行判断,毕竟对象属性调整是非常消耗CPU的。所以能不调增就尽量不调整)。
static inline CGFloat screenScale() { static CGFloat screenScale = 0.0; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ if ([NSThread isMainThread]) { screenScale = [[UIScreen mainScreen] scale]; } else { dispatch_sync(dispatch_get_main_queue(), ^{ screenScale = [[UIScreen mainScreen] scale]; }); } }); return screenScale;}static inline CGFloat roundPixelValue(CGFloat value) { CGFloat scale = screenScale(); return round(value * scale) / scale;}static inline CGRect roundPixelRect(CGRect rect) { return CGRectMake(roundPixelValue(rect.origin.x), roundPixelValue(rect.origin.y), roundPixelValue(rect.size.width), roundPixelValue(rect.size.height));}
预先申请一些Model的空间,大频率去刷UITableView ,不断的申请对CPU负荷也很大。所以,进入频道页面的时候,延迟1秒接受公屏消息,在后台申请好UITableViewCell 对应的Model空间,
//在后台线程预先申请100个数据Model//不用去初始化Model 的数据,ARC环境下会自动初始化为0 或者 NULL//GCDQueue 是自己写的一个方便操作GCD的工具[GCDQueue executeInLowPriorityGlobalQueue:^{ for(int i = 0; i < 100; ++ i) { XXXChannelTextMessage *message = [XXXChannelTextMessage new]; if (message) { [self.messageSet addObject:message]; } } }];/*** 对象不用的时候,同样捕捉到后台线程去释放。能重用尽量重用!!*/
UITableView
刷新频率要控制,这里使用的RAC,如果对效率要求到极致,可以不用RAC,毕竟消息转发的层数太多。这里如果有消息,1秒刷新一次,4s这类机型,2秒刷新一次!!!实际上的效果不提明显,可能是我们APP的频道人数不够多!
- (void)reloadTableView{ if (self.reloadDisposeable) {//如果当前有更新任务,直接返回 return; } static NSTimeInterval timer = 1.0f; static dispatch_once_t pre; dispatch_once(&pre,^{ //如果有必要,区分一下5C.低端设备刷新频率控制 if ([SystemInfoUtility iosScreenResolution] == UIDevice_iPhone4SRes) { timer *= 2; } }); //timer秒之后更新Tableview self.reloadDisposeable = [[RACScheduler mainThreadScheduler] afterDelay:timer schedule:^{ [self __update]; }];}- (void)__update { if (self.reloadDisposeable) {//结束标记 [self.reloadDisposeable dispose]; self.reloadDisposeable = nil; } VIPPerformBlockOnMainThread(^{ [self.tableView reloadData];//更新TableView [self scrollMessageTableToBottomIfNeeded:NO]; });}
尽量不使用__weak ,会增加把对象存入weak表的操作,weak对象也会加入autoreleasepool 中!
模拟器上观察卡顿的条件要经常打开看!
调试阶段,引入KMCGeigerCounter
来检测界面的卡顿情况。虽然这个本身就会存在一点点性能问题
引入 MLeaksFinder
观察内存泄漏。当然最后还是要使用XCode 提供的工具再检测一下是否有内存泄漏。
频道消息超过一定范围,及时清理一些(放在后台线程中清理),或者全部。然后Model记得重用。
做的一些Test: 比较OC中循环遍历的几种方式,虽然网上已经有很多比较了 比如 大神的这篇 ios中集合遍历方法的比较和技巧但是,由于我们操作集合的对象不同,而且牵扯到多线程,所以自己又比较了一翻。结论也跟大神的一致。有一点,不要乱用
NSLog
适当的使用缓存
使用
NSCache
对使用频率比较高的进行缓存,之所以选择NSCache是因为NSCache的又是比较明显:NSCache类结合了各种自动删除策略,以确保不会占用过多的系统内存。如果其它应用需要内存时,系统自动执行这些策略。当调用这些策略时,会从缓存中删除一些对象,以最大限度减少内存的占用。
NSCache是线程安全的,我们可以在不同的线程中添加、删除和查询缓存中的对象,而不需要锁定缓存区域。
不像NSMutableDictionary对象,一个缓存对象不会拷贝key对象。比如:公屏的消息要经过过滤率的。用户比较多的时候,大部分时候发的消息都一样:比如:6666 999 这样子的。连续几百个,几千个。每次过滤都会创建一个XML格式的对象去判断里面包含的类型能不能显示,频繁的申请空间,容易发热,对内存也是浪费。所以可以缓存:
//过滤Text能不能显示- (BOOL)filterAndAddChannelTexts:(NSString *)text{//text为空显示 if (!text) { return YES; } //清除text两边的空格 NSString *cleanString = [text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; if (!cleanString) { return YES; } //缓存对象 //以为仅仅只是存放BOOL值,所以不设置大小 static NSCache *cache = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ cache = [NSCache new]; }); NSString *origin = [text copy]; NSNumber *number = [cache objectForKey:text]; if (number) { //直接返回大小 return number.boolValue; } //创建XML对象进行过滤 ……
当然其他地方需要缓存的也尽量缓存一下。
使用RunLoop 把影响主线程的操作,分不同的时间段,提交到主线程,
- (void)XXXAddMessage { CFRunLoopRef runLoop = CFRunLoopGetCurrent(); CFStringRef runLoopMode = kCFRunLoopDefaultMode; CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity _) { //提交一个 NSDefaultRunLoopMode 到runLoop [self performSelector:@selector(AddMessage) onThread:[NSThread mainThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]]; CFRunLoopRemoveObserver(runLoop, observer, kCFRunLoopDefaultMode); CFRelease(observer); }); CFRunLoopAddObserver(runLoop, observer, runLoopMode);}- (void)AddMessage { //addMessage操作}
在有UI刷新或者,用户操作界面的时候任务就会取消
XXAssertMainThread
宏实现
//必须是主线程执行。#define XXAssertMainThread() NSAssert([NSThread isMainThread], @"This method must be called on the main thread")
Core Graphics绘制会有很大的性能开销,所以频道频繁创建的视图,会避免使用! - 如果对视图实现了-drawRect:方法,或者CALayerDelegate的-drawLayer:inContext:方法,那么在绘制任何东西之前都会产生一个巨大的性能开销。为了支持对图层内容的任意绘制,Core Animation必须创建一个内存中等大小的寄宿图片。然后一旦绘制结束之后,必须把图片数据通过IPC传到渲染服务器。在此基础上,Core Graphics绘制就会变得十分缓慢,所以在一个对性能十分挑剔的场景下这样做十分不好。 所以实现起来越简单越好!如果有大量使用,值得考虑有没有更好的方案!
使用
instruments
观察性能,耗时间的地方!CPU GPU使用率。
GPU使用率过高的情况下可以把UIImage 的解码一些操作放在后台线程,提前解码到内存。
尽量使用轻量级的控件。UILabel 可以使用 layer来代替,UIImageView 如果没有其他交互使用layer也足够了尽可能的合并网络请求。相同的网络请求次数过多,频率过高。
尽可能重用控件,数据!
控制线程的数目。针对业务,某些业务某些线程!
之所以做优化是因为频道里面人多的时候,公屏消息多,4s 5c 这样子的机器会卡顿。甚至频道里面人超过2万的时候高性能的机器也会发烫,发热 在做优化的过程中,参考了下面的连接。
参考链接:
每个版本APP做到最后必须做的事情
iOS APP性能优化
绘制像素到屏幕上,一定要搞懂!!!
绘制像素到屏幕上
YY大神的文章,要多看几遍才行
iOS保持界面流畅
iOS绘制一像素的线
- 直播APP公屏优化
- 直播app
- 直播app
- 直播APP
- 直播中的首屏加载优化
- iOS直播app原理
- 直播APP开发过程
- 直播app架构
- APP直播源码、云豹直播破解版
- SuperVideo,一款直播,点播,投屏并有的app
- 流媒体 直播细节优化
- 直播秒开优化
- API(APP会议直播)
- Android视频直播APP-WliveTV
- 一言不合你就用环信搞个直播APP
- 完整的iOS直播app
- 一个完整直播app功能分析
- 一个完整直播app功能分析
- WindowsBuilder控件中文编码问题
- AddressBook通讯录右边索引条
- 系统调用open的大概执行路径
- Github-git push 超时
- putty远程登录ssh主机
- 直播APP公屏优化
- ultra虚拟光驱加载iso文件,安装程序
- VMware Ubuntu 下与Win7共享文件夹
- 拾遗系列(十)NSURLConnection(了解)
- powershell命令学习
- 数据导入和导出的几种方法
- Json
- 利用颜色索引,判断from变化检测
- HDU 3582 BFS