zoukankan      html  css  js  c++  java
  • iOS-监听原生H5性能数据window.performance

    WebKit-WKWebView

    iOS8开始苹果推荐使用WKWebview作为H5开发的核心组件,以替代原有的UIWebView,以下是webkit基本介绍介绍:
    介绍博客 Webkit

    H5 - window.performance

    window.performance 是W3C性能小组引入的新的API,主流浏览器都支持
    iOS可以获取的字段可以通过xcode官方文档查看:(WebKit JS只有做Safari编程才能使用,所以只能查看)

    W3C的Performance的时间前后顺序如下:

    属性说明:

    navigationStart:浏览器处理当前网页的启动时间
    fetchStart:浏览器发起http请求读取文档的毫秒时间戳。
    domainLookupStart:域名查询开始时的时间戳。
    domainLookupEnd:域名查询结束时的时间戳。
    connectStart:http请求开始向服务器发送的时间戳。
    connectEnd:浏览器与服务器连接建立(握手和认证过程结束)的毫秒时间戳。
    requestStart:浏览器向服务器发出http请求时的时间戳。或者开始读取本地缓存时。
    responseStart:浏览器从服务器(或读取本地缓存)收到第一个字节时的时间戳。
    responseEnd:浏览器从服务器收到最后一个字节时的毫秒时间戳。
    domLoading:浏览器开始解析网页DOM结构的时间。
    domInteractive:网页dom树创建完成,开始加载内嵌资源的时间。
    domContentLoadedEventStart:网页DOMContentLoaded事件发生时的时间戳。
    domContentLoadedEventEnd:网页所有需要执行的脚本执行完成时的时间,domReady的时间。
    domComplete:网页dom结构生成时的时间戳。
    loadEventStart:当前网页load事件的回调函数开始执行的时间戳。
    loadEventEnd:当前网页load事件的回调函数结束运行时的时间戳。

    通过代码获取数据

    直接上代码:
    通过在wkwebview的didFinish方法中使用自定义的jsTiming方法:

        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            webView.jsTiming()
        }
    

    jsTiming()的源码

    import WebKit
    
    protocol MyWebViewTimingProtocal {
        func jsTiming()
    }
    
    extension WKWebView : MyWebViewTimingProtocal{
        
        /// 获取WebView的JS的性能数据
        func jsTiming() {
            let webView : WKWebView? = self
            if #available(iOS 10.0, *) {
                webView?.evaluateJavaScript("JSON.stringify(window.performance.timing.toJSON())") { (timingStr, error) in
                    if error == nil && timingStr != nil {
                        JSTimingTool.parseJSTimingString(timingStr as! String)
                    } else {
                        print("WKWebView Load Performance JS Faild!")
                    }
                }
            } else {
                let jsFuncStr = "function flatten(obj) {"
                    + "var ret = {}; "
                    + "for (var i in obj) { "
                    + "ret[i] = obj[i];"
                    + "}"
                    + "return ret;}"
                webView?.evaluateJavaScript(jsFuncStr) { (resultStr, error) in
                    if error == nil && resultStr != nil {
                        webView?.evaluateJavaScript("JSON.stringify(flatten(window.performance.timing))", completionHandler: { (timingStr, error) in
                            if error == nil && timingStr != nil {
                                JSTimingTool.parseJSTimingString(timingStr as! String)
                            } else {
                                print("WKWebView Load Performance JS Faild!")
                            }
                        })
                    } else {
                        print("WKWebView evaluateJavaScript Faild!")
                    }
                }
            }
        }
    }
    
    
    /// 解析window.performance的工具类
    private class JSTimingTool {
        
        
        /// 解析入口方法
        ///
        /// - Parameter timingStr:window.performance.timing字符串
        static func parseJSTimingString(_ timingStr: String) {
            if let dict = JSTimingTool.dictionaryFromString(timingStr) {
                JSTimingTool.parseJSTimingDictionary(dict)
            } else {
                print("Performance JS trans to Dictionary Faild!")
            }
        }
        
        
        /// 字符串转字典
        ///
        /// - Parameter str: 需要转换的字符串
        /// - Returns: 转换完成的字典
        static func dictionaryFromString(_ str: String) -> [String : Any]?{
            let data = str.data(using: String.Encoding.utf8)
            if let dict = try? JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as? [String : Any] {
                return dict
            }
            return nil
        }
        
        
        /// 分析性能数据字典
        ///
        /// - Parameter dict: window.performance.timing字典
        static func parseJSTimingDictionary(_ dict: Dictionary<String, Any>) {
            
            
            
            print("(String(describing: dict))")
            
            
            let domainLookupStart = dict["domainLookupStart"] as! CLongLong
            let domainLookupEnd = dict["domainLookupEnd"] as! CLongLong
            let connectStart = dict["connectStart"] as! CLongLong
            let connectEnd = dict["connectEnd"] as! CLongLong
            let responseStart = dict["responseStart"] as! CLongLong
            let responseEnd = dict["responseEnd"] as! CLongLong
            let domInteractive = dict["domInteractive"] as! CLongLong
            let domComplete = dict["domComplete"] as! CLongLong
            let fetchStart = dict["fetchStart"] as! CLongLong
            let domLoading = dict["domLoading"] as! CLongLong
            let domContentLoadedEventEnd = dict["domContentLoadedEventEnd"] as! CLongLong
            let loadEventStart = dict["loadEventStart"] as! CLongLong
            let loadEventEnd = dict["loadEventEnd"] as! CLongLong
            
            let dnstiming = domainLookupEnd - domainLookupStart //DNS查询耗时
            let tcptiming = connectEnd - connectStart //TCP链接耗时
            let requesttiming = responseEnd - responseStart //request请求耗时
            let domtiming = domComplete - domInteractive //解析dom树耗时
            let wheetScreentiming = domLoading - fetchStart //白屏时间
            let domreadytiming = domContentLoadedEventEnd - fetchStart //dom ready时间
            let domloadtiming = loadEventEnd - loadEventStart //dom load时间
            let onloadtiming = loadEventEnd - fetchStart //onload总时间
            
            print("dnstiming:(dnstiming)
    tcptiming:(tcptiming)
    requesttiming:(requesttiming)
    domtiming:(domtiming)
    wheetScreentiming:(wheetScreentiming)
    domreadytiming:(domreadytiming)
    domloadtiming:(domloadtiming)
    onloadtiming:(onloadtiming)
    ")
        }
    }
    
    

    示例

    以http://www.baidu.com为例获取到的数据

    ["navigationStart": 1563415353543, "connectStart": 1563415353858, "redirectStart": 0, 
    "unloadEventEnd": 0, "loadEventStart": 1563415358406,
     "responseEnd": 1563415354271, "domainLookupEnd": 1563415353857, "redirectEnd": 0, 
    "connectEnd": 1563415353921, "secureConnectionStart": 1563415353888, 
    "unloadEventStart": 0, "domContentLoadedEventStart": 1563415354271, 
    "responseStart": 1563415354218, "loadEventEnd": 1563415358406,
     "domInteractive": 1563415354271, "requestStart": 1563415353921,
     "domComplete": 1563415358406, "domLoading": 1563415354231, "fetchStart": 1563415353852, 
    "domContentLoadedEventEnd": 1563415354271, "domainLookupStart": 1563415353855]
    dnstiming:2
    tcptiming:63
    requesttiming:53
    domtiming:4135
    wheetScreentiming:379
    domreadytiming:419
    domloadtiming:0
    onloadtiming:4554
    

    全局监听

    如果需要针对所有页面都监控,可以使用runtime机制,监听webview的didFinish方法,通过AOP方式hook到对应的自定义didFinish方法,然后在自定义的didFinish方法中调用jsTiming方法

    局限性

    window.performance只能在webview的didFinish方法中监听一次,如果H5页面内部做跳转,是无法监听到的,所以更适合做首次加载的性能分析,如果有二级H5页面的的性能监听需求,还是需要前端开发同学进行协助。

  • 相关阅读:
    [转]Nginx配置信息详解
    [转]浅谈Nginx负载均衡和F5的区别
    [转]MySQL中datetime和timestamp的区别及使用
    理解MyCat分库分表
    理解秒杀系统
    [转]设计模式之桥接模式
    [转]MySQL查询语句执行过程详解
    两步完美解决 androud 模拟器太慢的问题
    android hook 框架 xposed 如何实现挂钩
    android hook 框架 xposed 如何实现注入
  • 原文地址:https://www.cnblogs.com/anywherego/p/11236233.html
Copyright © 2011-2022 走看看