@@ -10,65 +10,147 @@ import Foundation
10
10
open class SKANRMonitor : NSObject {
11
11
12
12
@objc public static let sharedInstance = SKANRMonitor ( )
13
- /// 单次耗时较长的卡顿阈值: 默认值为500ms ,单位:毫秒
14
- @objc public var singleTime : Int = 500
13
+ /// 单次耗时较长的卡顿阈值: 默认值为300ms ,单位:毫秒
14
+ @objc public var singleTime : Int = 300
15
15
16
16
fileprivate var observer : CFRunLoopObserver ?
17
17
18
18
fileprivate var activity : CFRunLoopActivity ?
19
19
20
+ fileprivate var lock = os_unfair_lock ( )
21
+
20
22
fileprivate var semaphore : DispatchSemaphore ?
21
23
22
24
fileprivate var underObserving : Bool = false
23
25
/// 卡顿次数记录
24
26
fileprivate var count : Int = 0
25
27
26
- fileprivate var pendingEntitys : [ SKBacktraceEntity ] = [ ]
28
+ fileprivate var pendingEntities : [ SKBacktraceEntity ] = [ ]
27
29
28
30
fileprivate var pendingEntityDict : [ String : SKBacktraceEntity ] = [ : ]
29
31
30
- @objc public func start( ) {
32
+ public typealias MonitorCallback = ( _ curEntity: SKBacktraceEntity , _ allEntities: [ SKBacktraceEntity ] ) -> Void
33
+
34
+ fileprivate var callback : MonitorCallback ?
35
+
36
+ fileprivate var filePath : String ? {
37
+ get {
38
+ let path = NSSearchPathForDirectoriesInDomains ( . cachesDirectory, . userDomainMask, true ) . first as NSString ?
39
+ if let filePath = path? . appendingPathComponent ( " wd_apm_anr.archive " ) {
40
+ return filePath
41
+ }
42
+ return nil
43
+ }
44
+ }
45
+
46
+ public override init ( ) {
47
+ super. init ( )
48
+ readDataFromDisk ( )
49
+ }
50
+
51
+ /// 开启监测
52
+ @objc public class func start( ) {
53
+ SKANRMonitor . sharedInstance. start ( )
54
+ }
55
+ /// 停止监测
56
+ @objc public class func stop( ) {
57
+ SKANRMonitor . sharedInstance. stop ( )
58
+ }
59
+
60
+ /// 监测到一个卡顿回调
61
+ public class func monitorCallback( _ callback: @escaping MonitorCallback ) {
62
+ SKANRMonitor . sharedInstance. callback = callback
63
+ }
64
+
65
+ /// 获取卡顿数据
66
+ public class func getPendingEntities( ) -> [ SKBacktraceEntity ] {
67
+ return SKANRMonitor . sharedInstance. pendingEntities
68
+ }
69
+
70
+ /// 清理卡顿数据
71
+ public class func clearPendingEntities( ) {
72
+ SKANRMonitor . sharedInstance. _clearEntities ( )
73
+ }
74
+
75
+ public func _clearEntities( ) {
76
+ os_unfair_lock_lock ( & lock)
77
+ pendingEntities. removeAll ( )
78
+ pendingEntityDict. removeAll ( )
79
+ if let filePath = filePath {
80
+ if FileManager . default. fileExists ( atPath: filePath) {
81
+ do {
82
+ try FileManager . default. removeItem ( atPath: filePath)
83
+ } catch {
84
+ print ( " SKANRMonitor remove anr data from disk error = \( error. localizedDescription) " )
85
+ }
86
+ }
87
+ }
88
+ os_unfair_lock_unlock ( & lock)
89
+ }
90
+
91
+ private func start( ) {
31
92
if nil == self . observer {
32
93
underObserving = true
33
94
semaphore = DispatchSemaphore ( value: 1 )
34
95
var context = CFRunLoopObserverContext ( version: 0 , info: nil , retain: nil , release: nil , copyDescription: nil )
35
96
self . observer = CFRunLoopObserverCreate ( kCFAllocatorDefault, CFRunLoopActivity . allActivities. rawValue, true , 0 , observerCallBack ( ) , & context)
36
97
CFRunLoopAddObserver ( CFRunLoopGetMain ( ) , observer, CFRunLoopMode . commonModes)
37
98
38
- Thread . detachNewThread {
39
- while ( self . underObserving) {
40
- if let activity = self . activity, let semaphore = self . semaphore {
41
- SKANRMonitor . sharedInstance. logActivity ( activity)
42
- let result = semaphore. wait ( timeout: DispatchTime . now ( ) + . milliseconds( self . singleTime) )
99
+ Thread . detachNewThread { [ self ] in
100
+ while ( underObserving) {
101
+ if let activity = activity, let semaphore = semaphore {
102
+ let result = semaphore. wait ( timeout: DispatchTime . now ( ) + . milliseconds( singleTime) )
43
103
if result == . timedOut {
44
- if self . observer == nil {
104
+ if observer == nil {
45
105
return
46
106
}
47
107
if activity == . beforeSources || activity == . afterWaiting {
48
- print ( " 监测到卡顿 " )
49
108
let entity = SKBackTrace . backTraceInfoEntity ( of: Thread . main)
50
- let key = " \( entity. validAddress) _ \( entity. validFunction) "
51
- if !self . pendingEntityDict. keys. contains ( key) {
52
- self . pendingEntityDict. updateValue ( entity, forKey: key)
53
- self . pendingEntitys. append ( entity)
54
- print ( entity. threadId)
55
- print ( entity. validAddress)
56
- print ( entity. validFunction)
57
- print ( entity. traceContent)
58
- } else {
59
- print ( " 相同的卡顿只记录一次 " )
60
- }
109
+ handleEntity ( entity)
61
110
}
62
111
} else {
63
- self . count = 0
112
+ count = 0
64
113
}
65
114
}
66
115
}
67
116
}
68
117
}
69
118
}
70
119
71
- @objc public func stop( ) {
120
+ private func handleEntity( _ entity: SKBacktraceEntity ) {
121
+ let key = " \( entity. validAddress) _ \( entity. validFunction) "
122
+ if !self . pendingEntityDict. keys. contains ( key) {
123
+ os_unfair_lock_lock ( & lock)
124
+ // limit cache count 50
125
+ if ( pendingEntities. count >= 50 ) {
126
+ let removeEntity = pendingEntities. removeLast ( )
127
+ let removeKey = " \( removeEntity. validAddress) _ \( removeEntity. validFunction) "
128
+ self . pendingEntityDict. removeValue ( forKey: removeKey)
129
+ }
130
+ self . pendingEntityDict. updateValue ( entity, forKey: key)
131
+ self . pendingEntities. insert ( entity, at: 0 )
132
+ os_unfair_lock_unlock ( & lock)
133
+
134
+ if let filePath = filePath {
135
+ do {
136
+ print ( " SKANRMonitor write data to filePath = \( filePath) " )
137
+ let data = try PropertyListEncoder ( ) . encode ( self . pendingEntities)
138
+ try data. write ( to: URL ( fileURLWithPath: filePath) )
139
+ }
140
+ catch {
141
+ print ( " SKANRMonitor write data to filePath error = \( error. localizedDescription) " )
142
+ }
143
+ }
144
+
145
+ if let callback = self . callback {
146
+ callback ( entity, self . pendingEntities)
147
+ }
148
+ } else {
149
+ print ( " SKANRMonitor 相同的卡顿只记录一次 " )
150
+ }
151
+ }
152
+
153
+ private func stop( ) {
72
154
if nil != self . observer {
73
155
underObserving = false
74
156
CFRunLoopRemoveObserver ( CFRunLoopGetMain ( ) , self . observer, CFRunLoopMode . commonModes)
@@ -79,13 +161,31 @@ open class SKANRMonitor: NSObject{
79
161
fileprivate func observerCallBack( ) -> CFRunLoopObserverCallBack {
80
162
return { ( observer, activity, pointer) in
81
163
SKANRMonitor . sharedInstance. activity = activity
82
- print ( " 即将进入RunLoop " )
83
164
if let semaphore = SKANRMonitor . sharedInstance. semaphore {
84
165
semaphore. signal ( )
85
166
}
86
167
}
87
168
}
88
169
170
+ private func readDataFromDisk( ) {
171
+ if let filePath = filePath, FileManager . default. fileExists ( atPath: filePath) {
172
+ do {
173
+ let data = try Data ( contentsOf: URL ( fileURLWithPath: filePath) )
174
+ let entities = try PropertyListDecoder ( ) . decode ( [ SKBacktraceEntity ] . self, from: data)
175
+ os_unfair_lock_lock ( & lock)
176
+ pendingEntities. append ( contentsOf: entities)
177
+ entities. forEach { e in
178
+ let key = " \( e. validAddress) _ \( e. validFunction) "
179
+ pendingEntityDict. updateValue ( e, forKey: key)
180
+ }
181
+ os_unfair_lock_unlock ( & lock)
182
+ }
183
+ catch {
184
+ print ( " SKANRMonitor read data from disk error = \( error. localizedDescription) " )
185
+ }
186
+ }
187
+ }
188
+
89
189
fileprivate func logActivity( _ activity: CFRunLoopActivity ) {
90
190
switch activity {
91
191
case . entry:
0 commit comments