采用混合开发App时,通常会涉及到跟客户端的交互,比如获取系统信息、获取 App 信息、设备信息等。这些都需要通过客户端上暴露方法给WebView才可以通过网页拿到相关信息,但是通常又面临着,不同的端平台用来获取时的方式是不一样的,这就导致了网页在获取一些信息时,需要去适配不同的平台,一旦需要去适配就表示这需要写很多的判断条件,自然而然的就会产生比较多的冗余代码。为了解决这个问题,我们就需要建立一个公用的转换方法,通过双端统一的方式来调用客户端方法,从而减少冗余代码的产生。
1. 问题的由来
在网页中,如果需要获取客户端的系统时,会通过一个方法getOs()来获取,但是不同的客户端暴露出的方法是不同的,比如Android通过 window.nativeAndroidClient.getOS()来获取,而对于iOS来说则是通过 window.nativeIOSClient.call("getOs")来获取,那么网页在获取客户端信息时则需要针对不同的客户端做适配,如下:
function getOs() {
return window["nativeAndroidClient"] ? nativeAndroidClient.getOs() : nativeIOSClient.call("getOs")
}
这样写法倒也没有什么不可以,但是我们在网页中不仅仅需要获取这一个属性,还有很多的属性,比如:App 版本、用户信息、设备信息、本地语言等,一个属性对应一个方法,那么我们就需要针对每一个方法去做一个判断,这样就显得代码不是那么好看了,最好的方式,则是把客户端的方法都统一,通过统一调用就能获取到客户端的信息。
注:
nativeAndroidClient与nativeIOSClient都是客户端注册在WebView window上的对象。
2. 解决问题
从上文描述看来,iOS通过window.nativeIOSClient.call(name)对象来获取客户端相关信息,这么写呢感觉不是太符合调用习惯,我们希望的获取可以是这样的方式:window.nativeIOSClient.getOs()我们需要改造nativeIOSClient对象,注意是改造而不是覆盖(也可以说是给此对象追加属性) ,如果覆盖的话我们则是把客户端暴露给网页的方法给换掉了,这样我们再想用客户端的方法则就用不上了,很危险。
2.1 改造 nativeIOSClient 对象
封装一个方法并执行,通过此方法可以给 nativeIOSClient 上追加获取属性的方法,可以通过window.nativeIOSClient.getOs()的调用方式来调用,代码如下:
const iOSClient = window['nativeIOSClient']
const androidClient = window['nativeAndroidClient']
function initIOSClient() {
Object.assign(iOSClient, {
getOs() {
return this.call("getOs")
},
getAppVersion() {
return this.call("getAppVersion")
}
...
})
}
iOSClient && initIOSClient()
这样我们就保证了在window['nativeIOSClient']也可以通过window.nativeIOSClient.getOs()的调用方式来调用。
2.2 封装统一的执行方法
我们已经通过第一步把iOS客户端获取属性的方法进行了改造,现在就是统一调用了,为什么需要统一调用?虽然我们把iOS客户端方法进行了改造,但是我们在页面上用时还需要判断客户端,然后再执行相应的方法。因为Android是绑定在nativeAndroidClient对象上,而iOS则是绑定在nativeIOSClient对象上,如果不封装则还是需要在页面上进行客户端判断,再分别调用。接下来整合一下,使得通过一个方法可以调用双端的方法,代码如下:
const iOSClient = window['nativeIOSClient']
const androidClient = window['nativeAndroidClient']
function executeNativeMethod(optios) {
const { iOSClient, androidClient, name, args } = optios
if (iOSClient && isFunction(iOSClient[name])) {
return iOSClient[name](args)
}
if (androidClient && isFunction(androidClient[name])) {
return androidClient[name](args)
}
}
function getParam(name, ...args) {
return executeNativeMethod({
iOSClient,
androidClient,
name,
args
})
}
注:
isFunction方法用来判断当前如果是非方法类型时,则不去执行,防止意外的报错。也很简单的封装。
export function isFunction(value) {
return typeof value === "function"
}
2.3 重写所有获取客户端属性的方法
重写后暴露出去,那么在页面中就可以通过getOs()方式进行获取,这样就非常便利在各个页面中的调用了。
export function getOs() {
return getParam("getOs")
}
export function getAppVension() {
return getParam("getAppVension")
}
当然,我们还需要考虑另外一个场景,就是说页面可能只在iOS客户端上生效,那么我们也就不需要这么费时费力的重新了直接把iOSClient导出即可。
const iOSClient = window['nativeIOSClient']
export {
iOSClient
}
3. 总结
通过简单的封装后,在网页中则不需要再写判断条件,就可以通过一个统一的方式来获取,这样就可以减少冗余代码的产生。其实在跟客户端的交互中,会遇到很多方法名不统一的问题,我们必须二次封装才能达到我们想要的效果,这样在维护网页时就可以极大的提高我们的开发效率,而不必再写很多的判断,当然每一次的封装都不是一蹴而就的,只能是一步步去写、一步步去提炼才能形成一个工具类。