激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - IOS - IOS中判斷卡頓的方案總結

IOS中判斷卡頓的方案總結

2021-06-07 16:50weixin_39800062 IOS

這篇文章主要介紹了IOS中判斷卡頓的方案總結,對IOS性能感興趣的同學,一定要看一下

FPS

 

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

監測FPS也有好幾種,這里只說最常用的方案,我最早是在YYFPSLabel中看到的。實現原理實現原理是向主線程的RunLoop的添加一個commonModes的CADisplayLink,每次屏幕刷新的時候都要執行CADisplayLink的方法,所以可以統計1s內屏幕刷新的次數,也就是FPS了,下面貼上我用Swift實現的代碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
class WeakProxy: NSObject {
 
weak var target: NSObjectProtocol?
 
init(target: NSObjectProtocol) {
self.target = target
super.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!
//記錄方法執行次數
var count: Int = 0
//記錄上次方法執行的時間,通過link.timestamp - _lastTime計算時間間隔
var lastTime: TimeInterval = 0
var _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 = 5
self.clipsToBounds = true
self.textAlignment = NSTextAlignment.center
self.isUserInteractionEnabled = false
self.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 刷新執行的方法
@objc func tick(link: CADisplayLink) {
 
guard lastTime != 0 else {
            lastTime = link.timestamp
return
        }
 
count += 1
let timePassed = link.timestamp - lastTime
 
//時間大于等于1秒計算一次,也就是FPSLabel刷新的間隔,不希望太頻繁刷新
guard timePassed >= 1 else {
return
        }
        lastTime = link.timestamp
let fps = Double(count) / timePassed
count = 0
 
let progress = fps / 60.0
let 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

 

其實FPS中CADisplayLink的使用也是基于RunLoop,都依賴main RunLoop。我們來看看

先來看看簡版的RunLoop的代碼

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 1.進入loop
__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled)
 
// 2.RunLoop 即將觸發 Timer 回調。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
// 3.RunLoop 即將觸發 Source0 (非port) 回調。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
// 4.RunLoop 觸發 Source0 (非port) 回調。
sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle)
// 5.執行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
 
// 6.RunLoop 的線程即將進入休眠(sleep)。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
 
// 7.調用 mach_msg 等待接受 mach_port 的消息。線程將進入休眠, 直到被下面某一個事件喚醒。
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort)
 
// 進入休眠
 
// 8.RunLoop 的線程剛剛被喚醒了。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting
 
// 9.如果一個 Timer 到時間了,觸發這個Timer的回調
__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
 
// 10.如果有dispatch到main_queue的block,執行bloc
 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
 
// 11.如果一個 Source1 (基于port) 發出事件了,處理這個事件
__CFRunLoopDoSource1(runloop, currentMode, source1, msg);
 
// 12.RunLoop 即將退出
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

我們可以看到RunLoop調用方法主要集中在kCFRunLoopBeforeSources和kCFRunLoopAfterWaiting之間,有人可能會問kCFRunLoopAfterWaiting之后也有一些方法調用,為什么不監測呢,我的理解,大部分導致卡頓的的方法是在kCFRunLoopBeforeSources和kCFRunLoopAfterWaiting之間,比如source0主要是處理App內部事件,App自己負責管理(出發),如UIEvent(Touch事件等,GS發起到RunLoop運行再到事件回調到UI)、CFSocketRef。開辟一個子線程,然后實時計算 kCFRunLoopBeforeSources 和 kCFRunLoopAfterWaiting 兩個狀態區域之間的耗時是否超過某個閥值,來斷定主線程的卡頓情況。

這里做法又有點不同,iOS實時卡頓監控3 是設置連續5次超時50ms認為卡頓,戴銘在 GCDFetchFeed4 中設置的是連續3次超時80ms認為卡頓的代碼。以下是iOS實時卡頓監控中提供的代碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
- (void)start
{
if (observer)
return;
 
// 信號
    semaphore = dispatch_semaphore_create(0);
 
// 注冊RunLoop狀態觀察
CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
    observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
                                       kCFRunLoopAllActivities,
YES,
0,
                                       &runLoopObserverCallBack,
                                       &context);
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
 
// 在子線程監控時長
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在閑置時基本處于Before Waiting狀態,這就導致了即便沒有發生任何卡頓,這種檢測方式也總能認定主線程處在卡頓狀態。這套卡頓監控方案大致思路為:創建一個子線程通過信號量去ping主線程,因為ping的時候主線程肯定是在kCFRunLoopBeforeSources和kCFRunLoopAfterWaiting之間。每次檢測時設置標記位為YES,然后派發任務到主線程中將標記位設置為NO。接著子線程沉睡超時闕值時長,判斷標志位是否成功設置成NO,如果沒有說明主線程發生了卡頓。ANREye5中就是使用子線程Ping的方式監測卡頓的。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@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實現的:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class CatonMonitor {
 
enum Constants {
static let timeOutInterval: TimeInterval = 0.05
static let queueTitle = "com.roy.PerformanceMonitor.CatonMonitor"
    }
 
private var queue: DispatchQueue = DispatchQueue(label: Constants.queueTitle)
private var isMonitoring = false
private var semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
 
public init() {}
 
public func start() {
guard !isMonitoring else { return }
 
        isMonitoring = true
        queue.async {
while self.isMonitoring {
 
var timeout = true
 
DispatchQueue.main.async {
                    timeout = false
self.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%

 

這個是Matrix-iOS 卡頓監控提到的:

我們也認為 CPU 過高也可能導致應用出現卡頓,所以在子線程檢查主線程狀態的同時,如果檢測到 CPU 占用過高,會捕獲當前的線程快照保存到文件中。目前微信應用中認為,單核 CPU 的占用超過了 80%,此時的 CPU 占用就過高了。

這種方式一般不能單獨拿來作為卡頓監測,但可以像微信Matrix一樣配合其他方式一起工作。

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#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 消耗大于設置值時打印和記錄堆棧
                    NSString *reStr = smStackOfThread(threads[i]);
                    SMCallStackModel *model = [[SMCallStackModel alloc] init];
                    model.stackStr = reStr;
//記錄數據庫中
                    [[[SMLagDB shareInstance] increaseWithStackModel:model] subscribeNext:^(id x) {}];
//                    NSLog(@"CPU useage overload thread stack:\n%@",reStr);
                }
            }
        }
    }
}

卡頓方法的棧信息

 

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

以上就是IOS中判斷卡頓的方案總結的詳細內容,更多關于IOS卡頓檢測的資料請關注服務器之家其它相關文章!

原文鏈接:https://blog.csdn.net/weixin_39800062/article/details/113413984

延伸 · 閱讀

精彩推薦
  • IOS詳解iOS中多個網絡請求的同步問題總結

    詳解iOS中多個網絡請求的同步問題總結

    這篇文章主要介紹了詳解iOS中多個網絡請求的同步問題總結,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧...

    liang199111312021-03-15
  • IOSiOS中滑動控制屏幕亮度和系統音量(附加AVAudioPlayer基本用法和Masonry簡單使用)

    iOS中滑動控制屏幕亮度和系統音量(附加AVAudioPlayer基本用法和

    這篇文章主要介紹了iOS中滑動控制屏幕亮度和系統音量(附加AVAudioPlayer基本用法和Masonry簡單使用)的相關資料,需要的朋友可以參考下...

    CodingFire13652021-02-26
  • IOSiOS自定義UICollectionViewFlowLayout實現圖片瀏覽效果

    iOS自定義UICollectionViewFlowLayout實現圖片瀏覽效果

    這篇文章主要介紹了iOS自定義UICollectionViewFlowLayout實現圖片瀏覽效果的相關資料,需要的朋友可以參考下...

    jiangamh8882021-01-11
  • IOSiOS開發之視圖切換

    iOS開發之視圖切換

    在iOS開發中視圖的切換是很頻繁的,獨立的視圖應用在實際開發過程中并不常見,除非你的應用足夠簡單。在iOS開發中常用的視圖切換有三種,今天我們將...

    執著丶執念5282021-01-16
  • IOSiOS中UILabel實現長按復制功能實例代碼

    iOS中UILabel實現長按復制功能實例代碼

    在iOS開發過程中,有時候會用到UILabel展示的內容,那么就設計到點擊UILabel復制它上面展示的內容的功能,也就是Label長按復制功能,下面這篇文章主要給大...

    devilx12792021-04-02
  • IOSiOS開發技巧之狀態欄字體顏色的設置方法

    iOS開發技巧之狀態欄字體顏色的設置方法

    有時候我們需要根據不同的背景修改狀態欄字體的顏色,下面這篇文章主要給大家介紹了關于iOS開發技巧之狀態欄字體顏色的設置方法,文中通過示例代碼...

    夢想家-mxj8922021-05-10
  • IOSiOS中MD5加密算法的介紹和使用

    iOS中MD5加密算法的介紹和使用

    MD5加密是最常用的加密方法之一,是從一段字符串中通過相應特征生成一段32位的數字字母混合碼。對輸入信息生成唯一的128位散列值(32個字符)。這篇文...

    LYSNote5432021-02-04
  • IOSiOS實現控制屏幕常亮不變暗的方法示例

    iOS實現控制屏幕常亮不變暗的方法示例

    最近在工作中遇到了要將iOS屏幕保持常亮的需求,所以下面這篇文章主要給大家介紹了關于利用iOS如何實現控制屏幕常亮不變暗的方法,文中給出了詳細的...

    隨風13332021-04-02
主站蜘蛛池模板: 国产精选在线 | 免费播放欧美毛片 | 成人做爽爽爽爽免费国产软件 | 日韩aⅴ一区二区三区 | 国产精品久久久久免费视频 | 成人免费av在线播放 | 精品免费国产一区二区三区 | www久久久久久| 斗罗破苍穹在线观看免费完整观看 | 草草免费视频 | 成人做爰高潮片免费视频韩国 | 青草av.久久免费一区 | 欧美a视频 | 国产亚洲黑人性受xxxx精品 | 中文字幕精品亚洲 | av手机在线电影 | 97zyz成人免费视频 | 国产高清美女一级毛片 | 黄色小视频免费在线观看 | 国产色片在线观看 | 一区二区久久精品66国产精品 | 欧美 亚洲 视频 | 一日本道久久久精品国产 | 99热草| 国产精品区一区二区三区 | 91精品国产乱码久久久久久久久 | 麻豆小视频在线观看 | 日韩精品羞羞答答 | 7777在线视频免费播放 | 国产午夜精品久久久久久免费视 | 麻豆国产一区 | 亚洲国产色婷婷 | 成人毛片免费视频 | 亚洲男人天堂 | 色人阁导航 | 爽毛片 | 超碰人人做人人爱 | 久久99国产精品免费网站 | 国内精品久久久久影院不卡 | 12av毛片| 性片久久 |