国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

iframe 與 webview ,記錄一次使用 jsBridge 遇到的 bug 解決過程

Jensen / 1304人閱讀

摘要:通訊狀態(tài)的改變次數(shù)被調(diào)用次數(shù)預計驗證證實完畢。既然是因為調(diào)用間隔太短,所以就采用了切圖仔常用的節(jié)流方案。隨便說說縱觀整個通訊過程,其實就是一個網(wǎng)絡協(xié)議的縮影。

前提-出現(xiàn)場景

    使用機型為 Android 9,API 28

    使用的 jsBridge 為 link

bug 描述

在頁面加載前后如果連續(xù)多次調(diào)用原生的方法,會遇到回調(diào)參數(shù)未被調(diào)用的情況。

// 多次調(diào)用如下函數(shù), 部分 callback 將不會被調(diào)用
window.WebViewJavascriptBridge.callHandler(api, parameter, callback);

bug 的穩(wěn)定復現(xiàn)方式

在頁面加載時通過jsBridge和原生進行10次以上的數(shù)據(jù)交換。

出現(xiàn)的原因 查詢所得

在多篇文章(1,2)中看到是因為 jsBridge 使用 iframe 的 src 變化 和 shouldOverrideUrlLoading 來實現(xiàn)原生與js的溝通導致的問題,而刷新 iframe 并不能保證 shouldOverrideUrlLoading 會被調(diào)用

于是我們以此為假設進行驗證

驗證1: jsBridge 是否使用 iframe.src 的變化來進行js與原生的通訊

我們可以直接看看進行一次完整的通訊的調(diào)用過程。

 //依據(jù)調(diào)用鏈 
 window.WebViewJavascriptBridge.callHandler(api, parameter, callback);
 
 function callHandler(handlerName, data, responseCallback) {
   _doSend(
     {
       handlerName: handlerName,
       data: data
     },
     responseCallback
   );
 }
 
 function _doSend(message, responseCallback) {
   if (responseCallback) {
     var callbackId = "cb_" + uniqueId++ + "_" + new Date().getTime();
     responseCallbacks[callbackId] = responseCallback;
     message.callbackId = callbackId;
   }
 
   sendMessageQueue.push(message);
   //改變html內(nèi)的iframe的src
   messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + "://" + QUEUE_HAS_MESSAGE;
 }
 
  // 此時步驟轉到原生層面
// shouldOverrideUrlLoading 將在 iframe.src 改變時被調(diào)用
public boolean shouldOverrideUrlLoading(WebView view, String urlString) {
    super.shouldOverrideUrlLoading(view, urlString);
    if (PhoneUtil.INSTANCE.startTelActivity(getActivity(), urlString)) return true;
    if (mWebViewHelper.shouldOverrideUrlLoading(view, urlString)) return true;
    return false;
}

//父類的 shouldOverrideUrlLoading 
public boolean shouldOverrideUrlLoading(WebView view, String url) {
    try {
        url = URLDecoder.decode(url, "UTF-8");
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }

  	// 根據(jù) url 的內(nèi)容,區(qū)分是哪種類型的操作
  	// 事實上 只有 YY_RETURN_DATA 和 YY_OVERRIDE_SCHEMA 兩種
  	// YY_RETURN_DATA 根據(jù) url 的 參數(shù),返回數(shù)據(jù),即原生備好數(shù)據(jù)后調(diào)用 js 原生方法(js 的回調(diào)函數(shù))
  	// YY_OVERRIDE_SCHEMA 則注入腳本到 webview 調(diào)用 js 原生方法 _fetchQueue
    if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) { 
        webView.handlerReturnData(url);
        return true;
    } else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { //
        webView.flushMessageQueue();
        return true;
    } else {
        return super.shouldOverrideUrlLoading(view, url);
    }
}

//通訊結束 

// YY_OVERRIDE_SCHEMA 類型通訊所調(diào)用的原生方法
function _fetchQueue() {
  var messageQueueString = JSON.stringify(sendMessageQueue);
  console.warn(++count, "-", messageQueueString);
  sendMessageQueue = [];
  //android can"t read directly the return data,
  //so we can reload iframe src to communicate with java
  messagingIframe.src =
    CUSTOM_PROTOCOL_SCHEME +
    "://return/_fetchQueue/" +
    encodeURIComponent(messageQueueString);
}

從源碼可以看出,一個完整的通訊過程,將改變兩次 src,也就是說 shouldOverrideUrlLoading 會被調(diào)用兩次(預計)。@Q說來 jsBridge 設計也奇怪,為什么不設計成一次 src,完成一次通訊

驗證1證實完畢。

驗證2:iframe 改變 src 是否與 shouldOverrideUrlLoading 調(diào)用次數(shù)一致。

我在 WebViewJavascriptBridge.js 中對 ifram.src 的變化 和 BasewebviewFragment.java 的 shouldOverrideUrlLoading 調(diào)用進行計數(shù),發(fā)現(xiàn)兩邊的次數(shù)確實不一致。

通訊狀態(tài) iframe 的 src 改變次數(shù) shouldOverrideUrlLoading 被調(diào)用次數(shù)
預計 18 18
T 13 9
T 17 14
T 13 6
F 17 18
F 6 3
T 11 8

驗證2 證實完畢。

同時我們也得知,就算二者調(diào)用次數(shù)不一致,也不影響 js 與 native 的通訊,幾次通訊成功的情況二者的次數(shù)都不一致,甚至我們可以初步預測,二者的次數(shù)根本不需要一致就能實現(xiàn)通訊。

@Q 那么通訊成功的充分必要條件是什么呢?

通訊失敗的原因

回顧我們之前所做的驗證1,一個完整的通訊過程,其調(diào)用時序圖如下:

回顧我們最初遇到的問題,多次調(diào)用 callHandler 后,部分 callback 沒有被調(diào)用,導致通訊失敗

根據(jù)流程圖逆行推理, callback 未被調(diào)用 => 表示攜帶該callback 的 respMessage 未被傳遞過來,也就是說 yy://return/ ${resp} 缺失了 => _fetchQueue 傳遞的數(shù)據(jù)有缺失

function _fetchQueue() {
  var messageQueueString = JSON.stringify(sendMessageQueue);  
  
  // ATENTION 這里在將 string 化后立即清空了當前的 messageQueue 
  sendMessageQueue = [];
  
  messagingIframe.src =
    CUSTOM_PROTOCOL_SCHEME +
    "://return/_fetchQueue/" +
    encodeURIComponent(messageQueueString);
}

從 _fetchQueue 的源碼中,發(fā)現(xiàn)在將 message 傳遞后就立馬清空了,實際上這并不準確,因為連續(xù)N次改變 iframe 的 src ,shouldOverrideUrlLoading 的實際調(diào)用次數(shù)為 M(M

上述圖示是一次失敗通訊的日志,可以看到,前6次調(diào)用為 _doSend 的調(diào)用,即改變了 6次 iframe 的 src,但實際上只有兩次生效了,第一次生效的通訊調(diào)用了 _fetchQueue ,傳遞前 6 次的 message 給 native,但是由于清空了 message 隊列,緊跟的第二次 _fetchQueue 執(zhí)行時傳遞空數(shù)組給 native ,又因為兩次 _fetchQueue 的調(diào)用間隔太短,實際上只有第二次 _fetchQueue 的調(diào)用傳遞給了 native ,此時 native 只收到一個 空數(shù)組的 通訊,自然沒有了后續(xù)的操作。

所以我們最初 callHandler 里的 callback,都沒人再調(diào)用了...

解決方法

原因已經(jīng)明了,當前的問題是如何解決。切入點有以下幾個,

    查清為什么多次 iframe.src 變化只調(diào)用更少次數(shù)的 shouldOverrideUrlLoading,并解決...

    修改 _fetchQueue 函數(shù)

    js 在調(diào)用時只能線性調(diào)用

鑒于1的實施難度對我這個切圖仔來說有點大,優(yōu)先考慮后續(xù)兩個解決方法。

修改 _fetchQueue 函數(shù)

    線性調(diào)用 _fetchQueue ,主要代碼如下。

function _fetchQueue() {
    if (sendMessageQueue.length === 0 || fetchingQueueLength > 0) {
        return;
    }

    // 記錄當前等待 native 響應的個數(shù)
    fetchingQueueLength += sendMessageQueue.length;
    
    var messageQueueString = JSON.stringify(sendMessageQueue);
    sendMessageQueue = [];
    //android can"t read directly the return data, so we can reload iframe src to communicate with java
    bizMessagingIframe.src = CUSTOM_PROTOCOL_SCHEME + "://return/_fetchQueue/" + encodeURIComponent(messageQueueString);
}

/* ... */

function _dispatchMessageFromNative(messageJSON) {
    setTimeout(function() {
        var message = JSON.parse(messageJSON);

        fetchingQueueLength--;
        // 如果通訊完畢,清理被阻塞的 message
        if (fetchingQueueLength === 0) {
            // 使用 sto,在當前的通訊結束后再 _fetchQueue 
            setTimeout(function() {
                _fetchQueue();
            });
        }
      
      ...

以私有變量 fetchingQueueLength 記錄等待響應的 message 數(shù)量,但是存在隊首阻塞的問題,甚至因為沒保證所以沒采用。

    既然是因為 _fetchQueue 調(diào)用間隔太短,所以就采用了切圖仔常用的節(jié)流方案。

    var lastCallTime = 0;
    var stoId = null;
    var FETCH_QUEUE = 20;
    
    function _fetchQueue() {
        // 空數(shù)組直接返回 
        if (sendMessageQueue.length === 0) {
          return;
        }
    
        if (new Date().getTime() - lastCallTime < FETCH_QUEUE) {
          if (!stoId) {
            stoId = setTimeout(_fetchQueue, FETCH_QUEUE);
          }
          return;
        }
    
        lastCallTime = new Date().getTime();
        stoId = null;
        var messageQueueString = JSON.stringify(sendMessageQueue);
        sendMessageQueue = [];
        //android can"t read directly the return data, so we can reload iframe src to communicate with java
        bizMessagingIframe.src = CUSTOM_PROTOCOL_SCHEME + "://return/_fetchQueue/" + encodeURIComponent(messageQueueString);
        
    }
    

    這個 20 ms,其實我是有些隨意的定義的,從 200 開始向下試驗,20 是我覺得比較穩(wěn)定一個數(shù)字… 。20 ms 內(nèi)連續(xù)的調(diào)用 _fetchQueue 將只有一次生效,回顧之前通訊流程的同學應該知道 _fetchQueue 的觸發(fā)是依靠 native 的調(diào)用的,所以 _fetchQueue 的觸發(fā)對 _doSend 來說是異步的,所以并不需要一一對應,_doSend 只是往 sendMessageQueue 里添加任務,而 _fetchQueue 只負責將 sendMessageQueue 里的任務清空,只要保證至少有一個 _fetchQueue 晚于 _doSend 執(zhí)行即可。

    但是這里改動 WebViewJavascriptBridge.js 是需要重新發(fā)包的。

修改 js 調(diào)用時的函數(shù)

這個其實有點難處理,因為是在 js 層面,這里解決的點仍然是 2. 中的 _fetchQueue 調(diào)用頻繁的問題,從這個角度切入有點隔山打牛的意味。但是因為改動只在頁面,不依賴原生發(fā)包,所以在某些場景也適用。

這里的思想類似,封裝 callHandler 函數(shù),節(jié)流或者串行均可,當然串行就會有阻塞的可能,節(jié)流,這里的節(jié)流是想讓 _fetchQueue 的調(diào)用節(jié)流,但是 _fetchQueue 的觸發(fā)畢竟是異步,而且掌控在原生代碼那邊,所有其實不太推薦適用這個方案。

隨便說說

縱觀整個通訊過程,其實就是一個網(wǎng)絡協(xié)議的縮影。最開始考慮部分通訊失敗的問題時,想的這是不是就是網(wǎng)絡里的丟包,想想 TCP 怎么解決丟包的,好像是記錄字節(jié)序 + 定時器,但是這里響應體只包含通訊內(nèi)容,光是標記請求就有點麻煩了,再加上定時器...如果要改就是大重構了…算了;后來開始針對 _fetchQueue ,要不就考慮學 HTTP 一來一回吧,但是這樣效率太低了,js 單線程也沒有并發(fā),而且還有隊首阻塞的問題… 后來轉而一想,既然 fetchQueue 間隔短,那我控制間隔不就好了嗎…于是引入了節(jié)流的方案… 變動小代碼簡單易懂…雖然這個 20ms 不太具有事實依據(jù)性。

總的來說解決問題并不難,難得是找到問題的核心,為了這個我甚至找了原生開發(fā)小哥 copy 一份源碼…,好在之前有過 RN 調(diào)試經(jīng)驗… 不至于卡在配置 android studio 上….當然我的方案不是最好的,如果你有更好的方案,歡迎留言。

文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/6895.html

相關文章

  • H5Native交互之JSBridge技術

    摘要:一原理篇下面分別介紹和與的底層交互原理在講解原理之前,首先來了解下的組件,先來看一下蘋果官方的介紹上面的意思是說是一個可加載網(wǎng)頁的對象,它有瀏覽記錄功能,且對加載的網(wǎng)頁內(nèi)容是可編程的。 做過混合開發(fā)的很多人都知道Ionic和PhoneGap之類的框架,這些框架在web基礎上包了一層Native,然后通過Bridge技術使得js可以調(diào)用視頻、位置、音頻等功能。本文就是介紹這層Bridge...

    zacklee 評論0 收藏0
  • (轉)iOS- JSBridge原理

    摘要:作者心葉時間原理概述簡介是代碼與代碼的通信橋梁。目前的一種統(tǒng)一方案是觸發(fā)捕獲原生分析執(zhí)行原生調(diào)用。另外調(diào)用時處理完畢后一定要及時通知進行回調(diào)要不然這個回調(diào)函數(shù)不會自動銷毀多了后會引發(fā)內(nèi)存泄漏。 作者:心葉時間:2019-03-25 10:18 原理概述 簡介 JSBridge是Native代碼與JS代碼的通信橋梁。目前的一種統(tǒng)一方案是:H5觸發(fā)url scheme->Native捕獲u...

    txgcwm 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<