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工程运行效果如下:
关于JSAPI的接口封装
JSAPI的封装包括核心JS和对外开放接口JS两个部分。 核心JS部分通过拦截某Q的wap请求页面获取,获取的JS进行编码混淆处理,已经通过调试进行了注释,其主要过程就是对参数和回调进行封装,并构建一个url链接通过创建一个隐藏的iframe进行发送。核心JS代码阅读
对参数和回调进行封装部分的代码如下:
//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); //带上存储回调的数组index;
//执行生成的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调用请求:
//创建一个iframe,执行src,供拦截
function N(n, r) {
console.log("logOpenURL:>>" + n);
var i = document.createElement("iframe");
i.style.cssText = "display:none;width:0px;height:0px;";
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
}
对外开放接口的封装:(使用者只需要对该部分进行接口扩展即可)
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代码调用说明
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的引擎原理如图所示:
一般app中都有自己定制的Webcontainer,为了更好的跟已有项目相融合,在Cordova的基础上我们进行了简化,通过JSAPIService服务的方式进行插件扩展开发如图所示:
本JSBridge是基于Phonegap的Cordova引擎的基础上简化而来, Android平台Webview和JS的交互方式共有三种:
ExposedJsApi:js直接调用java对象的方法;(同步)
重载chromeClient的prompt 截获方案;(异步)
url截获+webview.loadUrl回调的方案;(异步)
为了和IOS保持一致的JSAPI,只能选用第三套方案;
基于JSService的插件开发、配置和使用
IOS平台
git地址:https://github.com/Lede-Inc/LDJSBridge_IOS.git
在Native部分,定义一个模块插件对应于创建一个插件类, 模块中的每个插件接口对应插件类中某个方法。
集成LDJSBridge_IOS框架之后,只需要继承框架中的插件基类LDJSPlugin,如下所示:
插件接口定义
#import "LDJSPlugin.h"
@interface LDPDevice : LDJSPlugin
{}
//@func 获取设备信息
- (void)getDeviceInfo:(LDJSInvokedUrlCommand*)command;
@end
自定义插件接口实现
@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插件的统一配置
{
"update": "",
"module": "mapp",
"plugins": [
{
"pluginname": "device",
"pluginclass": "LDPDevice",
"exports": [
{
"showmethod": "getDeviceInfo",
"realmethod": "getDeviceInfo"
}
]
}
]
}
在webContainer中对JSService初始化, 当初始化完成之后,向前端页面发送一个ReadyEvent,前端即可开始调用JSAPI接口;
//注册插件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平台
git地址:https://github.com/Lede-Inc/LDJSBridge_Android.git
插件接口定义
public class LDPDevice extends LDJSPlugin {
public static final String TAG = "Device";
/**
* Constructor.
*/
public LDPDevice() {
}
}
LDJSPlugin 属性方法说明
/**
* 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;
}
}
自定义插件接口实现
@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中注册服务并调用:
JSBridge(Android和IOS平台)的设计和实现的更多相关文章
- 学习笔记:APP切图那点事儿–详细介绍android和ios平台
学习笔记:APP切图那点事儿–详细介绍android和ios平台 转载自:http://www.woofeng.cn/articles/168.html 版权归原作者所有 作者:亚茹有李 原文地址 ...
- 多媒体开发(7):编译Android与iOS平台的FFmpeg
编译FFmpeg,一个古老的话题,但小程还是介绍一遍,就当记录.之前介绍怎么给视频添加水印时,就已经提到FFmpeg的编译,并且在编译时指定了滤镜的功能. 但是,在手机盛行的时代,读者可能更需要的是能 ...
- Android、iOS平台RTMP/RTSP播放器实时音量调节
介绍移动端RTMP.RTSP播放器实时音量调节之前,我们之前也写过,为什么windows播放端加这样的接口,windows端播放器在多窗口大屏显示的场景下尤其需要,尽管我们老早就有了实时静音接口,相对 ...
- 经典好文:android和iOS平台的崩溃捕获和收集
通过崩溃捕获和收集,可以收集到已发布应用(游戏)的异常,以便开发人员发现和修改bug,对于提高软件质量有着极大的帮助.本文介绍了iOS和android平台下崩溃捕获和收集的原理及步骤,不过如果是个人开 ...
- 教你pomeloclient包libpomelo增加cocos2d-x 3.0工程(Windows、Android、IOS平台)
Windows平台 操作系统:Windows7(64-bit) VS版本号:2013 Cocos2d-x版本号:3.0 project路径:E:\cocos2d-prj\ 1.从github下载lib ...
- android和iOS平台的崩溃捕获和收集
转自:http://www.cnblogs.com/lancidie/archive/2013/04/13/3019349.html 通过崩溃捕获和收集,可以收集到已发布应用(游戏)的异常,以便开发人 ...
- JS对于Android和IOS平台的点击响应的适配
IOS点击事件 Click 300毫秒点击延迟 解决办法: 参考:http://cuiqingcai.com/1687.html 可判断设备 if (/(iPhone|iPad|iPod|iOS)/i ...
- ReactNative: Android与iOS平台兼容处理
方法一: 创建不同的文件扩展名:*.android.js*.io.js 方法二: import { Platform } from 'react-native'; if (Platform.OS == ...
- Unity在Android和iOS中如何调用Native API
本文主要是对unity中如何在Android和iOS中调用Native API进行介绍. 首先unity支持在C#中调用C++ dll,这样可以在Android和iOS中提供C++接口在unity中调 ...
随机推荐
- Makefile的简单例子
1.生成test可执行文件,源文件有prog.c prog.h cord.h test:prog.o code.o gcc -o test prog.o code.o prog.o:prog.c pr ...
- css详解笔记
CSS中的块级元素与行内元素 块级元素特性: 1.占据一整行,总是重起一行并且后面的元素也必须另起一行显示. 2.内联元素特性: 3.和其他内联元素显示在同一行. 块级元素列举如下: div(文档分区 ...
- [BZOJ 1221] [HNOI2001] 软件开发 【费用流 || 三分】
题目链接:BZOJ - 1221 题目分析 算法一:最小费用最大流 首先这是一道经典的网络流问题.每天建立两个节点,一个 i 表示使用毛巾,一个 i' 表示这天用过的毛巾. 然后 i 向 T 连 Ai ...
- [BZOJ 2440] [中山市选2011] 完全平方数 【二分 + 莫比乌斯函数】
题目链接:BZOJ - 2440 题目分析 首先,通过打表之类的方法可以知道,答案不会超过 2 * k . 那么我们使用二分,对于一个二分的值 x ,求出 [1, x] 之间的可以送出的数有多少个. ...
- API通常的url语法
?后面带的是get方式传递的值,如果有多个值,用 & 号分割.另外正式项目一般不用get方式传递,容易被人sql注入,即所谓的入侵. 详细看这篇http://www.cnblogs.com/k ...
- 深入了解一下PYTHON中关于SOCKETSERVER的模块-D
在LINUX上以FORK的方式使用SIMPLEHTTPSERVER. 记得哟,THREADING能在WIN和LINUX上实现,但FORK,不支持WIN的. 除了SIMPLE之外,还有CGI,就不示例啦 ...
- 深入了解一下PYTHON中关于SOCKETSERVER的模块-A
有了这块知识,应该对各类WEB框架有更好的理解吧..FLASK,DJANGO,WEBPY.... #!/usr/bin/env python from BaseHTTPServer import HT ...
- ckeditor与ckfinder简单整合使用
Ckeditor与ckfinder简单整合使用 功能:主要用来发送图文的email,图片上传到本地服务器,但是email的图片地址要写上该服务器的远程地址(图片地址:例如:http://www.bai ...
- 基于android的Socket通信
一.Socket通信简介 Android与服务器的通信方式主要有两种,一是Http通信,一是Socket通信.两者的最大差异在于,http连接使用的是“请求—响应方式”,即在请求时建立连接通道,当客户 ...
- 《STL源码剖析》chapter2空间配置器allocator
为什么不说allocator是内存配置器而说是空间配置器,因为空间不一定是内存,也可以是磁盘或其他辅助介质.是的,你可以写一个allocator,直接向硬盘取空间.sgi stl提供的配置器,配置的对 ...