网页中如何用js 控制ios与安卓手机振动

场景:现在有一个H5活动页面,上面有一个登陆按钮,要求点击登陆按钮以后,唤出App内部的登录界面,当登录成功以后将用户的手机号返回给H5页面,显示出来。这个场景应该算是比较完整的一次H5中的JavaScript与App原生代码进行交互了,这个过程,我们制定的方案满足以下几点:
满足基本的交互流程的功能
Android与iOS都能适用
H5的前端开发者,在书写JavaScript的业务代码的时候不需要为了迁就移动端语言的特性而写特殊的磨合代码
当H5页面上的JavaScript代码要调用原生的页面或者组件的时候,调用最好是双向的,一来一回,这样比较容易满足一些比较复杂的业务场景,就像上面的场景一样,有调用,有回调告知H5调用的结果。前端开发写的JavaScript代码基本上都是异步风格的,就拿上面的场景,如果登录是H5前端的,那么这个流程就会是:
function loginClick() {
loginComponent.login(function (error,result) {
//处理登录完成以后的逻辑
var loginComponent = {
callBack:null,
"login":function (callBack) {
this.show();
this.callBack = callB
show:function (loginComponent) {
//登录组件显示的逻辑
confirm:function (userName,password) {
ajax.post('https://xxxx.com/login',function (error,result) {
if(this.callBack !== null){
this.callBack(error,result);
如果要改成调用原生登录,那么这个流程就应该是这样:
确定了流程,接下来就可以详细设计和实现
原生与JavaScript的桥梁
为了实现上述流程,并且能让H5的前端开发尽可能少的语法损失,我们需要构建一个JavaScript与原生App进行交互的桥梁,这个桥梁来处理与App的协议交互,兼容iOS与Android的交互实现。
Android与iOS都支持在打开H5页面的时候,向H5页面的window对象上注入一个JavaScript可以访问到的对象,Android端使用的是
webView.addJavascriptInterface(myJavaScriptInterface, “bridge”);
iOS则可以使用JavaScriptCore来完成:
#import &Foundation/Foundation.h&
#import &JavaScriptCore/JavaScriptCore.h&
@protocol PICBridgeExport &JSExport&
@interface PICBridge : NSObject&PICBridgeExport&
self.jsContext =
[self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
self.bridge =[[PICBridge alloc] init];
  这里面Android的myJavaScriptInterface与PICBridge都是作为与JavaScript进行通信的桥梁。  我们使用设计这个桥梁的时候,需要使用一个具体的语法约定和数据约定,比方说,当前端开发调用App登录的时候,他一定是希望就像调用其他JavaScript的组件一样,而登录的结果通过传入callBack的函数来完成,对于callBack函数,我们希望借助NodeJS的规范:
function(error,res) {
//回调函数第一个参数是错误,第二个参数是结果
以上我们可以看到,bridge必须有能力将前端开发写的JavaScript回调函数传入到App内部,然后App处理完逻辑以后通过回调函数来告知前端处理,并且这个需要通过约定好的数据格式来传递入参和返回值。为了完成双向通信,我们就需要在JavaScript设置一个bridge,原生再注入一个bridge,这两个bridge按照一定的数据约定来进行双向通信和分发逻辑。
原生端注入到JS当中的“桥”(iOS端)
通过使用JavaScriptCore这个库,我们能很容易的将JavaScript传入的回调函数在objective-c或者是swift端持有,并回去回调这个回调函数。
#import &Foundation/Foundation.h&
#import &JavaScriptCore/JavaScriptCore.h&
@protocol PICBridgeExport &JSExport&
JSExportAs(callRouter, -(void)callRouter:(JSValue *)requestObject callBack:(JSValue *)callBack);
@interface PICBridge : NSObject&PICBridgeExport&
-(void)addActionHandler:(NSString *)actionHandlerName forCallBack:(void(^)(NSDictionary * params,void(^errorCallBack)(NSError * error),void(^successCallBack)(NSDictionary * responseDict)))callB
需要说明的是,JavaScript没有函数参数标签的概念,JSExportAs是用来将objective-c的方法映射为JavaScript的函数。-(void)callRouter:(JSValue&)requestObject callBack:(JSValue&)callBack);这个方法是暴露给JavaScript端调用的。第一个参数requestObject是一个JavaScript对象,传入到objective-c中以后就可以转换为key-value结构的字典,那么这个字典的数据约定是:
'Method':'Login',
'Data':null
其中Method是App内部对外提供的API,而这个Data则是该API需要的入参。第二个参数是一个callBack函数,该类型的JSValue可以调用callWithArguments:方法来invoke这个回调函数。前面已经说明,回调函数的第一个参数是error,第二个参数是一个结果,而回调的结果我们也进行一下约定,那就是:
'result':{}
这样的好处是,业务逻辑可以讲返回的结果放入result中,跟result同级别的我们还可以加入统一的签名认证的东西,在此暂时不延伸。原生端的bridge的来实现一下callRouter:
-(void)callRouter:(JSValue *)requestObject callBack:(JSValue *)callBack{
NSDictionary * dict = [requestObject toDictionary];
NSString * methodName = [dict objectForKey:@"Method"];
if (methodName != nil && methodName.length&<span style="color: #) {
NSDictionary * params = [dict objectForKey:@"Data"];
__weak PICBridge * weakSelf =
//因为JavaScript是单线程的,需要尽快完成调用逻辑,耗时操作需要异步提交到主线程中执行
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf callAction:methodName params:params success:^(NSDictionary *responseDict) {
if (responseDict != nil) {
NSString * result = [weakSelf responseStringWith:responseDict];
if (result) {
[callBack callWithArguments:@[@"null",result]];
[callBack callWithArguments:@[@"null",@"null"]];
[callBack callWithArguments:@[@"null",@"null"]];
} failure:^(NSError *error) {
if (error) {
[callBack callWithArguments:@[[error description],@"null"]];
[callBack callWithArguments:@[@"App Inner Error",@"null"]];
[callBack callWithArguments:@[@NO,[PICError ErrorWithCode:PICUnkonwError].description]];
//将返回的结果字典转换为字符串通过回调函数传回给JavaScript
-(NSString *)responseStringWith:(NSDictionary *)responseDict{
if (responseDict) {
NSDictionary * dict = @{@"result":responseDict};
NSData * data = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:nil];
NSString * result = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
callAction函数实际上就是分发业务逻辑用的
-(void)callAction:(NSString *)actionName params:(NSDictionary *)params success:(void(^)(NSDictionary * responseDict))success failure:(void(^)(NSError * error))failure{
void(^callBack)(NSDictionary * params,void(^errorCallBack)(NSError * error),void(^successCallBack)(NSDictionary * responseDict)) = [self.handlers objectForKey:actionName];
if (callBack != nil) {
callBack(params,failure,success);
这个callBack Block是在self.handlers的字典中存储,比较复杂,block第一个参数是传入的入参,后面两个参数是成功以后的回调和失败以后的回调,以便业务逻辑完成后进行回调给JavaScript。同时会有注册业务逻辑的方法:
-(void)addActionHandler:(NSString *)actionHandlerName forCallBack:(void(^)(NSDictionary * params,void(^errorCallBack)(NSError * error),void(^successCallBack)(NSDictionary * responseDict)))callBack{
if (actionHandlerName.length&<span style="color: # && callBack != nil) {
[self.handlers setObject:callBack forKey:actionHandlerName];
至此,原生端路由实现完毕。
JavaScript端路由
(function(win) {
var ua = navigator.userA
function getQueryString(name) {
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
var r = window.location.search.substr(<span style="color: #).match(reg);
if (r !== null) return unescape(r[<span style="color: #]);
return null;
function isAndroid() {
return ua.indexOf('Android') & <span style="color: #;
function isIOS() {
return /(iPhone|iPad|iPod)/i.test(ua);
var mobile = {
*通过bridge调用app端的方法
* @param method
* @param params
* @param callback
callAppRouter: function(method, params, callback) {
var req = {
'Method': method,
'Data': params
if (isIOS()) {
win.bridge.callRouter(req, function(err, result) {
var resultObj = null;
var errorMsg = null;
if (typeof(result) !== 'undefined' && result !== 'null' && result !== null) {
resultObj = JSON.parse(result);
if (resultObj) {
resultObj = resultObj['result'];
if (err !== 'null' && typeof(err) !== 'undefined' && err !== null) {
errorMsg =
callback(err, resultObj);
} else if (isAndroid()) {
//生成回调函数方法名称
var cbName = 'CB_' + Date.now() + '_' + Math.ceil(Math.random() * <span style="color: #);
//挂载一个临时函数到window变量上,方便app回调
win[cbName] = function(err, result) {
var resultO
if (typeof(result) !== 'undefined' && result !== null) {
resultObj = JSON.parse(result)['result'];
callback(err, resultObj);
//回调成功之后删除挂载到window上的临时函数
delete win[cbName];
win.bridge.callRouter(JSON.stringify(req), cbName);
login: function() {
// body...
this.callAppRouter('Login', null, function(errMsg, res) {
// body...
if (errMsg !== null && errMsg !== 'undefined' && errMsg !== 'null') {
var name = res['phone'];
if (name !== 'undefined' && name !== 'null') {
var button = document.getElementById('loginButton');
button.innerHTML =
//将mobile对象挂载到window全局
win.webBridge =
})(window);
在window上挂在一个叫webBridge的对象,其他业务JavaScript可以通过webBridge.login来进行调用原生端开放的API。callAppRouter方法的实现我们来分析一下:如果判断是iOS设备,则使用iOS注册的bridge对象进行调用callRouter方法:
if (isIOS()) {
win.bridge.callRouter(req, function(err, result) {
var resultObj = null;
var errorMsg = null;
if (typeof(result) !== 'undefined' && result !== 'null' && result !== null) {
resultObj = JSON.parse(result);
if (resultObj) {
resultObj = resultObj['result'];
if (err !== 'null' && typeof(err) !== 'undefined' && err !== null) {
errorMsg =
callback(err, resultObj);
req是标准的包含Method和Data的对象,紧接着传入回调函数,回调函数有err与result,里面做好各种类型检查。着重说一下Android端的实现,因为Android端的JavaScript方法注册,参数类型只能字符串,java语言本身没有匿名函数的概念,所以只能给Java端传入回调函数的名字,而回调函数的实现则在JavaScript端持有。
else if (isAndroid()) {
//生成回调函数方法名称
var cbName = 'CB_' + Date.now() + '_' + Math.ceil(Math.random() * <span style="color: #);
//挂载一个临时函数到window变量上,方便app回调
win[cbName] = function(err, result) {
var resultO
if (typeof(result) !== 'undefined' && result !== null) {
resultObj = JSON.parse(result)['result'];
callback(err, resultObj);
//回调成功之后删除挂载到window上的临时函数
delete win[cbName];
win.bridge.callRouter(JSON.stringify(req), cbName);
本质上就是将其他业务JavaScript代码传入的callBack函数通过随机生成函数名,挂在到window变量上,回调以后将其删除:delete win[cbName]。当调用Java端的bridge.callRouter(JSON.stringify(req), cbName),Java端拿到cbName,在完成业务逻辑后,按照标准数据格式,在JavaScript执行的上下文中,回调这个名字的方法。至此,前端的webBridge完成。
最后附上Demo地址:
阅读(...) 评论()怎么通过html js打开手机APP,IOS和ANDROID的_百度知道
怎么通过html js打开手机APP,IOS和ANDROID的
我有更好的答案
你是想把网页打包手机软件吗?如果是的话,这个工具可以hbuilder,还有很多类似工具,这种比较简单。
为您推荐:
其他类似问题
&#xe675;换一换
回答问题,赢新手礼包&#xe6b9;
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。在 SegmentFault,学习技能、解决问题
每个月,我们帮助 1000 万的开发者解决各种各样的技术问题。并助力他们在技术能力、职业生涯、影响力上获得提升。
问题对人有帮助,内容完整,我也想知道答案
问题没有实际价值,缺少关键内容,没有改进余地
网页中如何用js 控制ios与安卓手机振动以下方法已经试过:
navigator.vibrate(500);
答案对人有帮助,有参考价值
答案没帮助,是错误的答案,答非所问
混合开发提供了相关 api
同步到新浪微博
分享到微博?
关闭理由:
删除理由:
忽略理由:
推广(招聘、广告、SEO 等)方面的内容
与已有问题重复(请编辑该提问指向已有相同问题)
答非所问,不符合答题要求
宜作评论而非答案
带有人身攻击、辱骂、仇恨等违反条款的内容
无法获得确切结果的问题
非开发直接相关的问题
非技术提问的讨论型问题
其他原因(请补充说明)
我要该,理由是:
在 SegmentFault,学习技能、解决问题
每个月,我们帮助 1000 万的开发者解决各种各样的技术问题。并助力他们在技术能力、职业生涯、影响力上获得提升。iOS 问题:现在做的应用需要加载html5 ,实现js交互 ,iOS和安卓能用同一套js代码吗 - Code4App.com
现在做的应用需要加载html5 ,实现js交互 ,iOS和安卓能用同一套js代码吗
共有 3 个回答
可以。。。
登录后方可回复
@M先森 : 用这个库吗WebViewJavascriptBridge
登录后方可回复
同问,我只知道客户端可以调用服务器js方法,服务器只能用native的方式和客户端进行交互
登录后方可回复
登录后方可回答JSBridge(Android和IOS平台)的设计和实现 - IOS - 伯乐在线
& JSBridge(Android和IOS平台)的设计和实现
对于商务类的app,随着app注册使用人数递增,app的运营者们就会逐渐考虑在应用中开展一些推广活动。大多数活动具备时效性强、运营时间短的特征,一般产品们和运营者们都是通过wap页面快速投放到产品的活动模块。Wap页面可以声文并茂地介绍活动,但活动的最终目标是通过获取特权、跳转进入本地功能模块,最后达成交易。如何建立wap页面和本地Native页面的深度交互,这就需要用到本文介绍的JSBridge。
此外一些平台类的产品,如大家每天都在使用的微信、支付宝、手机qq等,无一例外都在使用集成JSBridge的webContainer完成众多业务组件功能,大大减少了客户端Native开发的工作量,不仅节约了大量人力开发成本,还能避开产品上线更新的版本审核周期限制(特别是IOS平台)。当然这些超级APP有强大的技术力量支撑,通过JSBridge有计划的进行API规范接口,不断向前端Wap开发人员开放,并在版本上向下兼容。但对于我们刚起步运营的中小级app来说暂时还没有必要如此大张旗鼓,相反前面提到的wap活动推广则是我们的主要需求。
为了满足这个需求,本文通过提炼JSBridge的核心部分改造成JSService方式供各个不同的产品零修改方式使用。各个不同的产品只需要按照插件的方式提供Native扩展接口,并在各自封装的webContainer中调用JSService对Wap调用进行拦截处理。
具体产品应用
目前该框架同时覆盖了Android和IOS平台,在我司的几个电商类产品中都得到了很好的使用,并趋于稳定。
本文的Demo工程运行效果如下:
jsapidemo_ios.png
jsapidemo_android.jpg
关于JSAPI的接口封装
JSAPI的封装包括核心JS和对外开放接口JS两个部分。 核心JS部分通过拦截某Q的wap请求页面获取,获取的JS进行编码混淆处理,已经通过调试进行了注释,其主要过程就是对参数和回调进行封装,并构建一个url链接通过创建一个隐藏的iframe进行发送。
对参数和回调进行封装部分的代码如下:
Objective-C
//mapp.invoke("device", "getDeviceInfo", e);
//@param e 类 必须
//@param n 类方法 必须
//@param i 同步回调的js方法
//@param s
function k(e, n, i, s) {
if (!e || !n)
i = r.call(arguments, 2), //相当于调用Array.prototype.slice(arguments) == arguments.slice(2),获取argument数组2以后的元素
//令s等于回调函数
s = i.length && i[i.length - 1],
s && typeof s == "function" ? i.pop() : typeof s == "undefined" ? i.pop() : s = null,
//u为当前存储回调函数的index;
//如果当前版本支持Bridge
if (C(e, n)) {
//将传进来的所有参数生成一个url字符串;
o = "ldjsbridge:" + "/" + "/" + encodeURIComponent(e) + "/" + encodeURIComponent(n),
i.forEach(function(e, t) {
typeof e == "object" && (e = JSON.stringify(e)),
t === 0 ? o += "?p=": o += "&p" + t + "=",
o += encodeURIComponent(String(e))
(o += "#" + u); //带上存储回调的数组
//执行生成的url, 有些函数是同步执行完毕,直接调用回调函数;而有些函数的调用要通过异步调用执行,需要通过
//全局调用去完成;
var f = N(o);
if (t.iOS) {
f = f ? f.result:
if (!s) //如果无回调函数,直接返回结果;
console.log("mappapi: the version don't support mapp." + e + "." + n);
1234567891011121314151617181920212223242526272829303132333435363738394041
//invoke&&&&//mapp.invoke("device", "getDeviceInfo", e);&&&&//@param e 类 必须&&&&//@param n 类方法 必须&&&&//@param i 同步回调的js方法&&&&//@param s &&&&function k(e, n, i, s) {&&&&&&&&if (!e || !n) return null;&&&&&&&&var o, u;&&&&&&&&i = r.call(arguments, 2), //相当于调用Array.prototype.slice(arguments) == arguments.slice(2),获取argument数组2以后的元素&&&&&&&&&//令s等于回调函数&&&&&&&&s = i.length && i[i.length - 1],&&&&&&&&s && typeof s == "function" ? i.pop() : typeof s == "undefined" ? i.pop() : s = null,&&&&&&&&&//u为当前存储回调函数的index;&&&&&&&&u = b(s);&&&&&&&&&//如果当前版本支持Bridge&&&&&&&&if (C(e, n)) {&&&&&&&&&&&&//将传进来的所有参数生成一个url字符串;&&&&&&&&&&&&o = "ldjsbridge:" + "/" + "/" + encodeURIComponent(e) + "/" + encodeURIComponent(n),&&&&&&&&&&&&i.forEach(function(e, t) {&&&&&&&&&&&&&&&&typeof e == "object" && (e = JSON.stringify(e)),&&&&&&&&&&&&&&&&t === 0 ? o += "?p=": o += "&p" + t + "=",&&&&&&&&&&&&&&&&o += encodeURIComponent(String(e))&&&&&&&&&&&&}),&&&&&&&&&&&&(o += "#" + u); //带上存储回调的数组&&&&&&&&&&&&&&//执行生成的url, 有些函数是同步执行完毕,直接调用回调函数;而有些函数的调用要通过异步调用执行,需要通过&&&&&&&&&&&&//全局调用去完成;&&&&&&&&&&&&&& var f = N(o);&&&&&&&&&&&&if (t.iOS) {&&&&&&&&&&&&&&&&f = f ? f.result: null;&&&&&&&&&&&&&&&&if (!s) return f; //如果无回调函数,直接返回结果;&&&&&&&&&&&&}&&&&&&&&}else {&&&&&&&&&&&&console.log("mappapi: the version don't support mapp." + e + "." + n);&&&&&&&&}&&&&}
创建iframe发送JSBridge调用请求:
Objective-C
//创建一个iframe,执行src,供拦截
function N(n, r) {
console.log("logOpenURL:&&" + n);
var i = document.createElement("iframe");
i.style.cssText = "display:width:0height:0";
var s = function() {
//通过全局执行函数执行回调函数;监听iframe是否加载完毕
result: "error"
//ios平台,令iframe的src为url,onload函数为全局回调函数
//并将iframe插入到body或者html的子节点中;
t.iOS && (i.onload = s, i.src = n);
var o = document.body || document.documentE
o.appendChild(i),
t.android && (i.onload = s, i.src = n);
var u = t.__RETURN_VALUE;
//当iframe执行完成之后,最后执行settimeout 0语句
return t.__RETURN_VALUE = e,
setTimeout(function() {
i.parentNode.removeChild(i)
123456789101112131415161718192021222324252627282930
&&&&//创建一个iframe,执行src,供拦截&&&&function N(n, r) {&&&&&&&&console.log("logOpenURL:&&" + n);&&&&&&&&var i = document.createElement("iframe");&&&&&&&&i.style.cssText = "display:width:0height:0";&&&&&&&&var s = function() {&&&&&&&&&&&&//通过全局执行函数执行回调函数;监听iframe是否加载完毕&&&&&&&&&&&&E(r, {&&&&&&&&&&&&&&&&r: -201,&&&&&&&&&&&&&&&&result: "error"&&&&&&&&&&&&})&&&&&&&&};&&&&&&&&&//ios平台,令iframe的src为url,onload函数为全局回调函数&&&&&&&&//并将iframe插入到body或者html的子节点中;&&&&&&&&t.iOS && (i.onload = s, i.src = n);&&&&&&&&var o = document.body || document.documentElement; &&&&&&&&o.appendChild(i),&&&&&&&&t.android && (i.onload = s, i.src = n);&&&&&&&&&//&&&&&&&&var u = t.__RETURN_VALUE;&&&&&&&&//当iframe执行完成之后,最后执行settimeout 0语句&&&&&&&&return t.__RETURN_VALUE = e,&&&&&&&&setTimeout(function() {&&&&&&&&&&&&i.parentNode.removeChild(i)&&&&&&&&},&&&&&&&&0),&&&&&&&&u&&&&}
对外开放接口的封装:(使用者只需要对该部分进行接口扩展即可)
Objective-C
mapp.build("mapp.device.getDeviceInfo", {
iOS: function(e) {
return mapp.invoke("device", "getDeviceInfo", e);
android: function(e) {
e = function(e) {
e = JSON.parse(e)
} catch(n) {}
mapp.invoke("device", "getDeviceInfo", e)
support: {
iOS: "1.0",
android: "1.0"
12345678910111213141516171819
mapp.build("mapp.device.getDeviceInfo", {&&&&iOS: function(e) {&&&&&&&&return mapp.invoke("device", "getDeviceInfo", e);&&&&},&&&&android: function(e) {&&&&&&&&var t = e;&&&&&&&&e = function(e) {&&&&&&&&&&&&try {&&&&&&&&&&&&&&&&e = JSON.parse(e)&&&&&&&&&&&&} catch(n) {}&&&&&&&&&&&&t && t(e)&&&&&&&&},&&&&&&&&mapp.invoke("device", "getDeviceInfo", e)&&&&},&&&&support: {&&&&&&&&iOS: "1.0",&&&&&&&&android: "1.0"&&&&}}),
核心JS代码调用说明
Objective-C
mapp.version: mappAPI自身版本号
mapp.iOS: 如果在ios app中,值为true
mapp.android: 如果在android app中,值为true
mapp.support: 检查当前app环境是否支持该接口,支持返回true
mapp.support("mqq.device.getClientInfo")
mapp.callback: 用于生成回调名字,跟着invoke参数传给客户端,供客户端回调
var callbackName = mapp.callback(function(type, index){
console.log("type: " + type + ", index: " + index);
mapp.invoke 方法:
mapp核心方法,用于调用客户端接口。
@param {String} namespace 命名空间
@param {String} method 接口名字
@param {Object/String} params 可选,API调用的参数
@param {Function} callback 可选,API调用的回调
* 调用普通的无参数接口:
mapp.invoke("ns", "method");
* 调用有异步回调函数的接口:
mapp.invoke("ns", "method", function(data){
console.log(data);
mapp.invoke("ns", "method", {
"params" : params
//参数通过json封装
"callback" : mapp.callback(handler), //生成回调名字
* 如果有多个参数调用:
mapp.invoke("ns", "method", param1, param2 /*,...*/,callback);
12345678910111213141516171819202122232425262728293031323334353637383940414243444546
mapp.version: mappAPI自身版本号&mapp.iOS: 如果在ios app中,值为true&mapp.android: 如果在android app中,值为true&mapp.support: 检查当前app环境是否支持该接口,支持返回true&&&&&mapp.support("mqq.device.getClientInfo")&mapp.callback: 用于生成回调名字,跟着invoke参数传给客户端,供客户端回调&&&&&var callbackName = mapp.callback(function(type, index){&&&&&&&&console.log("type: " + type + ", index: " + index);&&&&});&mapp.invoke 方法:&mapp核心方法,用于调用客户端接口。&&&&&&&&&@param {String} namespace 命名空间&&&&&&&&@param {String} method 接口名字&&&&&&&&@param {Object/String} params 可选,API调用的参数&&&&&&&&@param {Function} callback 可选,API调用的回调&* 调用普通的无参数接口:&&&&&&&&&mapp.invoke("ns", "method");&* 调用有异步回调函数的接口:&&&&&&&&&mapp.invoke("ns", "method", function(data){&&&&&&&&&&&&console.log(data);&&&&&&&&});&&&&&&&&&或&&&&&&&&&mapp.invoke("ns", "method", {&&&&&&&&&&&&"params" : params&& //参数通过json封装&&&&&&&&&&&&"callback" : mapp.callback(handler), //生成回调名字&&&&&&&&});&&* 如果有多个参数调用:&&&&&&&&&mapp.invoke("ns", "method", param1, param2 /*,...*/,callback);
JSService的具体实现-插件运行机制
JSService部分是基于Phonegap的Cordova引擎的基础上简化而来,其基本原理参照Cordova的引擎原理如图所示:
JSBridgeIOS_1.png
一般app中都有自己定制的Webcontainer,为了更好的跟已有项目相融合,在Cordova的基础上我们进行了简化,通过JSAPIService服务的方式进行插件扩展开发如图所示:
JSBridgeIOS_2.png
本JSBridge是基于Phonegap的Cordova引擎的基础上简化而来, Android平台Webview和JS的交互方式共有三种:
ExposedJsApi:js直接调用java对象的方法;(同步)
重载chromeClient的prompt 截获方案;(异步)
url截获+webview.loadUrl回调的方案;(异步)
为了和IOS保持一致的JSAPI,只能选用第三套方案;
基于JSService的插件开发、配置和使用
在Native部分,定义一个模块插件对应于创建一个插件类, 模块中的每个插件接口对应插件类中某个方法。
集成LDJSBridge_IOS框架之后,只需要继承框架中的插件基类LDJSPlugin,如下所示:
插件接口定义
Objective-C
#import "LDJSPlugin.h"
@interface LDPDevice : LDJSPlugin
//@func 获取设备信息
- (void)getDeviceInfo:(LDJSInvokedUrlCommand*)
&&&&#import "LDJSPlugin.h"&&&&@interface LDPDevice : LDJSPlugin&&&&{}&&&&&//@func 获取设备信息&&&&- (void)getDeviceInfo:(LDJSInvokedUrlCommand*)command;&&&&&@end
自定义插件接口实现
Objective-C
@implementation LDPDevice
*@func 获取设备信息
- (void)getDeviceInfo:(LDJSInvokedUrlCommand*)command{
//读取设备信息
NSMutableDictionary* deviceProperties = [NSMutableDictionary dictionaryWithCapacity:4];
UIDevice* device = [UIDevice currentDevice];
[deviceProperties setObject:[device systemName] forKey:@"systemName"];
[deviceProperties setObject:[device systemVersion] forKey:@"systemVersion"];
[deviceProperties setObject:[device model] forKey:@"model"];
[deviceProperties setObject:[device modelVersion] forKey:@"modelVersion"];
[deviceProperties setObject:[self uniqueAppInstanceIdentifier] forKey:@"identifier"];
LDJSPluginResult* pluginResult = [LDJSPluginResult resultWithStatus:LDJSCommandStatus_OK messageAsDictionary:[NSDictionary dictionaryWithDictionary:deviceProperties]];
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
12345678910111213141516171819202122
@implementation LDPDevice&/** *@func 获取设备信息 */- (void)getDeviceInfo:(LDJSInvokedUrlCommand*)command{&&&&//读取设备信息&&&&NSMutableDictionary* deviceProperties = [NSMutableDictionary dictionaryWithCapacity:4];&&&&&UIDevice* device = [UIDevice currentDevice];&&&&[deviceProperties setObject:[device systemName] forKey:@"systemName"];&&&&[deviceProperties setObject:[device systemVersion] forKey:@"systemVersion"];&&&&[deviceProperties setObject:[device model] forKey:@"model"];&&&&[deviceProperties setObject:[device modelVersion] forKey:@"modelVersion"];&&&&[deviceProperties setObject:[self uniqueAppInstanceIdentifier] forKey:@"identifier"];&&&&&LDJSPluginResult* pluginResult = [LDJSPluginResult resultWithStatus:LDJSCommandStatus_OK messageAsDictionary:[NSDictionary dictionaryWithDictionary:deviceProperties]];&&&&&[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];}&@end
在plugin.json文件中对plugin插件的统一配置
Objective-C
"update": "",
"module": "mapp",
"plugins": [
"pluginname": "device",
"pluginclass": "LDPDevice",
"exports": [
"showmethod": "getDeviceInfo",
"realmethod": "getDeviceInfo"
12345678910111213141516
{&&&&"update": "",&&&&"module": "mapp",&&&&"plugins": [&&&&&&&&{&&&&&&&&&&&&"pluginname": "device",&&&&&&&&&&&&"pluginclass": "LDPDevice",&&&&&&&&&&&&"exports": [&&&&&&&&&&&&&&&&{&&&&&&&&&&&&&&&&&&&&"showmethod": "getDeviceInfo",&&&&&&&&&&&&&&&&&&&&"realmethod": "getDeviceInfo"&&&&&&&&&&&&&&&&}&&&&&&&&&&&&]&&&&&&&&}&&&&]}
在webContainer中对JSService初始化, 当初始化完成之后,向前端页面发送一个ReadyEvent,前端即可开始调用JSAPI接口;
Objective-C
//注册插件Service
if(_bridgeService == nil){
_bridgeService = [[LDJSService alloc] initBridgeServiceWithConfig:@"PluginConfig.json"];
[_bridgeService connect:_webview Controller:self];
Called when the webview finishes loading.
This stops the activity view.
- (void)webViewDidFinishLoad:(UIWebView*)theWebView{
NSLog(@"Finished load of: %@", theWebView.request.URL);
//当webview finish load之后,发event事件通知前端JSBridgeService已经就绪
//监听事件由各个产品自行决定
[_bridgeService readyWithEvent:@"LDJSBridgeServiceReady"];
12345678910111213141516
//注册插件Service&&&&if(_bridgeService == nil){&&&&&&&&_bridgeService = [[LDJSService alloc] initBridgeServiceWithConfig:@"PluginConfig.json"];&&&&}&&&&[_bridgeService connect:_webview Controller:self];&&/** Called when the webview finishes loading.&&This stops the activity view. */- (void)webViewDidFinishLoad:(UIWebView*)theWebView{&&&&NSLog(@"Finished load of: %@", theWebView.request.URL);&&&&//当webview finish load之后,发event事件通知前端JSBridgeService已经就绪&&&&//监听事件由各个产品自行决定&&&&[_bridgeService readyWithEvent:@"LDJSBridgeServiceReady"];}
Android平台
插件接口定义
Objective-C
public class LDPDevice extends LDJSPlugin {
public static final String TAG = "Device";
* Constructor.
public LDPDevice() {
&&&&public class LDPDevice extends LDJSPlugin {&&&&&&&&&& public static final String TAG = "Device";&&&&&&&&&/**&&&&&&&&&&&& * Constructor.&&&&&&&&&&&& */&&&&&&&&&& public LDPDevice() {&&&&&&&&&& }&&&&&& }
LDJSPlugin 属性方法说明
Objective-C
* Plugins must extend this class and override one of the execute methods.
public class LDJSPlugin {
//在插件初始化的时候,会初始化当前插件所属的webview和controller
//供插件方法接口 返回处理结果
public WebView webV
public LDJSActivityInterface activityI
//所有自定义插件需要重载此方法
public boolean execute(String action, LDJSParams args, LDJSCallbackContext callbackContext) throws JSONException {
1234567891011121314151617
&& /**&&&&* Plugins must extend this class and override one of the execute methods.&&&&*/&&&&public class LDJSPlugin {&&&&&&&&&& public String id;&&&&&&&&&&& //在插件初始化的时候,会初始化当前插件所属的webview和controller&&&&&&&&//供插件方法接口 返回处理结果&&&&&&&&public WebView webView; &&&&&&&&public LDJSActivityInterface activityInterface;&&&&&&&&&//所有自定义插件需要重载此方法&&&&&&&&public boolean execute(String action, LDJSParams args, LDJSCallbackContext callbackContext) throws JSONException {&&&&&&&&&&&&&& return false;&&&&&&&&&& }&&&&&&& }
自定义插件接口实现
Objective-C
public boolean execute(String action, LDJSParams args, LDJSCallbackContext callbackContext) throws JSONException {
if (action.equals("getDeviceInfo")) {
JSONObject r = new JSONObject();
r.put("uuid", LDPDevice.uuid);
r.put("version", this.getOSVersion());
r.put("platform", this.getPlatform());
r.put("model", this.getModel());
callbackContext.success(r);
123456789101112131415
@Override&&&& public boolean execute(String action, LDJSParams args, LDJSCallbackContext callbackContext) throws JSONException {&&&&&&&& if (action.equals("getDeviceInfo")) {&&&&&&&&&&&& JSONObject r = new JSONObject();&&&&&&&&&&&& r.put("uuid", LDPDevice.uuid);&&&&&&&&&&&& r.put("version", this.getOSVersion());&&&&&&&&&&&& r.put("platform", this.getPlatform());&&&&&&&&&&&& r.put("model", this.getModel());&&&&&&&&&&&& callbackContext.success(r);&&&&&&&& }&&&&&&&& else {&&&&&&&&&&&& return false;&&&&&&&& }&&&&&&&& return true;&&&& }
在封装的webContainer中注册服务并调用:
Objective-C
* 初始化Activity,打开网页,注册插件服务
public void initActivity() {
//创建webview和显示view
createGapView();
createViews();
//注册插件服务
if(jsBridgeService == null){
jsBridgeService = new LDJSService(_webview, this, "PluginConfig.json");
//加载请求
if(this.url != null && !this.url.equalsIgnoreCase("")){
_webview.loadUrl(this.url);
* 初始化webview,如果需要调用JSAPI,必须为Webview注册WebViewClient和WebChromeClient
@SuppressLint("SetJavaScriptEnabled")
public void createGapView(){
if(_webview == null){
_webview = new WebView(LDPBaseWebViewActivity.this, null);
//设置允许webview和javascript交互
_webview.getSettings().setJavaScriptEnabled(true);
_webview.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
//绑定webviewclient
_webviewClient = new WebViewClient(){
public void onPageStarted(WebView view, String url, Bitmap favicon){
super.onPageStarted(view, url, favicon);
isWebviewStarted =
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
//发送事件通知前端
if(isWebviewStarted){
//在page加载之后,加载核心JS,前端页面可以在document.ready函数中直接调用了;
jsBridgeService.onWebPageFinished();
jsBridgeService.readyWithEventName("LDJSBridgeServiceReady");
isWebviewStarted =
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if(url.startsWith("about:")){
if(url.startsWith(LDJSService.LDJSBridgeScheme)){
//处理JSBridge特定的Scheme
jsBridgeService.handleURLFromWebview(url);
_webview.setWebViewClient(_webviewClient);
//绑定chromeClient
_webviewChromeClient = new WebChromeClient(){
public boolean onJsAlert(WebView view, String url, String message,
JsResult result) {
return super.onJsAlert(view, url, message, result);
_webview.setWebChromeClient(_webviewChromeClient);
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
&&/**&&&& * 初始化Activity,打开网页,注册插件服务&&&& */&&&&public void initActivity() {&&&&&&&&//创建webview和显示view&&&&&&&&createGapView();&&&&&&&&createViews();&&&&&&&&&//注册插件服务&&&&&&&&if(jsBridgeService == null){&&&&&&&&&&&&jsBridgeService = new LDJSService(_webview, this, "PluginConfig.json");&&&&&&&&}&&&&&&&&&//加载请求&&&&&&&&if(this.url != null && !this.url.equalsIgnoreCase("")){&&&&&&&&&&&&_webview.loadUrl(this.url);&&&&&&&&}&&&&}&&& /**&&&& * 初始化webview,如果需要调用JSAPI,必须为Webview注册WebViewClient和WebChromeClient&&&& */&&&&@SuppressLint("SetJavaScriptEnabled")&&&&public void createGapView(){&&&&&&&&if(_webview == null){&&&&&&&&&&&&_webview = new WebView(LDPBaseWebViewActivity.this, null);&&&&&&&&&&&&//设置允许webview和javascript交互&&&&&&&&&&&&_webview.getSettings().setJavaScriptEnabled(true);&&&&&&&&&&&&_webview.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);&&&&&&&&&&&&&//绑定webviewclient&&&&&&&&&&&&_webviewClient = new WebViewClient(){&&&&&&&&&&&&&&&&public void onPageStarted(WebView view, String url, Bitmap favicon){&&&&&&&&&&&&&&&&&&&&super.onPageStarted(view, url, favicon);&&&&&&&&&&&&&&&&&&&&isWebviewStarted = true;&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&&&&public void onPageFinished(WebView view, String url) {&&&&&&&&&&&&&&&&&&&&super.onPageFinished(view, url);&&&&&&&&&&&&&&&&&&&&&&&&//发送事件通知前端&&&&&&&&&&&&&&&&&&&&if(isWebviewStarted){&&&&&&&&&&&&&&&&&&&&&&&&//在page加载之后,加载核心JS,前端页面可以在document.ready函数中直接调用了;&&&&&&&&&&&&&&&&&&&&&&&&jsBridgeService.onWebPageFinished();&&&&&&&&&&&&&&&&&&&&&&&&&&&&jsBridgeService.readyWithEventName("LDJSBridgeServiceReady");&&&&&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&&&&&&&isWebviewStarted = false;&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&&&&&&@Override&&&&&&&&&&&&&&&&&&public boolean shouldOverrideUrlLoading(WebView view, String url) {&&&&&&&&&&&&&&&&&&&&&&&&&&if(url.startsWith("about:")){&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&return true;&&&&&&&&&&&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&&&&&&&&&&&if(url.startsWith(LDJSService.LDJSBridgeScheme)){&&&&&&&&&&&&&&&&&&&&&&&&&&&&//处理JSBridge特定的Scheme&&&&&&&&&&&&&&&&&&&&&&&&&&&&jsBridgeService.handleURLFromWebview(url);&&&&&&&&&&&&&&&&&&&&&&&&&&&&return true;&&&&&&&&&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&&&&&&&&&&&&return false;&&&&&&&&&&&&&&&&&&}&&&&&&&&&&&&};&&&&&&&&&&&&&_webview.setWebViewClient(_webviewClient);&&&&&&&&&&&&//绑定chromeClient&&&&&&&&&&&&_webviewChromeClient = new WebChromeClient(){&&&&&&&&&&&&&&&&@Override&&&&&&&&&&&&&&&&public boolean onJsAlert(WebView view, String url, String message,&&&&&&&&&&&&&&&&&&&&&&&&JsResult result) {&&&&&&&&&&&&&&&&&&&&return super.onJsAlert(view, url, message, result);&&&&&&&&&&&&&&&&}&&&&&&&&&&&&};&&&&&&&&&&&&_webview.setWebChromeClient(_webviewChromeClient);&&&&&&&&}&&&&}
第一次写博客,写得糙和不好的地方望见谅,本人将会不断改善和提高自身能力;所以本博客主要提供大概的解决方案,望能够和有需要的人士交流沟通具体实现方式的差异。

我要回帖

更多关于 安卓手机开发培训 的文章

 

随机推荐