简述
结合了原生应用(Native App)和网页应用(Web App)的开发方式。它允许开发者利用HTML5、CSS3和JavaScript等Web技术来编写应用程序,同时也可以调用原生API以实现更丰富的功能和更优的性能。简而言之就是原生提供壳+底层能力api,页面由h5开发。可以做到快速迭代,符合产品业务快速上线的需求。
优点
- 跨平台性:可以同时在iOS和Android等多个平台上运行,节省了开发成本和时间。
- 快速迭代:使用Web技术,可以快速开发和部署更新,不需要经过应用商店的审核流程。
- 开发效率:利用现有的Web开发技能和工具,可以快速构建应用原型和功能。
- 成本效益:相比于原生开发,混合开发可以减少人力成本,因为不需要为每个平台分别开发。
- 易于维护:由于代码复用率高,维护和更新起来更加方便。
- 即时更新:可以像网页一样随时更新内容,不需要用户下载更新。
缺点
- 性能问题:混合应用的性能通常不如原生应用,尤其是在复杂的用户界面和动画效果上。
- 用户体验:可能无法提供与原生应用完全一致的用户体验,特别是在触摸反馈和手势操作上。
- 依赖原生功能:对于一些特定的原生功能,如复杂的图形处理或硬件级别的操作,可能需要额外的插件或定制开发。
- 插件依赖性:混合开发通常依赖于第三方插件来实现原生功能,这些插件可能存在兼容性问题或不再维护的风险。
- 学习曲线:虽然对于Web开发者来说更容易上手,但对于不熟悉Web技术的开发者来说,仍然存在一定的学习曲线。
架构设计
原生&h5交互流程图
桥sdk代码设计
- 原生部分(hramony为例)
// 初始化
private initBaseBridges() {
this.registerBridge('InnerBridge',InnerBridge)
this.registerBridge('InnerWebRouterBridge',InnerWebRouterBridge)
this.registerBridge('HttpBridge',HttpBridge)
}
// 加载桥方法(具体功能)
public registerBridge(name: string, plugin: typeof BaseBridge) {
this.bridges.set(name, plugin);
// 扫描插件类下的方法,并添加到 pluginMethodMaps
const methods = Object.getOwnPropertyNames(plugin.prototype);
for (const method of methods) {
if (method !== 'constructor' && typeof plugin.prototype[method] === 'function') {
this.pluginMethodMaps.set(method, name);
}
}
}
// 桥方法例子-getVersion
public getVersion(entry: BridgeEntry): string {
const params: string = entry.params as string
Log.i(InnerBridge.TAG, 'getVersion');
new Promise(async (resolve, reject) => {
const obj = {}
obj['status'] = "ok"
obj['msg'] = "ok"
try {
const version = await AppHelper.getAppVersion()
obj['version'] = version
} catch (error) {
obj['status'] = "ok"
obj['msg'] = error.message
Log.error(InnerBridge.TAG, error);
}
resolve(obj)
}).then(result => {
entry.callBack({ result: JSON.stringify(result) })
}).catch(err => {
let error = ErrorManager.generateCustomErrorMessage(InnerError.getError(InnerErrorName.UNKNOWN_ERROR)
.code, err.getMessage())
entry.callBack({ error })
})
return ""
}
// 初始化webview容器时挂载桥对象(注入到页面中,提供给h5调用)
Web({ src: this.src, controller: this.webController })
.javaScriptProxy({
object: this.bridgeCaller,
name: getName(),
methodList: getMethods(),
controller: this.webController
})
getName(): string {
return "bradge_harmony";
}
getMethods(): string[] {
let methods: string[] = ['bradge_check', 'bradge_call'];
return methods;
}
// 暴露给h5的方法
// bradge_check用于检查桥功能是否存在
bradge_check: Function = (message: string) => {
LogUtil.getInstance().info(message)
var obj = {}
let bridgeTrace: BridgeTrace = new BridgeTrace(this.bridgeEnv?.mPageActionId);
try {
//解码前端桥调用消息
var argObj = JSON.parse(message)
//获取方法名称
let componentName: string | undefined = undefined;
componentName = argObj['bradge_call_func']
bridgeTrace.actionName = componentName
obj['version'] = 1
obj['status'] = 'error'
const hasMethod = bradgeApp.getInstance().getBridgeManager().contains(componentName);
if (hasMethod) {
obj['status'] = 'ok'
}
LogUtil.getInstance().info("bradge_check:"+JSON.stringify(obj))
//分发到各个桥
return JSON.stringify(obj);
} catch (e) {
LogUtil.getInstance()
.error(`[BridgeTrace] ${bridgeTrace.actionName} err: ${JSON.stringify(e)}`)
}
return "";
}
// bradge_call 调用桥功能并回调h5的callback
bradge_call: Function = (message: string) => {
LogUtil.getInstance().info(message)
let bridgeTrace: BridgeTrace = new BridgeTrace(this.bridgeEnv?.mPageActionId);
try {
//获取传递的数据
var argObj = JSON.parse(message)
let actionName: string | undefined = undefined;
actionName = argObj['bradge_call_func'];
bridgeTrace.actionName = actionName
LogUtil.getInstance()
.info(`[BridgeTrace] ${actionName} [aId:${bridgeTrace.pageActionId}, bId:${bridgeTrace.bridgeId}]-> start time=${bridgeTrace.startTime}`)
let callback: string | undefined = undefined;
callback = argObj['bradge_callback_func'];
let callbackId: string | undefined = undefined;
callbackId = argObj['bradge_callback_id'];
const bridgeContext = new BridgeContext(this.bridgeEnv, callback, callbackId,bridgeTrace);
const entry: BridgeEntry = new BridgeEntry(bridgeContext, message,this.webControllerProxy);
//分发到各个桥
const handleData = BradgeApp.getInstance().getBridgeManager().handle(actionName, entry);
return handleData.toString()
} catch (e) {
LogUtil.getInstance().error(`[BridgeTrace] ${bridgeTrace.actionName} err: ${JSON.stringify(e)}`)
}
return null;
}
- js-bradge部分
// 提供给业务页面调用的方法
/**
* @param args 参数
* @param call_func 调用的方法名称
* @param callback 回调方法
*/
const callPax = function callPax(args, call_func, callback) {
const arg = genArgs(args, call_func, callback);
// log(arg);
return genPromise(arg, call_func);
};
/**
* 统一处理传入参数和回调函数
* @param args 原参数
* @param call_func 调用的方法名称
* @param callback 回调方法
* @returns {*} 处理后的参数
*/
// eslint-disable-next-line camelcase
const genArgs = function genArgs(args, call_func, callback) {
// log(args);
if (args) {
// do nothing
} else {
args = {};
}
if (isUndefined(args)) {
args = {};
} else if (typeof args === 'string') {
try {
args = jsan.parse(args);
} catch (e) {
// log(e);
args = {};
}
} else if (isObject(args)) {
// log('isObj', args);
}
args.bradge_call_func = call_func;
args.bradge_call_origin = window.location.origin;
if (typeof callback === 'function') {
const id = genGUID();
args.bradge_callback_id = id;
const func_name = `func_${genGUID()}`;
args.bradge_callback_func = func_name;
window[id] = {};
window[id][func_name] = function func_name(value) {
try {
try {
reporter.log(`bradge回调流水[method:${call_func},cbId:${id},cbName:${args.bradge_callback_func}];v:${isString(value) ? value : jsan.stringify(value)}`);
} catch (e) {
reporter.log(`bradge回调流水[method:${call_func},cbId:${id},cbName:${args.bradge_callback_func}];v:${value}`);
}
// 这里可以加入更多的日志采集信息
// log('回调函数被调用', id, func_name);
// log('回调函数被调用,返回值', value);
let obj = {};
if (typeof value === 'string') {
try {
obj = jsan.parse(value);
} catch (e) {
obj = value;
}
} else {
obj = value;
}
callback(obj);
} finally {
// 回调方法时,移除该id对应的所有方法
eval(`delete window["${id}"]`);
}
};
}
return jsan.stringify(args);
};
/**
* 统一处理方法调用
* @param args 原参数
* @param call_func 调用的方法名
* @returns {Promise}
*/
const genPromise = function genPromise(args, call_func) {
let argObject = {};
if (isAndroid()) {
eval(`bradge_android.bradge_call(${jsan.stringify(args)})`);
return Promise.resolve()
}
if (isIos()) {
eval(`window.webkit.messageHandlers.bradge_call.postMessage({"key":${jsan.stringify(args)}})`);
return Promise.resolve()
}
};