日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁(yè)技術(shù)文章
文章詳情頁(yè)

IOS中判斷卡頓的方案總結(jié)

瀏覽:135日期:2022-09-16 17:31:22
FPS

FPS (Frames Per Second) 是圖像領(lǐng)域中的定義,表示每秒渲染幀數(shù),通常用于衡量畫面的流暢度,每秒幀數(shù)越多,則表示畫面越流暢,60fps 最佳,一般我們的APP的FPS 只要保持在 50-60之間,用戶體驗(yàn)都是比較流暢的。

監(jiān)測(cè)FPS也有好幾種,這里只說最常用的方案,我最早是在YYFPSLabel中看到的。實(shí)現(xiàn)原理實(shí)現(xiàn)原理是向主線程的RunLoop的添加一個(gè)commonModes的CADisplayLink,每次屏幕刷新的時(shí)候都要執(zhí)行CADisplayLink的方法,所以可以統(tǒng)計(jì)1s內(nèi)屏幕刷新的次數(shù),也就是FPS了,下面貼上我用Swift實(shí)現(xiàn)的代碼:

class WeakProxy: NSObject {weak var target: NSObjectProtocol?init(target: NSObjectProtocol) {self.target = targetsuper.init() }override func responds(to aSelector: Selector!) -> Bool {return (target?.responds(to: aSelector) ?? false) || super.responds(to: aSelector) }override func forwardingTarget(for aSelector: Selector!) -> Any? {return target }}class FPSLabel: UILabel {var link:CADisplayLink!//記錄方法執(zhí)行次數(shù)var count: Int = 0//記錄上次方法執(zhí)行的時(shí)間,通過link.timestamp - _lastTime計(jì)算時(shí)間間隔var lastTime: TimeInterval = 0var _font: UIFont!var _subFont: UIFont! fileprivate let defaultSize = CGSize(width: 55,height: 20)override init(frame: CGRect) {super.init(frame: frame)if frame.size.width == 0 && frame.size.height == 0 {self.frame.size = defaultSize}self.layer.cornerRadius = 5self.clipsToBounds = trueself.textAlignment = NSTextAlignment.centerself.isUserInteractionEnabled = falseself.backgroundColor = UIColor.white.withAlphaComponent(0.7)_font = UIFont(name: 'Menlo', size: 14)if _font != nil { _subFont = UIFont(name: 'Menlo', size: 4)}else{ _font = UIFont(name: 'Courier', size: 14) _subFont = UIFont(name: 'Courier', size: 4)}link = CADisplayLink(target: WeakProxy.init(target: self), selector: #selector(FPSLabel.tick(link:)))link.add(to: RunLoop.main, forMode: .commonModes) }//CADisplayLink 刷新執(zhí)行的方法@objc func tick(link: CADisplayLink) {guard lastTime != 0 else { lastTime = link.timestampreturn}count += 1let timePassed = link.timestamp - lastTime//時(shí)間大于等于1秒計(jì)算一次,也就是FPSLabel刷新的間隔,不希望太頻繁刷新guard timePassed >= 1 else {return}lastTime = link.timestamplet fps = Double(count) / timePassedcount = 0let progress = fps / 60.0let color = UIColor(hue: CGFloat(0.27 * (progress - 0.2)), saturation: 1, brightness: 0.9, alpha: 1)let text = NSMutableAttributedString(string: '(Int(round(fps))) FPS')text.addAttribute(NSAttributedStringKey.foregroundColor, value: color, range: NSRange(location: 0, length: text.length - 3))text.addAttribute(NSAttributedStringKey.foregroundColor, value: UIColor.white, range: NSRange(location: text.length - 3, length: 3))text.addAttribute(NSAttributedStringKey.font, value: _font, range: NSRange(location: 0, length: text.length))text.addAttribute(NSAttributedStringKey.font, value: _subFont, range: NSRange(location: text.length - 4, length: 1))self.attributedText = text }// 把displaylin從Runloop modes中移除deinit {link.invalidate() }required init?(coder aDecoder: NSCoder) {fatalError('init(coder:) has not been implemented') }}RunLoop

其實(shí)FPS中CADisplayLink的使用也是基于RunLoop,都依賴main RunLoop。我們來(lái)看看

先來(lái)看看簡(jiǎn)版的RunLoop的代碼

// 1.進(jìn)入loop__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled)// 2.RunLoop 即將觸發(fā) Timer 回調(diào)。__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);// 3.RunLoop 即將觸發(fā) Source0 (非port) 回調(diào)。__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);// 4.RunLoop 觸發(fā) Source0 (非port) 回調(diào)。sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle)// 5.執(zhí)行被加入的block__CFRunLoopDoBlocks(runloop, currentMode);// 6.RunLoop 的線程即將進(jìn)入休眠(sleep)。__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);// 7.調(diào)用 mach_msg 等待接受 mach_port 的消息。線程將進(jìn)入休眠, 直到被下面某一個(gè)事件喚醒。__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort)// 進(jìn)入休眠// 8.RunLoop 的線程剛剛被喚醒了。__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting// 9.如果一個(gè) Timer 到時(shí)間了,觸發(fā)這個(gè)Timer的回調(diào)__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())// 10.如果有dispatch到main_queue的block,執(zhí)行bloc __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);// 11.如果一個(gè) Source1 (基于port) 發(fā)出事件了,處理這個(gè)事件__CFRunLoopDoSource1(runloop, currentMode, source1, msg);// 12.RunLoop 即將退出__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

我們可以看到RunLoop調(diào)用方法主要集中在kCFRunLoopBeforeSources和kCFRunLoopAfterWaiting之間,有人可能會(huì)問kCFRunLoopAfterWaiting之后也有一些方法調(diào)用,為什么不監(jiān)測(cè)呢,我的理解,大部分導(dǎo)致卡頓的的方法是在kCFRunLoopBeforeSources和kCFRunLoopAfterWaiting之間,比如source0主要是處理App內(nèi)部事件,App自己負(fù)責(zé)管理(出發(fā)),如UIEvent(Touch事件等,GS發(fā)起到RunLoop運(yùn)行再到事件回調(diào)到UI)、CFSocketRef。開辟一個(gè)子線程,然后實(shí)時(shí)計(jì)算 kCFRunLoopBeforeSources 和 kCFRunLoopAfterWaiting 兩個(gè)狀態(tài)區(qū)域之間的耗時(shí)是否超過某個(gè)閥值,來(lái)斷定主線程的卡頓情況。

這里做法又有點(diǎn)不同,iOS實(shí)時(shí)卡頓監(jiān)控3 是設(shè)置連續(xù)5次超時(shí)50ms認(rèn)為卡頓,戴銘在 GCDFetchFeed4 中設(shè)置的是連續(xù)3次超時(shí)80ms認(rèn)為卡頓的代碼。以下是iOS實(shí)時(shí)卡頓監(jiān)控中提供的代碼:

- (void)start{if (observer)return;// 信號(hào) semaphore = dispatch_semaphore_create(0);// 注冊(cè)RunLoop狀態(tài)觀察CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL}; observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities,YES,0, &runLoopObserverCallBack, &context);CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);// 在子線程監(jiān)控時(shí)長(zhǎng)dispatch_async(dispatch_get_global_queue(0, 0), ^{while (YES){long st = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC));if (st != 0) {if (!observer){ timeoutCount = 0; semaphore = 0; activity = 0;return;}if (activity==kCFRunLoopBeforeSources || activity==kCFRunLoopAfterWaiting){if (++timeoutCount < 5)continue; PLCrashReporterConfig *config = [[PLCrashReporterConfig alloc] initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD symbolicationStrategy:PLCrashReporterSymbolicationStrategyAll]; PLCrashReporter *crashReporter = [[PLCrashReporter alloc] initWithConfiguration:config];NSData *data = [crashReporter generateLiveReport]; PLCrashReport *reporter = [[PLCrashReport alloc] initWithData:data error:NULL];NSString *report = [PLCrashReportTextFormatter stringValueForCrashReport:reporter withTextFormat:PLCrashReportTextFormatiOS];NSLog(@'------------n%@n------------', report);} } timeoutCount = 0;} });}子線程Ping

但是由于主線程的RunLoop在閑置時(shí)基本處于Before Waiting狀態(tài),這就導(dǎo)致了即便沒有發(fā)生任何卡頓,這種檢測(cè)方式也總能認(rèn)定主線程處在卡頓狀態(tài)。這套卡頓監(jiān)控方案大致思路為:創(chuàng)建一個(gè)子線程通過信號(hào)量去ping主線程,因?yàn)閜ing的時(shí)候主線程肯定是在kCFRunLoopBeforeSources和kCFRunLoopAfterWaiting之間。每次檢測(cè)時(shí)設(shè)置標(biāo)記位為YES,然后派發(fā)任務(wù)到主線程中將標(biāo)記位設(shè)置為NO。接著子線程沉睡超時(shí)闕值時(shí)長(zhǎng),判斷標(biāo)志位是否成功設(shè)置成NO,如果沒有說明主線程發(fā)生了卡頓。ANREye5中就是使用子線程Ping的方式監(jiān)測(cè)卡頓的。

@interface PingThread : NSThread......@end@implementation PingThread- (void)main { [self pingMainThread];}- (void)pingMainThread {while (!self.cancelled) {@autoreleasepool {dispatch_async(dispatch_get_main_queue(), ^{[_lock unlock]; });CFAbsoluteTime pingTime = CFAbsoluteTimeGetCurrent();NSArray *callSymbols = [StackBacktrace backtraceMainThread]; [_lock lock];if (CFAbsoluteTimeGetCurrent() - pingTime >= _threshold) {...... } [NSThread sleepForTimeInterval: _interval];} }}@end

以下是我用Swift實(shí)現(xiàn)的:

public class CatonMonitor {enum Constants {static let timeOutInterval: TimeInterval = 0.05static let queueTitle = 'com.roy.PerformanceMonitor.CatonMonitor' }private var queue: DispatchQueue = DispatchQueue(label: Constants.queueTitle)private var isMonitoring = falseprivate var semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)public init() {}public func start() {guard !isMonitoring else { return }isMonitoring = truequeue.async {while self.isMonitoring {var timeout = trueDispatchQueue.main.async { timeout = falseself.semaphore.signal()}Thread.sleep(forTimeInterval: Constants.timeOutInterval)if timeout {let symbols = RCBacktrace.callstack(.main)for symbol in symbols {print(symbol.description) }}self.semaphore.wait() }} }public func stop() {guard isMonitoring else { return }isMonitoring = false }}CPU超過了80%

這個(gè)是Matrix-iOS 卡頓監(jiān)控提到的:

我們也認(rèn)為 CPU 過高也可能導(dǎo)致應(yīng)用出現(xiàn)卡頓,所以在子線程檢查主線程狀態(tài)的同時(shí),如果檢測(cè)到 CPU 占用過高,會(huì)捕獲當(dāng)前的線程快照保存到文件中。目前微信應(yīng)用中認(rèn)為,單核 CPU 的占用超過了 80%,此時(shí)的 CPU 占用就過高了。

這種方式一般不能單獨(dú)拿來(lái)作為卡頓監(jiān)測(cè),但可以像微信Matrix一樣配合其他方式一起工作。

戴銘在GCDFetchFeed中如果CPU 的占用超過了 80%也捕獲函數(shù)調(diào)用棧,以下是代碼:

#define CPUMONITORRATE 80+ (void)updateCPU {thread_act_array_t threads;mach_msg_type_number_t threadCount = 0;const task_t thisTask = mach_task_self();kern_return_t kr = task_threads(thisTask, &threads, &threadCount);if (kr != KERN_SUCCESS) {return; }for (int i = 0; i < threadCount; i++) {thread_info_data_t threadInfo;thread_basic_info_t threadBaseInfo;mach_msg_type_number_t threadInfoCount = THREAD_INFO_MAX;if (thread_info((thread_act_t)threads[i], THREAD_BASIC_INFO, (thread_info_t)threadInfo, &threadInfoCount) == KERN_SUCCESS) { threadBaseInfo = (thread_basic_info_t)threadInfo;if (!(threadBaseInfo->flags & TH_FLAGS_IDLE)) {integer_t cpuUsage = threadBaseInfo->cpu_usage / 10;if (cpuUsage > CPUMONITORRATE) {//cup 消耗大于設(shè)置值時(shí)打印和記錄堆棧 NSString *reStr = smStackOfThread(threads[i]); SMCallStackModel *model = [[SMCallStackModel alloc] init]; model.stackStr = reStr;//記錄數(shù)據(jù)庫(kù)中 [[[SMLagDB shareInstance] increaseWithStackModel:model] subscribeNext:^(id x) {}];// NSLog(@'CPU useage overload thread stack:n%@',reStr);} }} }}卡頓方法的棧信息

當(dāng)我們得到卡頓的時(shí)間點(diǎn),就要立即拿到卡頓的堆棧,有兩種方式一種是遍歷棧幀,實(shí)現(xiàn)原理我在iOS獲取任意線程調(diào)用棧7寫的挺詳細(xì)的,同時(shí)開源了代碼RCBacktrace,另一種方式是通過Signal獲取任意線程調(diào)用棧,實(shí)現(xiàn)原理我在通過Signal handling(信號(hào)處理)獲取任意線程調(diào)用棧寫了,代碼在backtrace-swift,但這種方式在調(diào)試時(shí)比較麻煩,建議用第一種方式。

以上就是IOS中判斷卡頓的方案總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于IOS卡頓檢測(cè)的資料請(qǐng)關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: IOS
相關(guān)文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
久久精品免费一区二区三区| 日韩欧美激情电影| 国产亚洲欧洲| 欧美激情99| 久久av在线| 日本一二区不卡| 天堂俺去俺来也www久久婷婷| 久久精品国产免费| 在线精品亚洲| 色吊丝一区二区| 国产精品主播在线观看| 亚洲欧洲日本mm| 国产精品久久观看| 日韩一区二区三区免费视频| 一本大道色婷婷在线| 18国产精品| 热久久免费视频| 久久一区二区中文字幕| 欧美黄色精品| 亚洲精品一级二级三级| 日韩精品网站| 国产精品成人3p一区二区三区| 亚洲免费观看| 国内不卡的一区二区三区中文字幕| 中文字幕一区二区三区日韩精品 | 亚洲一级网站| 精品视频在线观看网站| 综合亚洲视频| 免费精品国产| 日韩欧美自拍| 国产一区二区三区四区| 欧美精品国产一区| 亚洲精品激情| 制服诱惑一区二区| 婷婷综合六月| 国产成人免费精品| 欧美激情精品| 久久国产人妖系列| 日韩一区网站| 亚洲精品少妇| 亚洲精品高潮| 视频一区欧美日韩| 99日韩精品| 久久久人人人| 亚洲精品88| 97人人精品| 精品一区二区三区的国产在线观看| 欧美日韩91| 青草久久视频| 一级成人国产| 亚洲欧美日韩一区在线观看| 91成人精品视频| 亚洲午夜91| 欧美二区视频| 国产高清一区二区| 亚洲二区免费| 免费av一区二区三区四区| 国产日韩综合| 日本美女一区| 国产在线不卡一区二区三区| 日韩av三区| 亚洲综合婷婷| 99xxxx成人网| 麻豆成人在线| 黑丝一区二区| 亚洲欧洲另类| 久久最新视频| 亚洲精品第一| 日韩高清欧美激情| 国产三级精品三级在线观看国产| 欧美日韩亚洲一区二区三区在线| 国产日韩欧美一区二区三区在线观看| 欧美在线首页| 麻豆精品久久| 久久香蕉网站| 97国产精品| 日本精品在线中文字幕| 激情婷婷久久| 欧美综合二区| 日韩免费精品| 青青草国产成人99久久| 久久精品 人人爱| 麻豆精品久久久| 91亚洲国产| 婷婷成人在线| 久久xxxx精品视频| 日韩欧美四区| 久久丁香四色| 最新中文字幕在线播放| 国产99久久| 天堂av在线一区| 日本不卡在线视频| 国产精品jk白丝蜜臀av小说| 国产精品精品| 欧美在线亚洲| 亚洲一区二区三区在线免费| 久久国际精品| 三上亚洲一区二区| 精品欧美激情在线观看| 蜜臀va亚洲va欧美va天堂 | 久久精品毛片| 日韩成人亚洲| 中文亚洲欧美| 国产欧美日韩影院| 成人一区而且| 欧美精品激情| 日本不卡高清| 久久精品色播| 99精品国产一区二区三区| 9久re热视频在线精品| 视频在线在亚洲| 国产精品久一| 一区二区三区视频免费观看| 综合激情婷婷| 岛国精品一区| 亚洲一区二区三区四区五区午夜| 日本午夜精品久久久久| 黄色aa久久| 在线看片日韩| 日韩av专区| 三级在线观看一区二区| 麻豆精品久久久| 免费毛片在线不卡| 日韩黄色av| 在线一区av| 亚洲欧洲美洲国产香蕉| 日韩1区在线| 在线精品亚洲| 久久影院午夜精品| 亚洲涩涩av| 中文在线免费视频| 亚洲一级淫片| 日韩电影二区| 日韩黄色在线观看| 99国产精品一区二区| 欧美日韩18| 在线一区电影| 国产中文欧美日韩在线| 亚洲精品一二三区区别| а√在线中文在线新版| 超碰99在线| 尤物tv在线精品| 国产亚洲欧洲| 日韩高清国产一区在线| 国产精品最新| 99久久久久国产精品| 亚洲精品乱码日韩| 麻豆国产91在线播放| 激情欧美亚洲| 日韩成人午夜精品| 一区二区三区四区日本视频| 视频一区视频二区中文字幕| 久久影院资源站| 国产午夜精品一区二区三区欧美| 日韩欧美激情电影| 色婷婷久久久| 欧美激情国产在线| 国产精品中文字幕制服诱惑| 亚洲欧美日韩国产一区二区| 日韩毛片视频| 麻豆国产精品| 日韩精品福利一区二区三区| 午夜久久久久| 国产精品99一区二区三| 日本午夜精品久久久| 欧美午夜不卡| 日本免费久久| 久久99性xxx老妇胖精品| 亚洲精品免费观看| 久久精品99久久无色码中文字幕| 老色鬼精品视频在线观看播放| 亚洲精品亚洲人成在线观看| 欧美不卡高清| 香蕉成人av| 精品国产美女a久久9999| 欧美啪啪一区| 亚洲+小说+欧美+激情+另类| 好看的亚洲午夜视频在线| 黑人精品一区| 久久影院一区二区三区| 日韩av午夜在线观看| 蜜桃视频一区二区| 精品91久久久久| 国产91精品对白在线播放| 精品三级av| 国产极品嫩模在线观看91精品| 日本精品另类| 日韩欧美中文字幕在线视频| 伊人www22综合色| 久久福利毛片| 中文在线一区| 欧美日韩国产一区精品一区| 久久精品主播| 91精品久久久久久久久久不卡| 亚洲黄色免费看| 高清av不卡| 日韩在线综合| 麻豆精品蜜桃| 日韩av一级| 色爱av综合网|