@@ -10,65 +10,147 @@ import Foundation
1010open class SKANRMonitor : NSObject {
1111
1212 @objc public static let sharedInstance = SKANRMonitor ( )
13- /// 单次耗时较长的卡顿阈值: 默认值为500ms ,单位:毫秒
14- @objc public var singleTime : Int = 500
13+ /// 单次耗时较长的卡顿阈值: 默认值为300ms ,单位:毫秒
14+ @objc public var singleTime : Int = 300
1515
1616 fileprivate var observer : CFRunLoopObserver ?
1717
1818 fileprivate var activity : CFRunLoopActivity ?
1919
20+ fileprivate var lock = os_unfair_lock ( )
21+
2022 fileprivate var semaphore : DispatchSemaphore ?
2123
2224 fileprivate var underObserving : Bool = false
2325 /// 卡顿次数记录
2426 fileprivate var count : Int = 0
2527
26- fileprivate var pendingEntitys : [ SKBacktraceEntity ] = [ ]
28+ fileprivate var pendingEntities : [ SKBacktraceEntity ] = [ ]
2729
2830 fileprivate var pendingEntityDict : [ String : SKBacktraceEntity ] = [ : ]
2931
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( ) {
3192 if nil == self . observer {
3293 underObserving = true
3394 semaphore = DispatchSemaphore ( value: 1 )
3495 var context = CFRunLoopObserverContext ( version: 0 , info: nil , retain: nil , release: nil , copyDescription: nil )
3596 self . observer = CFRunLoopObserverCreate ( kCFAllocatorDefault, CFRunLoopActivity . allActivities. rawValue, true , 0 , observerCallBack ( ) , & context)
3697 CFRunLoopAddObserver ( CFRunLoopGetMain ( ) , observer, CFRunLoopMode . commonModes)
3798
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) )
43103 if result == . timedOut {
44- if self . observer == nil {
104+ if observer == nil {
45105 return
46106 }
47107 if activity == . beforeSources || activity == . afterWaiting {
48- print ( " 监测到卡顿 " )
49108 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)
61110 }
62111 } else {
63- self . count = 0
112+ count = 0
64113 }
65114 }
66115 }
67116 }
68117 }
69118 }
70119
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( ) {
72154 if nil != self . observer {
73155 underObserving = false
74156 CFRunLoopRemoveObserver ( CFRunLoopGetMain ( ) , self . observer, CFRunLoopMode . commonModes)
@@ -79,13 +161,31 @@ open class SKANRMonitor: NSObject{
79161 fileprivate func observerCallBack( ) -> CFRunLoopObserverCallBack {
80162 return { ( observer, activity, pointer) in
81163 SKANRMonitor . sharedInstance. activity = activity
82- print ( " 即将进入RunLoop " )
83164 if let semaphore = SKANRMonitor . sharedInstance. semaphore {
84165 semaphore. signal ( )
85166 }
86167 }
87168 }
88169
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+
89189 fileprivate func logActivity( _ activity: CFRunLoopActivity ) {
90190 switch activity {
91191 case . entry:
0 commit comments