React Native WebView踩坑记
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
如果觉得有帮助,可以扫描二维码对我打赏,谢谢