React Native Android从源码看WebView 没有OverrideUrl解决办法,以及高度自适应

react native.jpg

00

其实我这篇文章的目的并不完全是要解决这个问题。而是想通过这个问题来简单讲讲React Native组件和Android原生控件的一个关系,以及如何通过看源码来排查解决React NativeAndroid机型遇到的问题的思路,当然iOS的思路也是大同小异的。ps:总结在最后。

需求背景:有一个文章详情是以html富文本的方式存在后台数据库的,现在需要React NativeWebView来展示。而且在这个详情页头部是有除WebView以外的组件。这个时候富文本里有一个<a>标签跳转链接,需要另外打开一个页面来承载这个链接。

01

知道了这个需要,我们第一反应肯定是先去看文档,http://reactnative.cn/中文网里WebView章节里有这么一个方法onShouldStartLoadWithRequest(允许为WebView发起的请求运行一个自定义的处理函数。返回true或false表示是否要继续执行响应的请求。),但是....重点在但是,这个方法只有iOS

作为一个Android开发人员我就有点不理解了,Android WebView明明有类似的方法回调shouldOverrideUrlLoading,不是号称React Native调用的就是原生的控件吗,为什么不提供呢?

02
接下来,我就去翻看了源码(以0.48版本为例)。node_modules/react-native/android/com/facebook/react/react-native/0.48.3/react-native-0.48.3-source.jar这个包里,有个类ReactWebViewManager.java,这就是facebook开发人员封装的给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;
    }
}

从源码来看,确实没有抛出事件给RN,个人分析原因,应该是因为Android和RN之间没有一个比较好的同步通信机制,至少官方文档里提到的通信方式都是异步的。所以这个地方暂时没有封装出去给RN来决定。

03

看到这里,其实已经发现了问题原因所在了。
接下来就是考虑怎么解决了。
这里我提供两个思路吧。
思路1:从Android这边入手。【强烈推荐】

既然官方提供的WebView没有提供方法,那我们完全可以自己封装一个WebView给RN用撒,RN那边设置一个参数是否需要shouldOverrideUrlLoading={true},Android这边接收这个参数,如果判断为true就在Android的shouldOverrideUrlLoading回调里将事件dispatchEvent分发给RN,RN那边写个回调就好啦,其实我觉得官方也可以这么来写。 后续我再写一篇文章,详细讲述这个编码过程。

思路2:从RN JS那边入手。

利用RN WebView的injectedJavaScript属性,给WebView注入一段js代码,拦截所有<a>标签的跳转,并将事件和即将跳转的url通过postMessage的方式回调给RN,这样就可以啦,以下是代码片段。这个方案其实不是一个保险的解决方案,因为看Android源码可以看到,injectedJavaScript是在onPageFinish里回调的,而这个回调在Android本身是有适配问题的,有时候是不会回调的,比如网页里某个css、js文件没下载下来,会一直卡住,以至于不会回调结束。所以还是推荐第一种方案。

renden的定义,【这里其实还实现WebView的高度自适应

  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.
//至此,是为了保证一定会调成功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); //这里是把网页内容高度传给rn,以实现自适应高度

//以下就是找到所有a标签,并将url传给RN处理
var aNodes = document.getElementsByTagName('a');
for (var i = 0; i < aNodes.length; i++) {
    aNodes[i].onclick = function (e) {
        e.preventDefault();//这句话是阻止a标签跳转
        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标签事件
            ...
    }
}

04

最后,做个首尾呼应。我们来简单总结下React Native组件和Android原生控件的一个关系。

通过上面这个案例分析,我们可以清晰的看到RN是做了一个 用js来调用原生控件的一个伟大事情,并在js端以组件的方式来使用,但是这个原生控件是经过了一定封装的,并不是将所有原生控件的属性方法都暴露给js端。这里就会有很大的坑,因为Android的适配很多时候是一个经验工作,再加上国内很多手机厂商都有自己的修改过的ROM,这就导致facebook的开发人员在封装控件的时候可能并不能完全考虑该控件的适配问题以及使用场景,就会出现纯js不能直接解决的问题。具体例子我就不再列举了,同理于iOS

所以,React Native固然好,但是也有一定的局限,他的发展之所以到现在还在0.48版本,也是有一定道理的。

当然,RN的好处也很多的,提高了业务的编码效率,让更多的web前端开发也能写App等等,最最重要的我觉得还是可以做到跨平台以及热更新。

05

至此!
感谢阅读!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容