+-
react-native中webview的通信桥梁:ird-RnBridge

背景介绍:

前段时间花了一些时间去研究react-native中webview的通信机制,了解到当中的原理,正好业务中也遇到了rn中webview内嵌h5的页面,发现虽然rn提供了一套类似js的postmessage机制,但在h5和rn之间的通信中往往仅靠这个机制是很难提高开发效率和降低代码的复杂度。
因此基于提升在rn中开发h5效率的缘由下,开发了这个ird-RnBridge。

版本迭代历史

简单介绍:

该桥梁主要是适用在react-native和h5之间的通信场景下,并且在rn侧和h5侧各提供了一套不相同的api方法集合以便调用。它提供了几个方面的功能:

1) 安全性校验建立桥梁:
由于rn的很多原生功能是通过webview提供给h5页面调用,如果不鉴别嵌入在webview中的h5页面是否安全,而直接全部提供,这会带来很多安全性的问题。因此rnbridge采取了双重校验方式:
首先:h5侧必须调用checkSafety发起建立桥梁的请求,rn会对其发过来的请求进行校验,该校验会交给rn侧处理,如果处理通过,则rnbridge会发送一个token值给到h5侧,以表明桥梁建立成功;
其次:每次h5和rn的通信,都会带上该token值,rnbridge在rn侧会对其进行匹配,不一致会禁止调用。

h5侧:

      RnBridge.checkSafety({demo: 'demo'}, (data) => {
          document.getElementById('demo').style.color = 'blue';
          console.log('bridge success:', data);
          RnBridge.getSessionStore(['sat2'], (data) => {
              const content = document.getElementById('content');
              content.innerText = JSON.stringify(data);
              console.log('data1', data);
          })
      });

rn侧:

RnBridge.initWebview(this.webview, {
    checkSafety: (params, send) => {
       this.veritySafety(params, send);
    },
});

veritySafety(params, send) {
    send({isSuccess: true, result: 'welcome'});
}

如果不定义checksafeCheck,则rnbridge自动认为是通过,从而自动建立桥梁。

2) rn侧和h5侧相互通信:
rn侧可以通过initWebview来注册提供给h5侧调用的api方法集合,当建立了桥梁之时,rnbridge会将注册在initWebview中的方法名的集合发送到h5侧,h5侧只需要直接调用invokeRN就可以调用rn侧的方法。同理rn侧也是。

3) 提供方便的调试方式:
因为h5内嵌在rn的webview之中,h5内的ajax和console都只能通过vconsole这个插件上看到,调试起来不怎么方便。所以rnbridge提供了debug方法并提供了console和ajax两种模式,可以直接将h5中的ajax和console直接在浏览器上看和调试。

console:
image.png

ajax:
image.png

4) 提供h5侧加载资源的性能参数:
webview加载h5以及h5和rn建立桥梁等参数可以通过sendPerformance和sendPerformanceByType来发起,并最终可以在rn层获取到h5的资源性能参数。

加载性能参数:
image.png

资源性能参数:
image.png

原理介绍:

这里大概分享一下rnbridge在桥梁建立和互相通信之间的一些设计原理:

安全校验,建立桥梁:

image.png

h5调用rn方法:

image.png

rn调用h5方法:

image.png

用途介绍:

因为rnbridge在rn侧和h5侧各自都拥有一套api方法集合,所以在调用这些方法之前,需要调用rnbridge的switchMode方法,从而选择对应的一套api方法集合;

例如在h5侧:

RnBridge.switchMode({mode: 'h5'});

此时h5的api集合就会注入到window.RnBridge之中,因此可以全局范围内随意调用。

在rn侧:

RnBridge.switchMode({mode: 'rn'});

此时rn的api集合就会注入到RnBridge本身,所以可以通过RnBridge来调用。

这是一个简单的例子:
h5侧:

 RnBridge.switchMode({mode: 'h5'});
 
 RnBridge.initH5({
    h1: (params, send) => {
      send({isSuccess: true, result: {a2: 39}});
    },
    h2: (params, send) => {
      send({isSuccess: false, result: {a1: 21}});
    }
});

RnBridge.checkSafety({demo: 'demo'}, (data) => {
    document.getElementById('demo').style.color = 'blue';
});

RnBridge.invokeRN({
    method: 'a1',
    params: {a1: 12},
    success: (result) => {
        document.getElementById('demo').style.color = 'green';
        document.getElementById('demo').innerText = JSON.stringify(result);
    },
   fail: (result) => {
        document.getElementById('demo').style.color = 'red';
        document.getElementById('demo').innerText = JSON.stringify(result);
    }
});

rn侧:

RnBridge.switchMode({mode: 'rn'});

export class Demo extends React.Component {
    constructor(props) {
        super(props);
        this.webview = null;
    }

    render() {
        return (
            <View style={{flex: 1}}>
                <WebView
                    originWhitelist={['*']}
                    source={{ uri: 'http://192.168.1.101:9001/'}}
                    ref={ele => this.webview = ele}
                    onMessage={(e) => {
                        console.log('e', e.nativeEvent.data);
                        RnBridge.listenH5(e.nativeEvent.data);
                    }}
                    onError={(e) => {
                        console.log('error', e)
                    }}
                    onLoadEnd={() => {
                        console.log('load end')
                    }}
                    onLoadStart={() => {
                        console.log('load start')
                    }}
                />
                <View>
                    <Text onPress={() => {
                        this.handleC()
                    }}>click</Text>
                </View>
            </View>
        )
    }

    componentDidMount() {
        console.log('RnBridge', RnBridge);
        RnBridge.initWebview(this.webview, {
            a1: (params, send) => {
                this.handleA(params, send);
            },
            a2: (params, send) => {
                this.handleB(params, send);
            },
            checkSafety: (params, send) => {
                this.veritySafety(params, send);
            },
            setTitle: (params, send) => {
                console.log('title', params);
            }
        })
    }

    veritySafety(params, send) {
        send({isSuccess: true, result: 'welcome'});
    }

    handleA(params, send) {
        console.log(params);
        send({isSuccess: false, result: 'asdf'})
    }

    handleB(params, send) {
        console.log(params);
        RnBridge.invokeH5({

        });
        setTimeout(() => {
            send({isSuccess: true, result: 'ok'})
        }, 1000);
    }

    handleC () {
        RnBridge.invokeH5({
            method: 'h3',
            params: {demo: true},
            success: (params) => {
                console.log('params', params);
            },
            fail: (params) => {
                console.log('fail', params);
            }
        })
    }
}

详细的适用介绍可以查看这里:
ird-rnbridge的readme

结尾:

这个工具库是利用本人晚上加班回来后,花休息时间来设计和开发,因此rnbridge可能存在一些不足之处,如有不足之处请提出,本人会利用业余时间持续优化和功能迭代;开源不易,且行且互勉,若觉得勉强还行,请在github中点个star以作鼓励。