Open zhaobinglong opened 3 years ago
在ios中,并没有现成的api让js去调用native的方法,但是UIWebView与WKWebView能够拦截h5内发起的所有网络请求。所以我们的思路就是通过在h5内发起约定好的特定协议的网络请求,如'jsbridge://bridge2.native?params=' + encodeURIComponent(obj)然后带上你要传递给ios的参数;然后在客户端内拦截到指定协议头的请求之后就阻止该请求并解析url上的参数,执行相应逻辑
// 这是一个H5调用IOS分享的例子
function createIframe(url){
var url = 'jsbridge://getShare?title=分享标题&desc=分享描述&link=http%3A%2F%2Fwww.douyu.com&cbName=jsCallClientBack';
var iframe = document.createElement('iframe');
iframe.style.width = '1px';
iframe.style.height = '1px';
iframe.style.display = 'none';
iframe.src = https://segmentfault.com/a/url;
document.body.appendChild(iframe);
setTimeout(function() {
iframe.remove();
}, 100);
}
Native调用Javascript语言,是通过UIWebView组件的stringByEvaluatingJavaScriptFromString方法来实现的,该方法返回js脚本的执行结果。
// IOS swift code
webview.stringByEvaluatingJavaScriptFromString("window.methodName()")
从上面代码可以看出它其实就是执行了一个字符串化的js代码,调用了window下的一个对象,如果我们要让native来调用我们js写的方法,那这个方法就要在window下能访问到。但从全局考虑,我们只要暴露一个对象如JSBridge给native调用就好了。
调用客户端原生方法的回调函数也将绑在window下供客户端成功反调用,实际上一次调用客户端方法最后产生的结果是双向互相调用。
通过schema方式,客户端使用shouldOverrideUrlLoading方法对url请求协议进行解析。这种js的调用方式与ios的一样,使用iframe来调用native方法。通过在webview页面里直接注入原生js代码方式,使用addJavascriptInterface方法来实现。
// android JAVA code
class JSInterface {
@JavascriptInterface
public String getShare() {
//...
return "share";
}
}
webView.addJavascriptInterface(new JSInterface(), "AndroidNativeApi");
在安卓APP中,客户端通过webview的loadUrl进行调用:
// android JAVA code
webView.loadUrl("javascript:window.jsBridge.getShare()");
H5端将方法绑定在window下的对象即可,无需与IOS作区分 上面的代码就是在页面的window对象里注入了AndroidNativeApi对象。在js里可以直接调用原生方法。
/* eslint-disable */
/**
* 引入该mixin后使用方式:
* 1.在appFunConst.js中定义好方法名,
* 2.在需要引入的vue文件data中定义registFunName,格式:registFunName: [GET_UPLPAD, RETURN_UPLPAD],,
* 3.在vue文件中调用:this.bridgeApp(GET_UPLPAD,RETURN_UPLPAD, params);
* bridgeApp函数的详细参数,搜索该文件,见定义处
*/
import { judgeAuthUtils, getUserInfo } from '@/libs/utils'
import { getButtonsPermissions } from '@/api/user/index'
export default {
data () {
return {
browserInfo: null,
userBtnPermissions: null
}
},
async mounted () {
await this.getUserPermission()
},
created () {
this.getBrowser()
// 注册方法
if (!this.browserInfo.isWeb && this.registFunName) {
console.log('bridge-app 注册的方法:', this.registFunName)
this.setWindowFun(this.registFunName)
}
},
methods: {
setWindowFun (funName) {
funName.forEach(item => {
window[item] = (params) => {
console.log('bridge-app.js ~ setWindowFun 调用window方法~ 方法名和params', item, params)
if (params && item.includes('return')) params = this.appDataToJson(params)
console.log('--', item);
console.log('--', params);
console.log('--', this);
console.log(this[item])
this[item](params)
}
})
},
// 注册事件监听,初始化
setupWebViewJavascriptBridge (callback) {
if (this.browserInfo.android) {
if (window.WebViewJavascriptBridge) {
callback(WebViewJavascriptBridge)
} else {
// 添加dom事件
document.addEventListener(
'WebViewJavascriptBridgeReady',
function () {
callback(WebViewJavascriptBridge)
},
false
)
}
}
if (this.browserInfo.iPhone) {
if (window.WebViewJavascriptBridge) {
return callback(WebViewJavascriptBridge)
}
if (window.WVJBCallbacks) {
return window.WVJBCallbacks.push(callback)
}
window.WVJBCallbacks = [callback]
var WVJBIframe = document.createElement('iframe')
WVJBIframe.style.display = 'none'
WVJBIframe.src = 'https://__bridge_loaded__'
document.documentElement.appendChild(WVJBIframe)
setTimeout(function () {
document.documentElement.removeChild(WVJBIframe)
}, 0)
}
},
/**
* 注册app和H5通信关系
* @param {*} jsCallAppFuncName H5调用APP同名方法
* @param {*} appCallJsFuncName App调用H5方法
* @param {*} params H5向app发送数据
*/
bridgeApp (jsCallAppFuncName, appCallJsFuncName, params) {
// console.log(navigator.userAgent);
if ((this.browserInfo.isWeb && jsCallAppFuncName === 'openPath') || (this.browserInfo.isWechatMinWeb && jsCallAppFuncName === 'openPath')) {
this.$router.push({
path: params.url,
query: this.getUrlVars(params.url)
})
return
}
// console.log('注册app和H5通信关系', appCallJsFuncName, jsCallAppFuncName, params)
// 回调函数,接收java发送来的数据
this.setupWebViewJavascriptBridge(function (bridge) {
if (jsCallAppFuncName) {
// console.log("file: bridge-app.js ~ H5调用APP同名方法(已注册) ~ 方法名:", jsCallAppFuncName, '参数', params)
/* jsCallAppFuncName是js调用app的方法名,params则是h5传给appapp的参数,function是调用成功后js收到app的回调。 */
/* 前端可以传params为json对象,但是在发给APP的时候,要序列化为字符串 */
if (params) {
params = JSON.stringify(params)
}
bridge.callHandler(jsCallAppFuncName, params, function (responseData, responseCallback) {
// TODO 这里有个优化,其实是可以直接接收到app返回的数据,待研究联调测试
console.log('!!!js调用app方法' + jsCallAppFuncName + '之后,app成功的回调信息:' + responseData + ',responseCallback=' + responseCallback)
})
}
if (appCallJsFuncName) {
console.log('file: bridge-app.js ~ app调用H5方法(已注册) ~ 方法名:', appCallJsFuncName)
/* appCallJsFuncName是js注册的方法,供app调用,callbackJsFunc是调用后js执行的回调 */
bridge.registerHandler(appCallJsFuncName, function (data, responseCallback) {
console.log('app调用H5方法' + appCallJsFuncName + '之后, app返回的信息:' + data)
responseCallback(data)
})
}
})
},
getBrowser () {
var userAgent = navigator.userAgent
var version = navigator.appVersion
var language = (navigator.browserLanguage || navigator.language).toLowerCase()
// 浏览器信息
this.browserInfo = {
trident: userAgent.indexOf('Trident') > -1,
presto: userAgent.indexOf('Presto') > -1,
webKit: userAgent.indexOf('AppleWebKit') > -1,
gecko: userAgent.indexOf('Gecko') > -1 && userAgent.indexOf('KHTML') === -1,
mobile: !!userAgent.match(/AppleWebKit.*Mobile.*/),
ios: !!userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/),
android: userAgent.indexOf('Android') > -1 || userAgent.indexOf('Linux') > -1,
iPhone: userAgent.indexOf('iPhone') > -1,
iPad: userAgent.indexOf('iPad') > -1,
webApp: userAgent.indexOf('Safari') === -1,
language: language,
version: version,
isWeb: localStorage.getItem('outApp') === '1', // 判断是否在浏览器中打开
isWechatMinWeb: userAgent.indexOf('MicroMessenger') > -1 // 判断是否在微信小程序
}
return this.browserInfo
},
// 处理ios 和 Android 返回的数据
appDataToJson (data) {
if (this.browserInfo.ios) {
// ios不需要转,直接返回
return data
} else {
// console.log("🚀 ~ file: bridge-app.js ~ line 137 ~ appDataToJson ~ data", data, typeof data)
const res = {}
if (!data) {
return res
}
if (data instanceof Object) {
return data
}
if (data.indexOf('{') === -1) {
return data
}
// console.log('is android', JSON.parse(data))
return JSON.parse(data)
}
},
// 获取url后的query参数
getUrlVars (url) {
var hash
var myJson = {}
var hashes = url.slice(url.indexOf('?') + 1).split('&')
for (var i = 0; i < hashes.length; i++) {
hash = hashes[i].split('=')
myJson[hash[0]] = hash[1]
}
return myJson
},
// 获取用户按钮权限
judgeAuth (btncode) {
let flag = false
this.userBtnPermissions = JSON.parse(this.userBtnPermissions)
flag = judgeAuthUtils(btncode, this.$route.path, this.userBtnPermissions)
return flag
},
getUserPermission () {
const userInfo = getUserInfo()
const key = userInfo ? userInfo.expireTime : null
const sessionData = localStorage.getItem(key)
if (sessionData) {
this.userBtnPermissions = sessionData
} else {
getButtonsPermissions().then(res => {
localStorage.setItem(key, JSON.stringify(res))
this.userBtnPermissions = localStorage.getItem(key)
})
}
}
},
beforeDestory () {
}
}
判断终端
由于各个平台的通信方式不一致,所以第一步需要判断当前的终端,再决定用什么通信方式
容器
参考
https://blog.csdn.net/frontend_frank/article/details/109281763