偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

iOS 之如何利用 RunLoop 原理去監(jiān)控卡頓?

移動開發(fā) iOS
通過 Runloop 來檢測卡頓,還是很有必要的。對提高 app 的用戶使用體驗還是很有幫助的。畢竟卡頓是偶顯的不容易復(fù)現(xiàn)。所以檢測卡頓來來抓取堆棧信息,分析并解決卡頓,還是很有必要的。

[[397057]]

1. 前言

卡頓問題,就是在主線程上無法響應(yīng)用戶交互的問題。如果一個 App 時不時地就給你卡一下,有 時還長時間無響應(yīng),這時你還愿意繼續(xù)用它嗎?所以說,卡頓問題對 App 的傷害是巨大的,也是 我們必須要重點解決的一個問題。

2. 卡頓原因

現(xiàn)在,我們先來看一下導(dǎo)致卡頓問題的幾種原因:

  • 復(fù)雜 UI 、圖文混排的繪制量過大;
  • 在主線程上做網(wǎng)絡(luò)同步請求;
  • 在主線程做大量的 IO 操作;
  • 運算量過大,CPU 持續(xù)高占用;
  • 死鎖和主子線程搶鎖。

那么,我們?nèi)绾伪O(jiān)控到什么時候會出現(xiàn)卡頓呢?是要監(jiān)視FPS嗎?

FPS 是一秒顯示的幀數(shù),也就是一秒內(nèi)畫面變化數(shù)量。當(dāng)FPS達到60,說明界面很流程,當(dāng)FPS低于24,頁面流暢度不是那么流暢,但是不能說卡主了。

由此可見,簡單地通過監(jiān)視 FPS 是很難確定是否會出現(xiàn)卡頓問題了,所以我就果斷棄了通過監(jiān)視 FPS 來監(jiān)控卡頓的方案。

那么,我們到底應(yīng)該使用什么方案來監(jiān)控卡頓呢?

3. 使用RunLoop來檢控卡頓

對于 iOS 開發(fā)來說,監(jiān)控卡頓就是要去找到主線程上都做了哪些事兒。我們都知道,線程的消息 事件是依賴于 NSRunLoop 的,所以從 NSRunLoop 入手,就可以知道主線程上都調(diào)用了哪些方 法。我們通過監(jiān)聽 NSRunLoop 的狀態(tài),就能夠發(fā)現(xiàn)調(diào)用方法是否執(zhí)行時間過長,從而判斷出是 否會出現(xiàn)卡頓。

所以,我推薦的監(jiān)控卡頓的方案是:通過監(jiān)控 RunLoop 的狀態(tài)來判斷是否會出現(xiàn)卡頓。

3.1 Runloop

RunLoop是iOS開發(fā)中的一個基礎(chǔ)概念,為了幫助你理解并用好這個對象,接下來我會先和你介紹一下它可以做哪些事兒,以及它為什么可以做成這些事兒。

RunLoop 這個對象,在 iOS 里由 CFRunLoop 實現(xiàn)。簡單來說,RunLoop 是用來監(jiān)聽輸入源,進 行調(diào)度處理的。這里的輸入源可以是輸入設(shè)備、網(wǎng)絡(luò)、周期性或者延遲時間、異步回調(diào)。

RunLoop 會接收兩種類型的輸入源:

  • 一種是來自另一個線程或者來自不同應(yīng)用的異步消息;
  • 另一 種是來自預(yù)訂時間或者重復(fù)間隔的同步事件。

RunLoop 的目的是,當(dāng)有事件要去處理時保持線程忙,當(dāng)沒有事件要處理時讓線程進入休眠。所 以,了解 RunLoop 原理不光能夠運用到監(jiān)控卡頓上,還可以提高用戶的交互體驗。通過將那些 繁重而不緊急會大量占用 CPU 的任務(wù)(比如圖片加載),放到空閑的 RunLoop 模式里執(zhí)行,這 樣就可以避開在 UITrackingRunLoopMode 這個 RunLoop 模式時是執(zhí)行。

UITrackingRunLoopMode 是用戶進行滾動操作時會切換到的 RunLoop 模式,避免在這個 RunLoop 模式執(zhí)行繁重的 CPU 任務(wù),就能避免影響用戶交互操作上體驗。

接下來,我就通過 CFRunLoop 的源碼來跟你分享下 RunLoop 的原理吧。

 3.2 RunLoop原理

其內(nèi)部代碼整理如下:

  1. /// 用DefaultMode啟動 
  2. void CFRunLoopRun(void) { 
  3.     CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); 
  4.   
  5. /// 用指定的Mode啟動,允許設(shè)置RunLoop超時時間 
  6. int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) { 
  7.     return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled); 
  8.   
  9. /// RunLoop的實現(xiàn) 
  10. int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) { 
  11.      
  12.     /// 首先根據(jù)modeName找到對應(yīng)mode 
  13.     CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false); 
  14.     /// 如果mode里沒有source/timer/observer, 直接返回。 
  15.     if (__CFRunLoopModeIsEmpty(currentMode)) return
  16.      
  17.     /// 1. 通知 Observers: RunLoop 即將進入 loop。 
  18.     __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry); 
  19.      
  20.     /// 內(nèi)部函數(shù),進入loop 
  21.     __CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) { 
  22.          
  23.         Boolean sourceHandledThisLoop = NO
  24.         int retVal = 0; 
  25.         do { 
  26.   
  27.             /// 2. 通知 Observers: RunLoop 即將觸發(fā) Timer 回調(diào)。 
  28.             __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers); 
  29.             /// 3. 通知 Observers: RunLoop 即將觸發(fā) Source0 (非port) 回調(diào)。 
  30.             __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources); 
  31.             /// 執(zhí)行被加入的block 
  32.             __CFRunLoopDoBlocks(runloop, currentMode); 
  33.              
  34.             /// 4. RunLoop 觸發(fā) Source0 (非port) 回調(diào)。 
  35.             sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle); 
  36.             /// 執(zhí)行被加入的block 
  37.             __CFRunLoopDoBlocks(runloop, currentMode); 
  38.   
  39.             /// 5. 如果有 Source1 (基于port) 處于 ready 狀態(tài),直接處理這個 Source1 然后跳轉(zhuǎn)去處理消息。 
  40.             if (__Source0DidDispatchPortLastTime) { 
  41.                 Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg) 
  42.                 if (hasMsg) goto handle_msg; 
  43.             } 
  44.              
  45.             /// 通知 Observers: RunLoop 的線程即將進入休眠(sleep)。 
  46.             if (!sourceHandledThisLoop) { 
  47.                 __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting); 
  48.             } 
  49.              
  50.             /// 7. 調(diào)用 mach_msg 等待接受 mach_port 的消息。線程將進入休眠, 直到被下面某一個事件喚醒。 
  51.             /// • 一個基于 port 的Source 的事件。 
  52.             /// • 一個 Timer 到時間了 
  53.             /// • RunLoop 自身的超時時間到了 
  54.             /// • 被其他什么調(diào)用者手動喚醒 
  55.             __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) { 
  56.                 mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg 
  57.             } 
  58.   
  59.             /// 8. 通知 Observers: RunLoop 的線程剛剛被喚醒了。 
  60.             __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting); 
  61.              
  62.             /// 收到消息,處理消息。 
  63.             handle_msg: 
  64.   
  65.             /// 9.1 如果一個 Timer 到時間了,觸發(fā)這個Timer的回調(diào)。 
  66.             if (msg_is_timer) { 
  67.                 __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time()) 
  68.             }  
  69.   
  70.             /// 9.2 如果有dispatch到main_queue的block,執(zhí)行block。 
  71.             else if (msg_is_dispatch) { 
  72.                 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); 
  73.             }  
  74.   
  75.             /// 9.3 如果一個 Source1 (基于port) 發(fā)出事件了,處理這個事件 
  76.             else { 
  77.                 CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort); 
  78.                 sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg); 
  79.                 if (sourceHandledThisLoop) { 
  80.                     mach_msg(reply, MACH_SEND_MSG, reply); 
  81.                 } 
  82.             } 
  83.              
  84.             /// 執(zhí)行加入到Loop的block 
  85.             __CFRunLoopDoBlocks(runloop, currentMode); 
  86.              
  87.   
  88.             if (sourceHandledThisLoop && stopAfterHandle) { 
  89.                 /// 進入loop時參數(shù)說處理完事件就返回。 
  90.                 retVal = kCFRunLoopRunHandledSource; 
  91.             } else if (timeout) { 
  92.                 /// 超出傳入?yún)?shù)標(biāo)記的超時時間了 
  93.                 retVal = kCFRunLoopRunTimedOut; 
  94.             } else if (__CFRunLoopIsStopped(runloop)) { 
  95.                 /// 被外部調(diào)用者強制停止了 
  96.                 retVal = kCFRunLoopRunStopped; 
  97.             } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) { 
  98.                 /// source/timer/observer一個都沒有了 
  99.                 retVal = kCFRunLoopRunFinished; 
  100.             } 
  101.              
  102.             /// 如果沒超時,mode里沒空,loop也沒被停止,那繼續(xù)loop。 
  103.         } while (retVal == 0); 
  104.     } 
  105.      
  106.     /// 10. 通知 Observers: RunLoop 即將退出。 
  107.     __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); 

可以看到,實際上 RunLoop 就是這樣一個函數(shù),其內(nèi)部是一個 do-while 循環(huán)。當(dāng)你調(diào)用 CFRunLoopRun() 時,線程就會一直停留在這個循環(huán)里;直到超時或被手動停止,該函數(shù)才會返回。

RunLoop內(nèi)部的邏輯圖:

RunLoop內(nèi)部原理.png

4. 如何檢測卡頓

4.1 首先知道RunLoop的六個狀態(tài)

  1. typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { 
  2.     kCFRunLoopEntry         = (1UL << 0), // 即將進入Loop 
  3.     kCFRunLoopBeforeTimers  = (1UL << 1), // 即將處理 Timer 
  4.     kCFRunLoopBeforeSources = (1UL << 2), // 即將處理 Source 
  5.     kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進入休眠 
  6.     kCFRunLoopAfterWaiting  = (1UL << 6), // 剛從休眠中喚醒 
  7.     kCFRunLoopExit          = (1UL << 7), // 即將退出Loop 
  8. kCFRunLoopAllActivities // loop所有狀態(tài)改變 
  9.  
  10. }; 

要想監(jiān)聽RunLoop,你就首先需要創(chuàng)建一個 CFRunLoopObserverContext 觀察者,代碼如下:

  1. - (void)registerObserver { 
  2.      
  3.     CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL}; 
  4.     //創(chuàng)建Run loop observer對象 
  5.     //第一個參數(shù)用于分配observer對象的內(nèi)存 
  6.     //第二個參數(shù)用以設(shè)置observer所要關(guān)注的事件,詳見回調(diào)函數(shù)myRunLoopObserver中注釋 
  7.     //第三個參數(shù)用于標(biāo)識該observer是在第一次進入run loop時執(zhí)行還是每次進入run loop處理時均執(zhí)行 
  8.     //第四個參數(shù)用于設(shè)置該observer的優(yōu)先級 
  9.     //第五個參數(shù)用于設(shè)置該observer的回調(diào)函數(shù) 
  10.     //第六個參數(shù)用于設(shè)置該observer的運行環(huán)境 
  11.     CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, 
  12.                                                             kCFRunLoopAllActivities, 
  13.                                                             YES, 
  14.                                                             0, 
  15.                                                             &runLoopObserverCallBack, 
  16.                                                             &context); 
  17.     CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes); 
  18.  

實時獲取變化的回調(diào)的方法:

  1. //每當(dāng)runloop狀態(tài)變化的觸發(fā)這個回調(diào)方法 
  2. static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { 
  3.     MyClass *object = (__bridge MyClass*)info; 
  4.     object->activity = activity; 

其中UI主要集中在

_CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION(source0)和CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION(source1)之前。

獲取kCFRunLoopBeforeSources到kCFRunLoopBeforeWaiting再到kCFRunLoopAfterWaiting的狀態(tài)就可以知道是否有卡頓的情況。

4.2 檢測卡頓的思路

只需要另外再開啟一個線程,實時計算這兩個狀態(tài)區(qū)域之間的耗時是否到達某個閥值,便能揪出這些性能殺手。

  • 監(jiān)聽runloop狀態(tài)變化回調(diào)方法
  1. // 就是runloop有一個狀態(tài)改變 就記錄一下 
  2. static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { 
  3.     BGPerformanceMonitor *monitor = (__bridge BGPerformanceMonitor*)info; 
  4.      
  5.     // 記錄狀態(tài)值 
  6.     monitor->activity = activity; 
  7.      
  8.     // 發(fā)送信號 
  9.     dispatch_semaphore_t semaphore = monitor->semaphore; 
  10.     long st = dispatch_semaphore_signal(semaphore); 
  11.     NSLog(@"dispatch_semaphore_signal:st=%ld,time:%@",st,[BGPerformanceMonitor getCurTime]); 
  12.      
  13.  
  14.        /* Run Loop Observer Activities */ 
  15. //    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { 
  16. //        kCFRunLoopEntry = (1UL << 0),    // 進入RunLoop循環(huán)(這里其實還沒進入) 
  17. //        kCFRunLoopBeforeTimers = (1UL << 1),  // RunLoop 要處理timer了 
  18. //        kCFRunLoopBeforeSources = (1UL << 2), // RunLoop 要處理source了 
  19. //        kCFRunLoopBeforeWaiting = (1UL << 5), // RunLoop要休眠了 
  20. //        kCFRunLoopAfterWaiting = (1UL << 6),   // RunLoop醒了 
  21. //        kCFRunLoopExit = (1UL << 7),           // RunLoop退出(和kCFRunLoopEntry對應(yīng)) 
  22. //        kCFRunLoopAllActivities = 0x0FFFFFFFU //RunLoop狀態(tài)變化 
  23. //    }; 
  24.     if (activity == kCFRunLoopEntry) {  // 即將進入RunLoop 
  25.         NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopEntry"); 
  26.     } else if (activity == kCFRunLoopBeforeTimers) {    // 即將處理Timer 
  27.         NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopBeforeTimers"); 
  28.     } else if (activity == kCFRunLoopBeforeSources) {   // 即將處理Source 
  29.         NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopBeforeSources"); 
  30.     } else if (activity == kCFRunLoopBeforeWaiting) {   //即將進入休眠 
  31.         NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopBeforeWaiting"); 
  32.     } else if (activity == kCFRunLoopAfterWaiting) {    // 剛從休眠中喚醒 
  33.         NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopAfterWaiting"); 
  34.     } else if (activity == kCFRunLoopExit) {    // 即將退出RunLoop 
  35.         NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopExit"); 
  36.     } else if (activity == kCFRunLoopAllActivities) { 
  37.         NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopAllActivities"); 
  38.     } 
  • 開啟runloop監(jiān)聽
  1. // 開始監(jiān)聽 
  2. - (void)startMonitor { 
  3.     if (observer) { 
  4.         return
  5.     } 
  6.      
  7.     // 創(chuàng)建信號 
  8.     semaphore = dispatch_semaphore_create(0); 
  9.     NSLog(@"dispatch_semaphore_create:%@",[BGPerformanceMonitor getCurTime]); 
  10.      
  11.     // 注冊RunLoop狀態(tài)觀察 
  12.     CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL}; 
  13.     //創(chuàng)建Run loop observer對象 
  14.     //第一個參數(shù)用于分配observer對象的內(nèi)存 
  15.     //第二個參數(shù)用以設(shè)置observer所要關(guān)注的事件,詳見回調(diào)函數(shù)myRunLoopObserver中注釋 
  16.     //第三個參數(shù)用于標(biāo)識該observer是在第一次進入run loop時執(zhí)行還是每次進入run loop處理時均執(zhí)行 
  17.     //第四個參數(shù)用于設(shè)置該observer的優(yōu)先級 
  18.     //第五個參數(shù)用于設(shè)置該observer的回調(diào)函數(shù) 
  19.     //第六個參數(shù)用于設(shè)置該observer的運行環(huán)境 
  20.     observer = CFRunLoopObserverCreate(kCFAllocatorDefault, 
  21.                                        kCFRunLoopAllActivities, 
  22.                                        YES, 
  23.                                        0, 
  24.                                        &runLoopObserverCallBack, 
  25.                                        &context); 
  26.     CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes); 
  27.      
  28.     // 在子線程監(jiān)控時長 
  29.     dispatch_async(dispatch_get_global_queue(0, 0), ^{ 
  30.         while (YES) {   // 有信號的話 就查詢當(dāng)前runloop的狀態(tài) 
  31.             // 假定連續(xù)5次超時50ms認(rèn)為卡頓(當(dāng)然也包含了單次超時250ms) 
  32.             // 因為下面 runloop 狀態(tài)改變回調(diào)方法runLoopObserverCallBack中會將信號量遞增 1,所以每次 runloop 狀態(tài)改變后,下面的語句都會執(zhí)行一次 
  33.             // dispatch_semaphore_wait:Returns zero on success, or non-zero if the timeout occurred. 
  34.             long st = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC)); 
  35.             NSLog(@"dispatch_semaphore_wait:st=%ld,time:%@",st,[self getCurTime]); 
  36.             if (st != 0) {  // 信號量超時了 - 即 runloop 的狀態(tài)長時間沒有發(fā)生變更,長期處于某一個狀態(tài)下 
  37.                 if (!observer) { 
  38.                     timeoutCount = 0; 
  39.                     semaphore = 0; 
  40.                     activity = 0; 
  41.                     return
  42.                 } 
  43.                 NSLog(@"st = %ld,activity = %lu,timeoutCount = %d,time:%@",st,activity,timeoutCount,[self getCurTime]); 
  44.                 // kCFRunLoopBeforeSources - 即將處理source kCFRunLoopAfterWaiting - 剛從休眠中喚醒 
  45.                 // 獲取kCFRunLoopBeforeSources到kCFRunLoopBeforeWaiting再到kCFRunLoopAfterWaiting的狀態(tài)就可以知道是否有卡頓的情況。 
  46.                 // kCFRunLoopBeforeSources:停留在這個狀態(tài),表示在做很多事情 
  47.                 if (activity == kCFRunLoopBeforeSources || activity == kCFRunLoopAfterWaiting) {    // 發(fā)生卡頓,記錄卡頓次數(shù) 
  48.                     if (++timeoutCount < 5) { 
  49.                         continue;   // 不足 5 次,直接 continue 當(dāng)次循環(huán),不將timeoutCount置為0 
  50.                     } 
  51.                      
  52.                      
  53.                     // 收集Crash信息也可用于實時獲取各線程的調(diào)用堆棧 
  54.                     PLCrashReporterConfig *config = [[PLCrashReporterConfig alloc] initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD symbolicationStrategy:PLCrashReporterSymbolicationStrategyAll]; 
  55.                      
  56.                     PLCrashReporter *crashReporter = [[PLCrashReporter alloc] initWithConfiguration:config]; 
  57.                      
  58.                     NSData *data = [crashReporter generateLiveReport]; 
  59.                     PLCrashReport *reporter = [[PLCrashReport alloc] initWithData:data error:NULL]; 
  60.                     NSString *report = [PLCrashReportTextFormatter stringValueForCrashReport:reporter withTextFormat:PLCrashReportTextFormatiOS]; 
  61.                      
  62.                     NSLog(@"---------卡頓信息\n%@\n--------------",report); 
  63.                 } 
  64.             } 
  65.             NSLog(@"dispatch_semaphore_wait timeoutCount = 0,time:%@",[self getCurTime]); 
  66.             timeoutCount = 0; 
  67.         } 
  68.     }); 

記錄卡頓的函數(shù)調(diào)用

監(jiān)控到了卡頓現(xiàn)場,當(dāng)然下一步便是記錄此時的函數(shù)調(diào)用信息,此處可以使用一個第三方Crash 收集組件 PLCrashReporter,它不僅可以收集 Crash 信息也可用于實時獲取各線程的調(diào)用堆棧,示例如下:

  1. PLCrashReporterConfig *config = [[PLCrashReporterConfig alloc] initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD 
  2.                                                                    symbolicationStrategy:PLCrashReporterSymbolicationStrategyAll]; 
  3. PLCrashReporter *crashReporter = [[PLCrashReporter alloc] initWithConfiguration:config]; 
  4. NSData *data = [crashReporter generateLiveReport]; 
  5. PLCrashReport *reporter = [[PLCrashReport alloc] initWithData:data error:NULL]; 
  6. NSString *report = [PLCrashReportTextFormatter stringValueForCrashReport:reporter 
  7.                                                           withTextFormat:PLCrashReportTextFormatiOS]; 
  8. NSLog(@"------------\n%@\n------------", report); 

當(dāng)檢測到卡頓時,抓取堆棧信息,然后在客戶端做一些過濾處理,便可以上報到服務(wù)器,通過收集一定量的卡頓數(shù)據(jù)后經(jīng)過分析便能準(zhǔn)確定位需要優(yōu)化的邏輯,這個實時卡頓監(jiān)控就大功告成了!

5. 結(jié)尾

 

通過 Runloop 來檢測卡頓,還是很有必要的。對提高 app 的用戶使用體驗還是很有幫助的。畢竟卡頓是偶顯的不容易復(fù)現(xiàn)。所以檢測卡頓來來抓取堆棧信息,分析并解決卡頓,還是很有必要的。

 

責(zé)任編輯:武曉燕 來源: 網(wǎng)羅開發(fā)
相關(guān)推薦

2025-04-29 08:20:00

無線監(jiān)控網(wǎng)絡(luò)無線網(wǎng)絡(luò)

2013-03-27 10:32:53

iOS多線程原理runloop介紹GCD

2018-07-27 18:47:01

數(shù)據(jù)庫MySQL線程

2021-08-03 16:35:04

AndroidANR內(nèi)存

2011-08-15 14:27:51

CocoaRunLoop

2021-04-02 14:23:12

WiFi網(wǎng)絡(luò)技術(shù)

2021-03-31 21:20:15

WiFi網(wǎng)絡(luò)漫游

2017-03-02 12:39:04

移動端iOS監(jiān)控體系

2023-02-03 15:14:15

2017-11-21 09:25:23

2021-11-28 21:26:39

Windows 7Windows微軟

2021-03-15 10:31:48

手機安卓蘋果

2021-08-31 23:09:50

微信功能技巧

2013-12-27 10:37:01

2011-03-25 15:01:26

Cacti監(jiān)控memcache

2011-03-29 15:35:14

cactimemcache

2015-06-03 16:33:23

手機銀行應(yīng)用性能APP

2012-08-20 09:45:18

SQL Server

2022-05-02 08:30:46

網(wǎng)絡(luò)Wi-Fi

2019-07-01 15:46:35

云平臺Kubernetes問題排查
點贊
收藏

51CTO技術(shù)棧公眾號