dsBridge.js源码解读备注,本人非专业js开发,解读错误之处希望指出.
主要是用于梳理 dsBridge实现原理
一,申明bridge
bridge对象接口如下
var bridge: {
default: typeof globalThis;
//js invoke native
call: (method: any, args: any, cb: any) => any;
//native invoke js(syn | asyn)
register: (name: any, fun: any, asyn: any) => void;
//native invoke js (asyn)
registerAsyn: (name: any, fun: any) => void;
//内置事件:js invoke native
hasNativeMethod: (name: any, type: any) => any;
//内置事件:js invoke native
disableJavascriptDialogBlock: (disable: any) => void;
}
- call方法:js通过prompt跟native进行通信
call允许传三个参数,如果第三个参数传的是一个方法,则说明是js异步调用native(异步的本质其实就是native的逻辑执行完毕之后再主动调用一个js提供的方法来达到异步的效果)
call: function (method, args, cb) {
var ret = '';
if (typeof args == 'function') {
cb = args;
args = {};
}
var arg={data:args===undefined?null:args}//定义arg对象
//异步(通过传方法来实现异步)
if (typeof cb == 'function') { //如果cb参数是一个方法
var cbName = 'dscb' + window.dscb++; //cbName = dscb1、dscb2、dscb3...
window[cbName] = cb; //window.dscb1 = cb,因此可以调用 dscb1(v)、dscb2(v)等函数,对应 function callAsyn() {
// dsBridge.call("testAsyn","testAsyn", function (v) {
// alert(v)
// })
// } 中的 function(v){}
arg['_dscbstub'] = cbName; //arg对象中添加一个属性 _dscbstub = cbName
}
arg = JSON.stringify(arg) //arg转为json
//if in webview that dsBridge provided, call!
if(window._dsbridge){//是否注入过 _dsbridge对象
//调用已有的_dsbridge的call()方法
ret= _dsbridge.call(method, arg)
}else if(window._dswk||navigator.userAgent.indexOf("_dsbridge")!=-1){//如果注入过_dswk对象,或者 userAgent的最后是_dsbridge
//通过prompt进行通信(客户端的runJavaScriptTextInputPanelWithPrompt协议里面会收到:_dsbridge=xxx 和 arg参数)
ret = prompt("_dsbridge=" + method, arg);
}
return JSON.parse(ret||'{}').data//数据格式转化
},
JS同步调用native
- WKWebView的UI协议识别_dsbridge前缀
- 截取方法和参数,通过runtime的msgsend完成事件
//⚠️ ⚠️ ⚠️js端通过手动拼接传递: prompt("_dsbridge=" + method, arg);
NSString * prefix=@"_dsbridge=";
if ([prompt hasPrefix:prefix])
{
NSString *method= [prompt substringFromIndex:[prefix length]];
NSString *result=nil;
if(isDebug){
result =[self call:method :defaultText ];
}else{
@try {
result =[self call:method :defaultText ];
}@catch(NSException *exception){
NSLog(@"%@", exception);
}
}
completionHandler(result);
}
JS异步调用native
- js通过prompt将事件传递给native,native里面根据双方约定的字符规则进行截取!
- js异步调用native,js端会在参数里面加一个_dscbstub字段告诉客户端来识别!
- 同时还会手动构造一个 'dscb' + window.dscb++; //(dscb1)的方法传给客户端,让客户端执行完逻辑之后在调用一个dscbXXX方法来达到js异步的效果
//⚠️ ⚠️ ⚠️js异步调用(添加_dscbstub来区别同步调用)
if(args && (cb= args[@"_dscbstub"])){
if([JavascriptInterfaceObject respondsToSelector:selasyn]){
__weak typeof(self) weakSelf = self;
//⚠️ ⚠️ ⚠️申明一个异步的回调block,等原生交互完成之后触发
void (^completionHandler)(id,BOOL) = ^(id value,BOOL complete){
NSString *del=@"";
result[@"code"]=@0;
if(value!=nil){
result[@"data"]=value;
}
value=[JSBUtil objToJsonString:result];
value=[value stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
/*
⚠️ ⚠️ ⚠️:通过原生调用调用dscb2(xxxxx),来完成h5的回调动作 调用完成之后删除该对象方法
注入脚本如下:
try {
dscb2(JSON.parse(decodeURIComponent("{"data ":"hello[asyn call]","code ":0}")).data);
delete window.dscb2;
} catch(e) {};
*/
if(complete){
del=[@"delete window." stringByAppendingString:cb];
}
NSString*js=[NSString stringWithFormat:@"try {%@(JSON.parse(decodeURIComponent(\"%@\")).data);%@; } catch(e){};",cb,(value == nil) ? @"" : value,del];
__strong typeof(self) strongSelf = weakSelf;
@synchronized(self)
{
UInt64 t=[[NSDate date] timeIntervalSince1970]*1000;
jsCache=[jsCache stringByAppendingString:js];
if(t-lastCallTime<50){
if(!isPending){
[strongSelf evalJavascript:50];
isPending=true;
}
}else{
//⚠️ ⚠️ ⚠️ 注入脚本完成回调
[strongSelf evalJavascript:0];
}
}
};
/*
⚠️ ⚠️ ⚠️ runtime方式调用.eg: 调用JsEchoApi对象的asyn方法
- (void) asyn: (id) arg :(JSCallback)completionHandler {
completionHandler(arg,YES);
}
asyn执行完毕触发如上的completionHandler闭包体完成js异步回调流程
*/
void(*action)(id,SEL,id,id) = (void(*)(id,SEL,id,id))objc_msgSend;
action(JavascriptInterfaceObject,selasyn,arg,completionHandler);
break;
}
- register方法:js注册同步|异步方法给native调用(默认注册是的同步)
同步 | 异步的方法会放在申明好的 window._dsaf : window._dsf里面。
/*
注册方法给客户端调用
[self callHandler:@"_hasJavascriptMethod" arguments:@[handlerName] completionHandler:^(NSNumber* _Nullable value) {
callback([value boolValue]);
}];
*/
register: function (name, fun, asyn) {
//根据asyn确定同步还是异步(默认是同步调用)
var q = asyn ? window._dsaf : window._dsf
if (!window._dsInit) {
window._dsInit = true;
//notify native that js apis register successfully on next event loop
//延迟0s执行 - 确保bridge未实例化而导致调用失败(同步执行有风险)
setTimeout(function () {
bridge.call("_dsb.dsinit");
}, 0)
}
//将注册给native调用方法对应放到_dsaf|_dsf容器里面
if (typeof fun == "object") {
q._obs[name] = fun;
} else {
q[name] = fun
}
},
注意一下: bridge.call("_dsb.dsinit");的目的是让native收到dsinit消息之后去执行原生调用js的缓存队列
//⚠️ ⚠️ ⚠️:加载页面的时候立即注册了一个_hasJavascriptMethod方法,js注册方法里面会立即调用 bridge.call("_dsb.dsinit");
- (id) dsinit:(NSDictionary *) args{
[self dispatchStartupQueue];
return nil;
}
//⚠️ ⚠️ ⚠️:收到dsinit消息之后开启执行调用队列(native调用js的方法队列)
- (void)dispatchStartupQueue{
if(callInfoList==nil) return;
for (DSCallInfo * callInfo in callInfoList) {
[self dispatchJavascriptCall:callInfo];
}
callInfoList=nil;
}
- registerAsyn方法:js注册异步方法给native调用
/*
dsBridge.hasNativeMethod(name)
*/
hasNativeMethod: function (name, type) {
return this.call("_dsb.hasNativeMethod", {name: name, type:type||"all"});
},
registerAsyn: function (name, fun) {
this.register(name, fun, true);
},
二,瘦身
三,完整JS解读
//定义一个bridge对象
var bridge = {
default:this,// for typescript
/*
bridge对象声明一个call方法: js的参数可以自动缺省匹配(很强)
如果cb传的是一个方法的话,则该是异步调用
同步:
dsBridge.call("testSyn", "Hello")
异步:
dsBridge.call("testAsyn","hello", function (v) {
alert(v)
})
*/
call: function (method, args, cb) {
var ret = '';
if (typeof args == 'function') {
cb = args;
args = {};
}
var arg={data:args===undefined?null:args}//定义arg对象
//异步(通过传方法来实现异步)
if (typeof cb == 'function') { //如果cb参数是一个方法
var cbName = 'dscb' + window.dscb++; //cbName = dscb1、dscb2、dscb3...
window[cbName] = cb; //window.dscb1 = cb,因此可以调用 dscb1(v)、dscb2(v)等函数,对应 function callAsyn() {
// dsBridge.call("testAsyn","testAsyn", function (v) {
// alert(v)
// })
// } 中的 function(v){}
arg['_dscbstub'] = cbName; //arg对象中添加一个属性 _dscbstub = cbName
}
arg = JSON.stringify(arg) //arg转为json
//if in webview that dsBridge provided, call!
if(window._dsbridge){//是否注入过 _dsbridge对象
//调用已有的_dsbridge的call()方法
ret= _dsbridge.call(method, arg)
}else if(window._dswk||navigator.userAgent.indexOf("_dsbridge")!=-1){//如果注入过_dswk对象,或者 userAgent的最后是_dsbridge
//通过prompt进行通信(客户端的runJavaScriptTextInputPanelWithPrompt协议里面会收到:_dsbridge=xxx 和 arg参数)
ret = prompt("_dsbridge=" + method, arg);
}
return JSON.parse(ret||'{}').data//数据格式转化
},
/*
注册方法给客户端调用
[self callHandler:@"_hasJavascriptMethod" arguments:@[handlerName] completionHandler:^(NSNumber* _Nullable value) {
callback([value boolValue]);
}];
*/
register: function (name, fun, asyn) {
//根据asyn确定同步还是异步(默认是同步调用)
var q = asyn ? window._dsaf : window._dsf
if (!window._dsInit) {
window._dsInit = true;
//notify native that js apis register successfully on next event loop
//延迟0s执行 - 确保bridge未实例化而导致调用失败(同步执行有风险)
setTimeout(function () {
bridge.call("_dsb.dsinit");
}, 0)
}
//将注册给native调用方法对应放到_dsaf|_dsf容器里面
if (typeof fun == "object") {
q._obs[name] = fun;
} else {
q[name] = fun
}
},
/*
内置事件:
dsBridge.registerAsyn('append', function (arg1, arg2, arg3, responseCallback) {
responseCallback(arg1 + " " + arg2 + " " + arg3);
})
*/
registerAsyn: function (name, fun) {
this.register(name, fun, true);
},
/*
内置事件:
dsBridge.hasNativeMethod(name)
*/
hasNativeMethod: function (name, type) {
return this.call("_dsb.hasNativeMethod", {name: name, type:type||"all"});
},
/*
内置事件:
*/
disableJavascriptDialogBlock: function (disable) {
this.call("_dsb.disableJavascriptDialogBlock", {
disable: disable !== false
})
}
};
//立即执行函数
!function () {
//防止重复注入
if (window._dsf) return;
var _close=window.close;
//申明一个ob对象
var ob = {
//保存JS同步方法
_dsf: {
_obs: {}
},
//保存JS异步方法
_dsaf: {
_obs: {}
},
dscb: 0,
dsBridge: bridge,
//注入bridge对象:属性名为:dsBridge
dsBridge: bridge,
close: function () {
//_dsb是跟iOS前端约定好的内置命名空间事件:[self addJavascriptObject:interalApis namespace:@"_dsb"];
if(bridge.hasNativeMethod('_dsb.closePage')){
bridge.call("_dsb.closePage")
}else{
_close.call(window)
}
},
/*
oc调用同步js的addValue方法
// namespace syn test
[dwebview callHandler:@"syn.addValue" arguments:@[@55,@6] completionHandler:^(NSDictionary * _Nullable value) {
NSLog(@"Namespace syn.addValue(5,6): %@",value);
}];
客户端调用js方法
NSString * json=[JSBUtil objToJsonString:@{@"method":info.method,@"callbackId":info.id,
@"data":[JSBUtil objToJsonString: info.args]}];
[self evaluateJavaScript:[NSString stringWithFormat:@"window._handleMessageFromNative(%@)",json]
completionHandler:nil];
eg: 同步:json = {"callbackId":0,"method":"syn.addValue","data":"[5,6]"}
异步:json = {"callbackId":0,"method":"asyn.addValue","data":"[5,6]"}
*/
_handleMessageFromNative: function (info) {
var arg = JSON.parse(info.data);
var ret = {
id: info.callbackId,//客户端传一个callbackId后续回传给客户端方便从字典里面获取对应的block
complete: true
}
var f = this._dsf[info.method];
var af = this._dsaf[info.method]
var callSyn = function (f, ob) {
//f劫持ob对象的方法和属性,传递arg参数
ret.data = f.apply(ob, arg)//等价于 ret.data = addValue(5,6) 即:ret.data = 11
//将结果再传给native
bridge.call("_dsb.returnValue", ret)
}
var callAsyn = function (f, ob) {
//arg追加参数(追加方法实现异步)
arg.push(function (data, complete) {
ret.data = data;
ret.complete = complete!==false;
bridge.call("_dsb.returnValue", ret)
})
f.apply(ob, arg)
}
if (f) {
callSyn(f, this._dsf);
} else if (af) {
callAsyn(af, this._dsaf);
} else {
//with namespace
var name = info.method.split('.');
if (name.length<2) return;
//获取方法名字
var method=name.pop();//取最后一个元素:方法名
var namespace=name.join('.')
var obs = this._dsf._obs;
var ob = obs[namespace] || {};
//obs容器里面获取方法
var m = ob[method];
//如果在同步的容器里面找到该方法 则同步执行
if (m && typeof m == "function") {
callSyn(m, ob);
return;
}
//接着在异步的容器里面找方法 找到则执行异步
obs = this._dsaf._obs;
ob = obs[namespace] || {};
m = ob[method];
if (m && typeof m == "function") {
callAsyn(m, ob);
return;
}
}
}
}
//将全部的属性赋值给window, 这样h5就可以通过window._dsf,window.dsBridge来访问,默认可以不写window. 直接_dsf或者dsBridge就可以访问
for (var attr in ob) {
window[attr] = ob[attr]
}
/*
立即调用一个_hasJavascriptMethod方法 给客户端调用
[self callHandler:@"_hasJavascriptMethod" arguments:@[handlerName] completionHandler:^(NSNumber* _Nullable value) {
callback([value boolValue]);
}];
*/
bridge.register("_hasJavascriptMethod", function (method, tag) {
var name = method.split('.')
//命名空间是通过类似倒置域名的方式来实现:比如上面的 bridge.call("_dsb.dsinit");
//没有命名空间 则method就是方法名:dsinit
if(name.length<2) {
return !!(_dsf[name]||_dsaf[name])
}else{
// with namespace
var method=name.pop()//取最后一个元素:dsinit
var namespace=name.join('.')
//容器里面获取是否已注册方法
var ob=_dsf._obs[namespace]||_dsaf._obs[namespace]
return ob&&!!ob[method]
}
})
}();
网友评论