React Native Android從源碼看WebView 沒(méi)有OverrideUrl解決辦法,以及高度自適應(yīng)

react native.jpg

00

其實(shí)我這篇文章的目的并不完全是要解決這個(gè)問(wèn)題。而是想通過(guò)這個(gè)問(wèn)題來(lái)簡(jiǎn)單講講React Native組件和Android原生控件的一個(gè)關(guān)系,以及如何通過(guò)看源碼來(lái)排查解決React NativeAndroid機(jī)型遇到的問(wèn)題的思路,當(dāng)然iOS的思路也是大同小異的。ps:總結(jié)在最后。

需求背景:有一個(gè)文章詳情是以html富文本的方式存在后臺(tái)數(shù)據(jù)庫(kù)的,現(xiàn)在需要React NativeWebView來(lái)展示。而且在這個(gè)詳情頁(yè)頭部是有除WebView以外的組件。這個(gè)時(shí)候富文本里有一個(gè)<a>標(biāo)簽跳轉(zhuǎn)鏈接,需要另外打開(kāi)一個(gè)頁(yè)面來(lái)承載這個(gè)鏈接。

01

知道了這個(gè)需要,我們第一反應(yīng)肯定是先去看文檔,http://reactnative.cn/中文網(wǎng)里WebView章節(jié)里有這么一個(gè)方法onShouldStartLoadWithRequest(允許為WebView發(fā)起的請(qǐng)求運(yùn)行一個(gè)自定義的處理函數(shù)。返回true或false表示是否要繼續(xù)執(zhí)行響應(yīng)的請(qǐng)求。),但是....重點(diǎn)在但是,這個(gè)方法只有iOS

作為一個(gè)Android開(kāi)發(fā)人員我就有點(diǎn)不理解了,Android WebView明明有類似的方法回調(diào)shouldOverrideUrlLoading,不是號(hào)稱React Native調(diào)用的就是原生的控件嗎,為什么不提供呢?

02
接下來(lái),我就去翻看了源碼(以0.48版本為例)。node_modules/react-native/android/com/facebook/react/react-native/0.48.3/react-native-0.48.3-source.jar這個(gè)包里,有個(gè)類ReactWebViewManager.java,這就是facebook開(kāi)發(fā)人員封裝的給RN用的WebView了。找到WebViewshouldOverrideUrlLoading方法。源碼如下

@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
    if (url.startsWith("http://") || url.startsWith("https://") ||
        url.startsWith("file://") || url.equals("about:blank")) {
      return false;
    } else {
      try {
        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        view.getContext().startActivity(intent);
      } catch (ActivityNotFoundException e) {
        FLog.w(ReactConstants.TAG, "activity not found to handle uri scheme for: " + url, e);
      }
      return true;
    }
}

從源碼來(lái)看,確實(shí)沒(méi)有拋出事件給RN,個(gè)人分析原因,應(yīng)該是因?yàn)锳ndroid和RN之間沒(méi)有一個(gè)比較好的同步通信機(jī)制,至少官方文檔里提到的通信方式都是異步的。所以這個(gè)地方暫時(shí)沒(méi)有封裝出去給RN來(lái)決定。

03

看到這里,其實(shí)已經(jīng)發(fā)現(xiàn)了問(wèn)題原因所在了。
接下來(lái)就是考慮怎么解決了。
這里我提供兩個(gè)思路吧。
思路1:從Android這邊入手。【強(qiáng)烈推薦】

既然官方提供的WebView沒(méi)有提供方法,那我們完全可以自己封裝一個(gè)WebView給RN用撒,RN那邊設(shè)置一個(gè)參數(shù)是否需要shouldOverrideUrlLoading={true},Android這邊接收這個(gè)參數(shù),如果判斷為true就在Android的shouldOverrideUrlLoading回調(diào)里將事件dispatchEvent分發(fā)給RN,RN那邊寫個(gè)回調(diào)就好啦,其實(shí)我覺(jué)得官方也可以這么來(lái)寫。 后續(xù)我再寫一篇文章,詳細(xì)講述這個(gè)編碼過(guò)程。

思路2:從RN JS那邊入手。

利用RN WebView的injectedJavaScript屬性,給WebView注入一段js代碼,攔截所有<a>標(biāo)簽的跳轉(zhuǎn),并將事件和即將跳轉(zhuǎn)的url通過(guò)postMessage的方式回調(diào)給RN,這樣就可以啦,以下是代碼片段。這個(gè)方案其實(shí)不是一個(gè)保險(xiǎn)的解決方案,因?yàn)榭?code>Android源碼可以看到,injectedJavaScript是在onPageFinish里回調(diào)的,而這個(gè)回調(diào)在Android本身是有適配問(wèn)題的,有時(shí)候是不會(huì)回調(diào)的,比如網(wǎng)頁(yè)里某個(gè)css、js文件沒(méi)下載下來(lái),會(huì)一直卡住,以至于不會(huì)回調(diào)結(jié)束。所以還是推薦第一種方案。

renden的定義,【這里其實(shí)還實(shí)現(xiàn)WebView的高度自適應(yīng)

  render() {
    const _w = this.props.width || Dimensions.get('window').width;
    const _h = this.props.autoHeight ? this.state.webViewHeight : this.props.defaultHeight;

    return <WebView
        injectedJavaScript={'(' + String(injectedScript) + ')();'}
        scrollEnabled={this.props.scrollEnabled || false}
        onMessage={this._onMessage}
        javaScriptEnabled={true}
        automaticallyAdjustContentInsets={true}
        renderLoading={this._loadingView}
        {...this.props}
        style={[{width: _w}, this.props.style, {height: _h}]}
    />
    
}

注入的js

const injectedScript = function () {

function awaitPostMessage() {
    var isReactNativePostMessageReady = !!window.originalPostMessage;
    var queue = [];
    var currentPostMessageFn = function store(message) {
        if (queue.length > 100) queue.shift();
        queue.push(message);
    };
    if (!isReactNativePostMessageReady) {
        var originalPostMessage = window.postMessage;
        Object.defineProperty(window, 'postMessage', {
            configurable: true,
            enumerable: true,
            get: function () {
                return currentPostMessageFn;
            },
            set: function (fn) {
                currentPostMessageFn = fn;
                isReactNativePostMessageReady = true;
                setTimeout(sendQueue, 0);
            }
        });
        window.postMessage.toString = function () {
            return String(originalPostMessage);
        };
    }

    function sendQueue() {
        while (queue.length > 0) window.postMessage(queue.shift());
    }
}


awaitPostMessage(); // Call this only once in your Web Code.
//至此,是為了保證一定會(huì)調(diào)成功postMessage

var originalPostMessage = window.postMessage;

var patchedPostMessage = function (message, targetOrigin, transfer) {
    originalPostMessage(message, targetOrigin, transfer);
};

patchedPostMessage.toString = function () {
    return String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage');
};

window.postMessage = patchedPostMessage;

let height;
if (document.documentElement.clientHeight > document.body.clientHeight) {
    height = document.documentElement.clientHeight
} else {
    height = document.body.clientHeight
}

window.postMessage("height=" + height); //這里是把網(wǎng)頁(yè)內(nèi)容高度傳給rn,以實(shí)現(xiàn)自適應(yīng)高度

//以下就是找到所有a標(biāo)簽,并將url傳給RN處理
var aNodes = document.getElementsByTagName('a');
for (var i = 0; i < aNodes.length; i++) {
    aNodes[i].onclick = function (e) {
        e.preventDefault();//這句話是阻止a標(biāo)簽跳轉(zhuǎn)
        window.postMessage("url=" + e.target.href)
    }
}
};

onMessage的處理

_onMessage(e) {
    let data = e.nativeEvent.data;
    if (data.slice(0, 7) == 'height=') {
        let height = data.substring(7, data.length)
        this.setState({
            webViewHeight: parseInt(height)
        });
    } else if (data.slice(0, 4) == 'url=') {
        let url = data.substring(4, data.length)
        //處理攔截的a標(biāo)簽事件
            ...
    }
}

04

最后,做個(gè)首尾呼應(yīng)。我們來(lái)簡(jiǎn)單總結(jié)下React Native組件和Android原生控件的一個(gè)關(guān)系。

通過(guò)上面這個(gè)案例分析,我們可以清晰的看到RN是做了一個(gè) 用js來(lái)調(diào)用原生控件的一個(gè)偉大事情,并在js端以組件的方式來(lái)使用,但是這個(gè)原生控件是經(jīng)過(guò)了一定封裝的,并不是將所有原生控件的屬性方法都暴露給js端。這里就會(huì)有很大的坑,因?yàn)?code>Android的適配很多時(shí)候是一個(gè)經(jīng)驗(yàn)工作,再加上國(guó)內(nèi)很多手機(jī)廠商都有自己的修改過(guò)的ROM,這就導(dǎo)致facebook的開(kāi)發(fā)人員在封裝控件的時(shí)候可能并不能完全考慮該控件的適配問(wèn)題以及使用場(chǎng)景,就會(huì)出現(xiàn)純js不能直接解決的問(wèn)題。具體例子我就不再列舉了,同理于iOS。

所以,React Native固然好,但是也有一定的局限,他的發(fā)展之所以到現(xiàn)在還在0.48版本,也是有一定道理的。

當(dāng)然,RN的好處也很多的,提高了業(yè)務(wù)的編碼效率,讓更多的web前端開(kāi)發(fā)也能寫App等等,最最重要的我覺(jué)得還是可以做到跨平臺(tái)以及熱更新。

05

至此!
感謝閱讀!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容