RN - 封装Android原生WebView组件,实现JS获取原生消息回调及JS控制native组件

Posted iOSTianNan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RN - 封装Android原生WebView组件,实现JS获取原生消息回调及JS控制native组件相关的知识,希望对你有一定的参考价值。

之前为了处理react-native-webView 无法处理安卓端微信支付的问题, 封装了安卓原生组件提供给RN使用, 此次还需要针对RN组件支持回调事件操作原生组件执行某些操作

这个是基于RN 调用原生WebPage 解决微信支付Referer问题的继续扩展

目的:
1.实现RN webView组件 获取 onCanGoBack=(canGoBack)=> 回调
2.实现RN webView组件 获取 onTitle=(title)=> 回调
3.实现RN webView组件 通过ref调用 goBlack() 函数, 实现导航箭头的回退功能

参考链接1
参考链接2

核心内容:
1. native向RN发送消息事件, RN通过回调函数获取消息
->即实现 onCanGoBack=(canGoBack:boolean)=>
->onTitle=(title)=>
2.RN向Native发送操作命令,Native接收后执行操作
-> 即实现this.refs.web.goBack()

A: 基础部分 - 封装原生WebView组件

1. WxPayReactWebViewManager实现

新建ViewManager类,并继承SimpleViewManager,SimpleViewManager类需要传入一个泛型,该泛型继承android的View,也就是说该泛型是要使用android 平台的哪个View就传入该View

	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:

requireNativeComponent - 通过该函数将原生组件名称 与 RN组件类名 进行关联,返回原生组件的实例

var WxPayRCTWebView = requireNativeComponent('WxPayRCTWebView', WxPayWeb, nativeOnly: propABC:true)
//propABC属性不想暴露给外面的话,就是通过nativeOnly来设置

WxPayWeb:

WxPayWeb 真正的RN组件类, 实际是返回的一个 <WxPayRCTWebView ...this.props .../>原生组件

  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);
            

onCanGoBack : 约定的 回调函数名
canGoBack : 回调函数返回的参数名
onTitle : 约定的 回调函数名
title : 回调函数返回的参数名

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接收后执行操作

RN端加载的网页跳转了多次,现在想返回上一级网页,最终其实是通过RN组件向
Native组件发送操作命令, Native接收到命令后,调用自己组件的方法,
这里就是webViewd.goBack()

在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组件 WxPayWebgoBack 命令的实现

  // 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以上是关于RN - 封装Android原生WebView组件,实现JS获取原生消息回调及JS控制native组件的主要内容,如果未能解决你的问题,请参考以下文章

RN-Android 封装原生安卓web组件 - H5白屏/卡顿/渲染问题

[RN] Camera: ViewGroupManager

RN系列:Android原生与RN如何交互通信

RN与原生交互——传参并带有回调

RN调用原生的方法

FlutterWeb 和 WebView 原生交互调用