参考链接1
参考链接2
核心内容:
1. native向RN发送消息事件, RN通过回调函数获取消息
->即实现 onCanGoBack={(canGoBack:boolean)=>{}}
->onTitle={(title)=>{}}
2.RN向Native发送操作命令,Native接收后执行操作
-> 即实现this.refs.web.goBack()
A: 基础部分 - 封装原生WebView组件
1. WxPayReactWebViewManager实现
public static final String RN_CLASS = "WxPayRCTWebView";
@Override
public String getName() {
return RN_CLASS;
}
实现视图方法 -返回ViewManager的实例对象, 这里返回WebView的实例对象
@SuppressLint("ResourceAsColor")
@Override
protected WebView createViewInstance(ThemedReactContext reactContext) {
...具体代码省略
设置RN组件可以支持的参数名
/**
* js传递的参数
*
* @param view
* @param loadInfo
*/
@ReactProp(name = "loadInfo")
public void setLoadInfo(WebView view, @Nullable ReadableMap loadInfo) {
...具体代码省略
2 WxPayWebViewReactPackage的实现
createNativeModules()是注册模块的,createViewManagers()是注册管理器的,如果想要注册多个模块就在List里面多个添加就行
/**
* 注册模块
* @param reactContext
* @return
*/
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
/**
* 注册管理器
* @param reactContext
* @return
*/
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
new WxPayReactWebViewManager()
);
}
3 MainApplication中注册ReactPackage
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for
// example:
packages.add(new WxPayWebViewReactPackage());
return packages;
}
4 RN中 - 创建WxPayWeb组件
WxPayRCTWebView:
var WxPayRCTWebView = requireNativeComponent('WxPayRCTWebView', WxPayWeb, {nativeOnly: {propABC:true}})
//propABC属性不想暴露给外面的话,就是通过nativeOnly来设置
WxPayWeb
:
render() {
return <WxPayRCTWebView
{...this.props}
ref={RCT_WxPayWebVIEW_REF}
//onCanGoBack={this._onCanGoBack}
//onTitle={this._onTitle}
/>
}
WxPayWeb.propTypes
:
声明RN组件的支持参数
//RN组件支持的props列表
//新版本必须加...View.propTypes
WxPayWeb.propTypes = {
loadInfo:PropTypes.any,
html: PropTypes.string,
onCanGoBack: PropTypes.func,
onTitle: PropTypes.func,
...View.propTypes
}
目前支持的类型映射
:
// java对应js 类型
Boolean -> Bool
Integer -> Number
Double -> Number
Float -> Number
String -> String
Callback -> function
ReadableMap -> Object
ReadableArray -> Array
5 具体使用
loadInfo
就是自定义的参数名
<WxPayWeb
ref={(WxPayWeb: any) => this.web = WxPayWeb}
style={{flex: 1}}
loadInfo={{
url: url,
referer: url
}}
/>
至此,已经完成基本原生组件的封装,以及RN层面组件的使用,也支持 传递参数供原生使用
B: Native与RN 事件传递及通信
一. Native向RN发送消息,RN通过回调函数获取消息
重写方法- 创建要支持的自定义事件名
自定义事件,只需要重写ReactVideoManager
下的getExportedCustomBubblingEventTypeConstants()
方法就好
/**
* 注册-自定义事件名
* 自定义多个
*
* @return
*/
@Override
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
// onCanGoBack 自定义事件名, 其他固定写法 (包括 bubbled)
MapBuilder.Builder<String, Object> builder = MapBuilder.<String, Object>builder();
builder.put("onCanGoBack", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onCanGoBack")));
// onTitle 自定义事件名, 其他固定写法 (包括 bubbled)
builder.put("onTitle", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onTitle")));
return builder.build();
}
webView
加载时,通过webView.canGoBack()
方法判断是否canGoBack, 向RN层发送消息事件
// 网页回调函数
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
if (wv.canGoBack()) {
WritableMap event = Arguments.createMap();
event.putBoolean("canGoBack", true);
// 发送给js
this.dispatch(reactContext, "onCanGoBack", event);
} else {
WritableMap event = Arguments.createMap();
event.putBoolean("canGoBack", false);
// 发送给js
this.dispatch(reactContext, "onCanGoBack", event);
}
super.onPageStarted(view, url, favicon);
}
/**
* 向js发送事件回调,传值
* @param context
* @param EventName
* @param eventMap
*/
public void dispatch(ThemedReactContext context, String EventName, WritableMap eventMap) {
reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(wv.getId(), EventName, eventMap);
}
RN端回调函数实现及数据接收:
render() {
return <BXRCTWebView
{...this.props}
ref={RCT_BXWEBVIEW_REF}
onCanGoBack={this._onCanGoBack}
onTitle={this._onTitle}
/>
}
// onCanGoBack 回调
_onCanGoBack = (event) => {
//回调均接受的是event,取值为 event.nativeEvent下的各个字段
this.props.onCanGoBack && this.props.onCanGoBack(event.nativeEvent.canGoBack)
}
// onTitle 回调
_onTitle = (event) => {
//回调均接受的是event,取值为 event.nativeEvent下的各个字段
this.props.onTitle && this.props.onTitle(event.nativeEvent.title);
}
//新版本必须加...View.propTypes
BxWeb.propTypes = {
url: PropTypes.string,
html: PropTypes.string,
onCanGoBack: PropTypes.func,
onTitle: PropTypes.func,
...View.propTypes
}
WxPayWeb
组件的完整使用
<WxPayWeb
ref={(WxPayWeb: any) => this.web = WxPayWeb}
style={{flex: 1}}
loadInfo={{
url: url,
referer: url
}}
onCanGoBack={(canGoBack: boolean) => {
// canGoBack
}}
onTitle={(title: string) => {
// title
}}
/>
二. RN向Native发送操作命令,Native接收后执行操作
在native侧, 需要实现两个函数, 用于注册命名列表, 以及接收命令列表
实现 getCommandsMap:
原生组件提供函数调用列表
receiveCommand:
接收js命令的函数
注意 是这个 public void receiveCommand(WebView root, int commandId, ReadableArray args){}
而不是 public void receiveCommand(WebView root, String commandId, ReadableArray args)
后者接收不到
/**
* 注册 js调用native的方法集合
*
* @return
*/
@Nullable
@Override
public Map<String, Integer> getCommandsMap() {
return MapBuilder.of(
"goBack", 1,
"toast", 2
);
}
/**
* 响应 js调用函数的实现
*
* @param root
* @param commandId
* @param args
*/
@Override
public void receiveCommand(WebView root, int commandId, ReadableArray args) {
Log.d("receiveCommand======1", commandId + "");
switch (commandId) {
case 1:
root.goBack();
break;
case 2:
Toast.makeText(wv.getContext(), "hello", Toast.LENGTH_LONG);
break;
default:
break;
}
}
RN组件 WxPayWeb
对 goBack
命令的实现
// js组件执行事件 向native层发送命令
goBack() {
//向native层发送命令
UIManager.dispatchViewManagerCommand(
findNodeHandle(this.refs[RCT_WxPayWebVIEW_REF]),
UIManager.WxPayRCTWebView.Commands.goBack,//Commands.goBack与native层定义的"goBack"一致
null//命令携带的参数数据
);
}
toast_test() {
//向native层发送命令
UIManager.dispatchViewManagerCommand(
findNodeHandle(this.refs[RCT_WxPayWebVIEW_REF]),
UIManager.WxPayRCTWebView.Commands.toast,//Commands.toast与native层定义的"toast"一致
["a", "b"]//命令携带的参数数据 [数据a,数据b...]
);
}
RN组件调用执行
this.refs.web.goBack();
所有代码
Java侧代码:
WxPayWebViewReactPackage
package com.regan.ebankhome;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class WxPayWebViewReactPackage implements ReactPackage {
/**
* 注册模块
* @param reactContext
* @return
*/
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
/**
* 注册管理器
* @param reactContext
* @return
*/
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
new WxPayReactWebViewManager()
);
}
}
WxPayReactWebViewManager
package com.regan.ebankhome;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.net.http.SslError;
import android.util.Log;
import android.webkit.ClientCertRequest;
import android.webkit.SslErrorHandler;
import android.webkit.WebResourceRequest;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.annotations.ReactPropGroup;
import com.facebook.react.uimanager.annotations.ReactPropertyHolder;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import java.util.HashMap;
import java.util.Map;
import static com.umeng.socialize.utils.ContextUtil.getContext;
/**
* ViewManager
*/
public class WxPayReactWebViewManager extends SimpleViewManager<WebView> {
public static final String RN_CLASS = "WxPayRCTWebView";
public String url;
public String referer;
private WebView wv;
/**
* 返回的 组件名称 - 原生组件名称
* @return
*/
@Override
public String getName() {
return RN_CLASS;
}
/**
* 返回ViewManager的实例对象, 这里返回WebView的实例对象
* @param reactContext
* @return
*/
@SuppressLint("ResourceAsColor")
@Override
protected WebView createViewInstance(ThemedReactContext reactContext) {
wv = new WebView(reactContext);
webSetting();
wv.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
try {
if (url.startsWith("weixin://") || url.startsWith("alipays://")) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
reactContext.startActivity(intent);
return true;
}
} catch (Exception e) {
return false;
}
if (url.contains("https://wx.tenpay.com")) {
Map<String, String> extraHeaders = new HashMap<>();
extraHeaders.put("Referer", referer.length() > 0 ? referer : url);
view.loadUrl(url, extraHeaders);
return true;
}
view.loadUrl(url);
return true;
}
/**
* 向js发送事件回调,传值
* @param context
* @param EventName
* @param eventMap
*/
public void dispatch(ThemedReactContext context, String EventName, WritableMap eventMap) {
reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(wv.getId(), EventName, eventMap);
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
if (wv.canGoBack()) {
Log.d("canGoBack===>", "yes-goback");
WritableMap event = Arguments.createMap();
event.putBoolean("canGoBack", true);
// 发送给js
this.dispatch(reactContext, "onCanGoBack", event);
} else {
WritableMap event = Arguments.createMap();
event.putBoolean("canGoBack", false);
// 发送给js
this.dispatch(reactContext, "onCanGoBack", event);
}
super.onPageStarted(view, url, favicon);
}
@Override
public void onPageFinished(WebView view, String url) {
Log.d("onPageFinished===>", url);
WritableMap event = Arguments.createMap();
event.putString("title", view.getTitle());
// 发送给js
this.dispatch(reactContext, "onTitle", event);
super.onPageFinished(view, url);
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
return super.shouldOverrideUrlLoading(view, request);
}
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
// super.onReceivedSslError(view, handler, error);
// 不要使用super,否则有些手机访问不了,因为包含了一条 handler.cancel()
// super.onReceivedSslError(view, handler, error);
// 接受所有网站的证书,忽略SSL错误,执行访问网页
handler.proceed();
}
});
return wv;
}
private void webSetting() {
WebSettings webSettings = wv.getSettings();
webSettings.setAllowFileAccessFromFileURLs(true);
webSettings.setJavaScriptEnabled(true);
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
webSettings.setDomStorageEnabled(true);
webSettings.setDatabaseEnabled(true);
webSettings.setAppCacheEnabled(true);
webSettings.setAllowFileAccess(true);
webSettings.setSavePassword(true);
webSettings.setSupportZoom(true);
webSettings.setBuiltInZoomControls(false);
webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
webSettings.setUseWideViewPort(false);
webSettings.setLoadWithOverviewMode(true);
}
/**
* js传递的参数
*
* @param view
* @param loadInfo
*/
@ReactProp(name = "loadInfo")
public void setLoadInfo(WebView view, @Nullable ReadableMap loadInfo) {
String url = loadInfo.getString("url");
String referer = loadInfo.getString("referer");
Log.d("*(*(*(*(--url", url);
Log.d("*(*(*(*(--referer", referer);
this.url = url;
this.referer = referer;
view.loadUrl(url, (Map<String, String>) new HashMap<>().put("Referer", this.referer));
}
/**
* js传递的参数
*
* @param view
* @param html
*/
@ReactProp(name = "html")
public void setHtml(WebView view, @Nullable String html) {
Log.d("*(*(*(*(--html", html);
view.loadData(html, "text/html; charset=utf-8", "UTF-8");
}
/**
* 注册-自定义事件名
* 自定义多个
*
* @return
*/
@Override
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
// onCanGoBack 自定义事件名, 其他固定写法 (包括 bubbled)
MapBuilder.Builder<String, Object> builder = MapBuilder.<String, Object>builder();
builder.put("onCanGoBack", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onCanGoBack")));
// onTitle 自定义事件名, 其他固定写法 (包括 bubbled)
builder.put("onTitle", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onTitle")));
return builder.build();
}
/**
* 注册 js调用native的方法集合
*
* @return
*/
@Nullable
@Override
public Map<String, Integer> getCommandsMap() {
return MapBuilder.of(
"goBack", 1,
"toast", 2
);
}
/**
* 响应 js调用函数的实现
*
* @param root
* @param commandId
* @param args
*/
@Override
public void receiveCommand(WebView root, int commandId, ReadableArray args) {
Log.d("receiveCommand======1", commandId + "");
switch (commandId) {
case 1:
root.goBack();
break;
case 2:
Toast.makeText(wv.getContext(), "hello", Toast.LENGTH_LONG);
break;
default:
break;
}
}
}
MainApplication
中省略…
js侧代码:
WxPayWeb
/**
*
* @auther: tiannan
* @date: 2021-07-08 15:44
* @profile: WxPayWebView
* @project: ebankhome_c_b
* @description:
*
*/
import React, {Component} from 'react';
import {requireNativeComponent, View, UIManager, findNodeHandle} from 'react-native';
import PropTypes from 'prop-types';
//REF
var RCT_WxPayWebVIEW_REF = 'RCT_WxPayWebVIEW_REF';
// requireNativeComponent - 通过该函数将原生组件名称 与 RN组件类名 进行关联,返回原生组件的实例
// WxPayRCTWebView - 原生组件可以用追加 RCT表示, 明确这是原生组件的创建
// WxPayWeb - RN组件类
// nativeOnly:{todo:false}, 表示todo属性私有,外部不能使用
var WxPayRCTWebView = requireNativeComponent('WxPayRCTWebView', WxPayWeb, {nativeOnly: {}})
// WxPayWeb 真正的RN组件类, 实际是返回的一个 <WxPayRCTWebView {...this.props} .../>原生组件
// 可以提供 如goBack()等方法提供给外部调用
// 可以接受 props, 实现RN传递参数 或者 回调函数给原生组件
// 通过 WxPayWeb.propTypes ={a:PropTypes.String ,b:PropTypes.func} 来明确支持的props及类型
class WxPayWeb extends Component {
constructor(props) {
super(props);
}
// js组件执行事件 向native层发送命令
goBack() {
//向native层发送命令
UIManager.dispatchViewManagerCommand(
findNodeHandle(this.refs[RCT_WxPayWebVIEW_REF]),
UIManager.WxPayRCTWebView.Commands.goBack,//Commands.goBack与native层定义的"goBack"一致
null//命令携带的参数数据
);
}
toast_test() {
//向native层发送命令
UIManager.dispatchViewManagerCommand(
findNodeHandle(this.refs[RCT_WxPayWebVIEW_REF]),
UIManager.WxPayRCTWebView.Commands.toast,//Commands.toast与native层定义的"toast"一致
["a", "b"]//命令携带的参数数据 [数据a,数据b...]
);
}
render() {
return <WxPayRCTWebView
{...this.props}
ref={RCT_WxPayWebVIEW_REF}
onCanGoBack={this._onCanGoBack}
onTitle={this._onTitle}
/>
}
// onCanGoBack 回调
_onCanGoBack = (event) => {
//回调均接受的是event,取值为 event.nativeEvent下的各个字段
this.props.onCanGoBack && this.props.onCanGoBack(event.nativeEvent.canGoBack)
}
// onTitle 回调
_onTitle = (event) => {
//回调均接受的是event,取值为 event.nativeEvent下的各个字段
this.props.onTitle && this.props.onTitle(event.nativeEvent.title);
}
}
//RN组件支持的props列表
//新版本必须加...View.propTypes
WxPayWeb.propTypes = {
loadInfo: PropTypes.any,
html: PropTypes.string,
onCanGoBack: PropTypes.func,
onTitle: PropTypes.func,
...View.propTypes
}
export default WxPayWeb
//----------------------------------------- 旧代码 ---------------------------
// var iface = {
// name: 'WebView',
// propTypes: {
// url: "",
// html: "",
// // readTitle: null,
// // canGoBack: null,
// ...View.propTypes
// },
// }
//
// const WxPayWeb = requireNativeComponent('WxPayRCTWebView', iface, {
// nativeOnly: {
// "testID": true,
// 'renderToHardwareTextureAndroid': true,
// 'accessibilityComponentType': true,
// 'accessibilityLabel': true,
// 'importantForAccessibility': true,
// 'accessibilityLiveRegion': true,
// 'onLayout': true,
// }
// });
//
// export default WxPayWeb;
组件调用
<WxPayWeb
ref={(WxPayWeb: any) => this.web = WxPayWeb}
style={{flex: 1}}
loadInfo={{
url: url,
referer: url
}}
onCanGoBack={(canGoBack: boolean) => {
this.props.navigation.setParams({
canGoBack,
});
}}
onTitle={(title: string) => {
this.props.navigation.setParams({
changeTitle: title.startsWith('http') ? '' : title,
});
}}
/>