Android webview交互性能監(jiān)測指標獲取方法

業(yè)界衡量移動web app交互性能的優(yōu)劣主要是通過監(jiān)測webview渲染頁面時白屏時間墨技,DOM樹構(gòu)建時間惩阶,整頁時間和首屏時間這三個指標來完成的,那么這四個指標分別的意義是什么呢扣汪?我們從w3c提供的navigation Timing中看到交互性能指的是Processing和onLoad這兩部分的時間断楷。

timing-overview.png

在瀏覽器交互階段(Processing和onLoad時間段)瀏覽器接收服務器返回的基礎頁數(shù)據(jù)后,瀏覽器需要對HTML這個單純的文本內(nèi)容進行解析崭别,從文本中構(gòu)建出一個內(nèi)部數(shù)據(jù)結(jié)構(gòu)冬筒,叫做DOM樹(DOM tree),用于組織將要繪制在屏幕上的內(nèi)容茅主。從HTML也能得到外聯(lián)或內(nèi)聯(lián)的CSS腳本和JavaScript腳本舞痰,當然還有媒體文件,比如圖片诀姚、視頻响牛、聲音,這些都需要再次發(fā)起網(wǎng)絡請求下載赫段。CSS文本內(nèi)容中的規(guī)則同樣會被構(gòu)建成一個內(nèi)部數(shù)據(jù)結(jié)構(gòu)呀打,叫做CSS樹(CSS tree),來決定DOM樹的節(jié)點在屏幕上的布局瑞佩、顏色聚磺、狀態(tài)效果。JavaScript腳本被觸發(fā)執(zhí)行后炬丸,除了計算業(yè)務瘫寝,往往還需要操作DOM樹,就是所謂的DOM API稠炬。

webkitflow.png
  • 白屏時間
    指瀏覽器開始顯示內(nèi)容的時間焕阿。但是在傳統(tǒng)的采集方式里,是在HTML的head標簽結(jié)尾里記錄時間戳首启,來計算白屏時間暮屡。在這個時刻,瀏覽器開始解析body標簽內(nèi)的內(nèi)容毅桃。而現(xiàn)代瀏覽器不會等待CSS樹(所有CSS文件下載和解析完成)和DOM樹(整個body標簽解析完成)構(gòu)建完成才開始繪制褒纲,而是馬上開始顯示中間結(jié)果。所以經(jīng)常在低網(wǎng)速的環(huán)境中钥飞,觀察到頁面由上至下緩慢顯示完莺掠,或者先顯示文本內(nèi)容后再重繪成帶有格式的頁面內(nèi)容。在android中我們通過使用webview.WebChromeClientonReceivedTitle事件來近似獲得白屏時間读宙。

  • DOM樹構(gòu)建時間
    指瀏覽器開始對基礎頁文本內(nèi)容進行解析到從文本中構(gòu)建出一個內(nèi)部數(shù)據(jù)結(jié)構(gòu)(DOM樹)的時間彻秆,這個事件是從HTML中的onLoad的延伸而來的,當一個頁面完成加載時,初始化腳本的方法是使用load事件唇兑,但這個類函數(shù)的缺點是僅在所有資源都完全加載后才被觸發(fā)酒朵,這有時會導致比較嚴重的延遲,開發(fā)人員隨后創(chuàng)建了domready事件扎附,它在DOM加載之后及資源加載之前被觸發(fā)蔫耽。domready被眾多JavaScript庫所采用,它在本地瀏覽器中以DOMContentLoaded事件的形式被使用。在android中我們通過注入js代碼到webview中的方式來實現(xiàn)帕棉;具體實現(xiàn)上针肥,在WebChromeClientonReceivedTitle事件被觸發(fā)時注入我們的js代碼,然后通過WebChromeClientonJsPrompt事件來獲取domc(window.DOMContentLoaded事件)時間香伴。

@Override
public void onReceivedTitle (WebView view, String title) {
    view.loadUrl("javascript:" + 
    "window.addEventListener('DOMContentLoaded', function() {" +
        "prompt('domc:' + new Date().getTime());" + 
    );
}

@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult r) {
    Log.i(UAQ_WEB_ACTIVITY, "**** Blocking Javascript Prompt :" + message);
    if(message != null){
        if(!preCacheRun){
            String[] strs = message.split(":");
            if(2 == strs.length){   
                if("domc".equals(strs[0])){
                    result.getCurrentRun().setDocComplete(Long.valueOf(strs[1].trim()));
                }
            }
        }   
    }
    r.confirm(defaultValue);
    return true;
}
  • 首屏時間
    指從網(wǎng)頁應用的角度定義的指標,在Navigation Timing或者瀏覽器實現(xiàn)中并沒有相關指標值具则。首屏時間即纲,是指用戶看到第一屏,即整個網(wǎng)頁頂部大小為當前窗口的區(qū)域博肋,顯示完整的時間低斋。常用的方法有,頁面標簽標記法匪凡、圖像相似度比較法和首屏高度內(nèi)圖片加載法膊畴;
  1. 頁面標簽標記法,在HTML文檔中對應首屏內(nèi)容的標簽結(jié)束位置病游,使用內(nèi)聯(lián)的JavaScript代碼記錄當前時間戳唇跨,比較局限;
  2. 圖像相似度比較法衬衬,通過比較連續(xù)截屏圖像的像素點變化趨勢確定首屏時間买猖,最為科學和直觀的方式,但是比較消耗本地設備的運行資源滋尉;
  3. 首屏高度內(nèi)圖片加載法玉控,通過尋找首屏區(qū)域內(nèi)的所有圖片,計算它們加載完的時間去得到首屏時間狮惜,這樣比較符合網(wǎng)頁的實際體驗并且比較節(jié)省設備運行資源高诺;
    具體實現(xiàn)上我采用的是最后一種,即“首屏高度內(nèi)圖片加載法”碾篡;因為通常需要考慮首屏時間的頁面虱而,都是因為在首屏位置內(nèi)放入了較多的圖片資源。現(xiàn)代瀏覽器處理圖片資源時是異步的耽梅,會先將圖片長寬應用于頁面排版薛窥,然后隨著收到圖片數(shù)據(jù)由上至下繪制顯示的。并且瀏覽器對每個頁面的TCP連接數(shù)限制,使得并不是所有圖片都能立刻開始下載和顯示诅迷。因此我們在DOM樹構(gòu)建完成后即可遍歷獲得所有在設備屏幕高度內(nèi)的所有圖片資源標簽佩番,在所有圖片標簽中添加document.onload事件,在整頁加載完成(window.onLoad事件發(fā)生)時遍歷圖片標簽并獲得之前注冊的document.onload事件時間的最大值罢杉,該最大值減去navigationStart即認為近似的首屏時間趟畏。在android中我們通過注入js代碼到webview中的方式來實現(xiàn);具體實現(xiàn)上滩租,在WebChromeClient的onReceivedTitle事件被觸發(fā)時注入我們的js代碼赋秀,然后通過WebChromeClientonJsPrompt事件來獲取firstscreen時間。

js部分計算首屏時間的邏輯代碼:

function first_screen () {
    var imgs = document.getElementsByTagName("img"), fs = +new Date;
    var fsItems = [], that = this;
    function getOffsetTop(elem) {
        var top = 0;
        top = window.pageYOffset ? window.pageYOffset : document.documentElement.scrollTop;
        try{
            top += elem.getBoundingClientRect().top;    
        }catch(e){

        }finally{
            return top;
        }

    }
    var loadEvent = function() {
        //gif避免
        if (this.removeEventListener) {
            this.removeEventListener("load", loadEvent, false);
        }
        fsItems.push({
            img : this,
            time : +new Date
        });
    }
    for (var i = 0; i < imgs.length; i++) {
        (function() {
            var img = imgs[i];

            if (img.addEventListener) {

                !img.complete && img.addEventListener("load", loadEvent, false);
            } else if (img.attachEvent) {

                img.attachEvent("onreadystatechange", function() {
                    if (img.readyState == "complete") {
                        loadEvent.call(img, loadEvent);
                    }

                });
            }

        })();
    }
    function firstscreen_time() {
        var sh = document.documentElement.clientHeight;
        for (var i = 0; i < fsItems.length; i++) {
            var item = fsItems[i], img = item['img'], time = item['time'], top = getOffsetTop(img);
            if (top > 0 && top < sh) {
                fs = time > fs ? time : fs;
            }
        }
        return fs;
    }      
    window.addEventListener('load', function() {
                            prompt('firstscreen:' + firstscreen_time());
                        });
}

webview的注入代碼:

private void registerOnLoadHandler(WebView view) {
    String jscontent = getJavaScriptAsString();
    view.loadUrl("javascript:" + jscontent + 
      "window.addEventListener('DOMContentLoaded', function() {" +
          "first_screen();
       });"
    );
}

@Override
public void onReceivedTitle (WebView view, String title) {
    registerOnLoadHandler(view);
}

@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult r) {
    Log.i(UAQ_WEB_ACTIVITY, "**** Blocking Javascript Prompt :" + message);
    if(message != null){
        if(!preCacheRun){
            String[] strs = message.split(":");
            if(2 == strs.length){   
                if("firstscreen".equals(strs[0])){
                    result.getCurrentRun().setFirstScreen(Long.valueOf(strs[1].trim()));
                }
            }
        }   
    }
    r.confirm(defaultValue);
    return true;
}
  • 整頁時間
    指頁面完成整個加載過程的時刻律想。從Navigation Timing API上采集猎莲,就是loadEventEnd減去navigationStart。在傳統(tǒng)采集方法中技即,會使用window對象的onload事件來記錄時間戳著洼,它表示瀏覽器認定該頁面已經(jīng)載入完全了。android中我們通過注入js代碼到webview中的方式來實現(xiàn)而叼;具體實現(xiàn)上身笤,在WebChromeClient的onReceivedTitle事件被觸發(fā)時注入我們的js代碼,然后通過WebChromeClientonJsPrompt事件來獲取load(window.onLoad事件)時間葵陵。
@Override
public void onReceivedTitle (WebView view, String title) {
    view.loadUrl("javascript:" + 
    "window.addEventListener('load', function() {" +
        "prompt('load:' + new Date().getTime());" + 
    );
}
 
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult r) {
    Log.i(UAQ_WEB_ACTIVITY, "**** Blocking Javascript Prompt :" + message);
    if(message != null){
        if(!preCacheRun){
            String[] strs = message.split(":");
            if(2 == strs.length){   
                if("load".equals(strs[0])){
                    result.getCurrentRun().setFullyLoaded(Long.valueOf(strs[1].trim()));
                }
            }
        }   
    }
    r.confirm(defaultValue);
    return true;
}

歡迎關注我的個人訂閱號

個人訂閱號
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末液荸,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子脱篙,更是在濱河造成了極大的恐慌娇钱,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涡尘,死亡現(xiàn)場離奇詭異忍弛,居然都是意外死亡,警方通過查閱死者的電腦和手機考抄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門细疚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人川梅,你說我怎么就攤上這事疯兼。” “怎么了贫途?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵吧彪,是天一觀的道長。 經(jīng)常有香客問我丢早,道長姨裸,這世上最難降的妖魔是什么秧倾? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮傀缩,結(jié)果婚禮上那先,老公的妹妹穿的比我還像新娘。我一直安慰自己赡艰,他們只是感情好售淡,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著慷垮,像睡著了一般揖闸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上料身,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天汤纸,我揣著相機與錄音,去河邊找鬼惯驼。 笑死蹲嚣,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的祟牲。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼抖部,長吁一口氣:“原來是場噩夢啊……” “哼说贝!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起慎颗,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤乡恕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后俯萎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體傲宜,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年夫啊,在試婚紗的時候發(fā)現(xiàn)自己被綠了函卒。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡撇眯,死狀恐怖报嵌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情熊榛,我是刑警寧澤锚国,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站玄坦,受9級特大地震影響血筑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一豺总、第九天 我趴在偏房一處隱蔽的房頂上張望车伞。 院中可真熱鬧,春花似錦园欣、人聲如沸帖世。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽日矫。三九已至,卻和暖如春绑榴,著一層夾襖步出監(jiān)牢的瞬間哪轿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工翔怎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留窃诉,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓赤套,卻偏偏與公主長得像飘痛,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子容握,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容