React NativeReact Native开发react-native

React Native WebView踩坑记

2016-10-26  本文已影响7166人  ronniegong

React Native WebView踩坑记

在使用React Native开发应用时,有些第三方的页面需要在WebView中展示,最初使用WebView展示几个简单的广告推广页时,没有遇到什么问题。随着使用深入,遇到React Native WebView组件的几个问题,一番探索,终于明白了问题点。

React Native Android WebView body height 100%

遇到这个问题是在使用0.30版本时,在该版本下有一个外部业务的页面在我们的WebView展示时渲染不出来,表现为页面是一个空白页。

通过观察日志、断点调试也没有看到什么错误信息提示。

遇到这种问题,思路是首先搜索官方issues,经过搜索,果然发现已有issue
https://github.com/facebook/react-native/issues/5211

下面的讨论中,有人提到了Android端WebView对height 100%的样式不能识别,当页面body设置height 100%时,在WebView中会为页面body设置height 0,因此页面渲染为空白页。

有了这个提示,打开chrome://inspect开始调试当前WebView,经过审查工具查看样式,发现确实这一页面为body设置了height 100%。幸好WebView允许我们向页面注入js代码,于是可以通过向页面注入下列代码解决

const jsForInjection = `
  var el = document.getElementsByTagName('body')[0];
  el.style.height = '${Dimensions.get('window').height}px';
`

跟进React Native release notes发现,在0.32版本,修复了这一缺陷,链接https://github.com/facebook/react-native/commit/1bb1385c7d199a473f76cdec357de2ab4d1d61b6

React Native Android&iOS WebView view height 100%

看到前文提到的官方说明,提到已修复这一缺陷还没高兴多久。又遇到了另一个问题。在另一个业务的页面中,使用了比较复杂的布局方式,在页面内容的某一个子区域,又使用了height 100%这种布局。

这一次不只是Android了,iOS的WebView也不能正确解析这一样式,使用height 100%布局的这一个子区域渲染为空白区域。

这里没有通用的解决方式了,其他业务如果可以去修改自身的样式布局当然是最好。更实际的解决方案,是发现一例,解决一例。

以这里遇到的一个页面为例,通过chrome://inspect定位到具体是哪一个区域的问题后,通过注入js,定向的修改这一样式。

这是一个没有办法的办法了。

 var licaiapp = document.getElementsByClassName('hero-product')[0];
  if(licaiapp){
  licaiapp.style.height = '542px';
  }

React Native Android 私有协议 crash

在网页中使用js通过私有协议调起外部App,这种方式对大家来说应该不会陌生。

在Android 0.30版本的使用中,这里没遇到问题,但是升级到0.35版本后,发现在用户没有安装App A时,在WebView加载的页面中,通过私有协议调起App A时,会导致App crash。

通过查看Android Studio日志,发现下面异常信息

No Activity found to handle Intent { act=android.intent.action.VIEW dat=investapp://webend-promotion flg=0x10000000 }

根据这一异常信息,查看安装端ReactWebViewManager源码,发现问题的根源在于下面方法

@Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        if (url.startsWith("http://") || url.startsWith("https://")) {
          return false;
        } else {
          Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); 
          intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
          view.getContext().startActivity(intent);   
          return true;   
        }              
    }

React Native 0.31版本开始,在ReactWebViewManager中增加了上述方法,加载私有协议时,通过else中的逻辑调起App。问题是这里代码,在用户未安装这一App,startActivity不能被正确响应时会抛出Exception,而代码的调用处又没有try/catch,因此导致了crash

给官方提了issue,不过很快被close说觉得不是RN的问题。。。
https://github.com/facebook/react-native/issues/10499

看其他issue也有提到这个bug,不再去修改问题了。

解决办法,基于官方的ReactWebViewManager做下修改吧,然后封装一个自定义WebView出来给JS端调用。

React Native iOS 私有协议问题

在iOS模拟器加载一个外部业务的页面时,遇到一个奇怪的问题。业务的页面,首先在WebView里渲染出来,然后又跳去了默认失败界面,如下图所示

起初一直没理解到底是什么问题,实际上已有issue说的还算明白了
https://github.com/facebook/react-native/issues/9037

最初在没理解问题是什么时候,尝试了其他方案,引入了社区的react-native-wkwebview来替代官方的WebView,引入后发现确实解决了问题,页面可以正常渲染,在已安装目标App的手机上,也可以调起App了。

后来随着对Android端WebView问题的跟进,在加上又看了下上面反馈的issue,意识到这里也是因为WebView加载私有协议失败引起的,不同的是,在iOS端,加载私有协议失败后,进入了失败处理逻辑,跳到了失败提示界面。

解决方案,iOS端为解决这一问题提供了一个便利的方法,

onShouldStartLoadWithRequest

可以在这个方法中,根据url的协议头,决定是否真的发起请求,还是调起外部App,代码如下


onShouldStartLoadWithRequest(event){
        if(event.url.startsWith('http://') || event.url.startsWith('https://')) {
            return true;
        }else{
            Linking.canOpenURL(event.url)
                .then(supported => {
                    if(supported){
                       return Linking.openURL(url);
                    }else{
                        return false;
                    }
                }).catch(err => {
                    return false;
            })
        }
    }

ReactNative与WebView双向通信

这个问题当前业务中还没用到,不过目前官方API,仅支持向WebView中注入JS,还不支持从WebView中的页面调用React Native中的方法。

解决办法,社区已有一个解决方案react-native-webview-bridge

如果觉得有帮助,可以扫描二维码对我打赏,谢谢

上一篇下一篇

猜你喜欢

热点阅读