介绍
市面上主流的hybrid app框架主要有
- React Native:由FaceBook开发,使用JavaScript和React来构建原生应用程序
- Flutter:由Google开发,使用Dart语言。Flutter使用自己的渲染引擎
- Ionic:基于 Web 技术(HTML、CSS 和 JavaScript),使用 Angular 框架。Ionic 提供了一组 UI 组件和工具,使开发人员能够构建跨平台移动应用程序。
还有hybrid app框架,但是我本人没有进行过多的涉及,这里就不展开了,想要进一步了解的友友们可以自行查阅资料哈。我在这主要是想讲讲使用hybrid app开发时的一些使用方案,并且结合自己现在做的,总结一下自己的心得。
要解决的问题
- web调用原生(实质是JavaScript调用Java)
- 原生调用web(实质是Java调用Java)
- 数据通道的搭建---性能及易用性
原生+webVview方案
这是最常用的Hybrid方案之一。应用的主要框架由原生代码构建,同时在应用的某些部分内嵌入WebView组件,用于显示Web页面加载Web应用。Web页面通过WebView运行,并可以与原生代码进行通信。通俗点来说,就是网页的模式,通常由“HTML5云网站+APP应用客户端”两部分构成。混合开发是一种取长补短的开发模式,原生代码部分利用WebView插件或者其他框架为H5提供容器。
优点:
- 开发效率高,节约时间。同一套代码在Android和IOS上基本都可以使用
- 更新迭代以及部署比较方便,每次升级版本只需要在服务器端升级即可,不需要再上次App Store进行审核
- 代码维护方便,版本更新快,节约产品成本
- 基于web,但是同时也可用拥有原生支持的业务
- 可离线运行
缺点:
- 功能/界面无法自定:所有内容都是固定的,不能换界面或者增加功能
- 加载缓慢/网络要求高:混合APP数据需要全部从服务器获取,每个页面都需要重新加载,因此打开速度慢,网络占用高,缓冲时间长,容易让用户反感
但是webview也是有一定的缺点的,即web应用的体验无法达到原生应用的体验。但是开发效率高,被很多app所用。比如京东、淘宝、今日头条等APP都是利用混合开发模式而成的。这也是目前笔者开发团队中开发APP时最经常使用到的方案。
H5和原生如何交互的呢?
H5与原生APP的交互指的是在原生APP中嵌入H5页面,是的用户可以在原生APP中直接访问H5页面并进行交互操作。H5与原生APP交互原理是通过webview实现的。那么webview又是什么呢?
webview
webview是Android和IOS系统中提供的一个组件,使得可以在原生APP中嵌入H5页面。webview可以加载HTML、CSS、JavaScript等web技术,同时也可以调用原生APP提供的API,实现与原生APP的交互
H5与原生交互方式
在H5页面中,可以通过JavaScript调用原生提供的API,实现与原生的交互。原生APP需要提供一个JavaScriptBridge类,用来接收H5页面发来的请求,并执行相应的操作。
我这里主要想讲一下如何应用第三方框架实现H5与原生之间的交互。目前比较流行的支持H5与原生App之间交互的框架有:WebViewJavaScriptBridge、JSBridge、HybridBridge等。这些框架都提供了API接口,方便H5页面与原生APP的交互,同时也提供了一些辅助功能,如:H5页面的路由跳转、原生APP的Toase提示、H5页面的Loading动画等。
笔者主要是从事前端开发的,那么也就是在进行Hybrid APP开发时负责的是H5页面的开发,然后我们团队用到的实现与原生APP之间交互的第三方框架主要是dsBridge,所以接下来我也主要围绕dsBridge展开,讲述H5与原生交互的一些主流程以及实际应用。
DSBridge
介绍
- 国内推出的JavaScript bridge跨平台混合开发框架
- 官方提供了Android/ios版本,真正实现跨平台
- DSBridge支持同步及异步调用(DSbridge是唯一一个支持同步调用的javascript bridge)
- 无需iFrame,性能好
- Github地址:
- IOS:GitHub - wendux/DSBridge-IOS: :earth_asia: A modern cross-platform JavaScript bridge, through which you can invoke each other's functions synchronously or asynchronously between JavaScript and native.
- Android:GitHub - wendux/DSBridge-Android: :earth_americas: A modern cross-platform JavaScript bridge, through which you can invoke each other's functions synchronously or asynchronously between JavaScript and native.
DSBridge的接入方式
原生端
- 直接源码接入
- 下载DSBridge源码,作为独立的Module接入工程
- Gradle依赖
- 配置Gradle脚本,Sync自动接入
H5端
- npm进行下载:npm install dsbridge -g
- cdn方式引入
- 直接下载源码,放到指定的js文件中进行引入(后文会贴上dsBridge的源码)
H5与原生交互的核心
在web和原生之间进行交互,如传递数据,调用函数,那么其交互的核心就是要解决双方之间的通信问题,且其中分为H5调用原生提供好的接口,和原生调用H5注册好的方法。那么H5和原生之间是如何通信的呢?
H5调用原生
steps:
- 原生在webview上注册方法,以提供JavaScript调用
- H5初始化DSBridge上下文环境(H5便可以使用DSBridge提供的一些api方法)
- H5端使用dsBridge.call()直接调用原生端提供的方法
原生端调用H5端
steps:
- 前端注册注册JavaScript方法以供原生端使用
- 客户端获取DWebView实例(DWebView:其实dsBridge对webview只做了一层封装,提供了dsBridge特有的JavaScript到Java的能力)
- 客户端通过callHandler函数调用H5注册的JavaScript方法,并且传入一个回调函数。
同步调用及异步调用
原生端注册同步异步方法的方式
- 同步调用:public object handler(object arg)
- 参数arg是给H5回传的数据
- 异步调用:public void handler(Object arg,CompletionHandler handler)
- 参数arg是给H5回传的数据
- handler是回调接口,在前端执行一定操作逻辑之后,通过handler去回调消息
原生提供给H5调用的方法
同步调用
1.原生端注册同步方法
注意点:
- 原生端注册同步方法,参数msg必填,h5可以不传
- 注解必须加上,h5端才能调用(dsbridge的一个安全措施)
2.在指定url的webview页面注入刚才写好的提供给H5调用的方法,这个才能实现真正供H5调用
3.H5端进行调用
- 初始化DSbridge(引入DSBridge)
- 使用dsBridge.call('方法名',obj) // obj是在指定方法中传给原生的参数
效果(这里实例的是安卓应用):
异步调用
步骤跟同步调用大同小异,这里就不展开。但是需要注意的是,这里呀原生异步注册时和H5调用时区别
H5注册方法供原生调用
同步调用
1.前端注册同步方法
2.原生端调用H5注册的同步方法
逻辑梳理:
1.这里loadUrl指明了webview指定页面,点击指定元素触发onClick方法
2.方法中调用了callHandler方法调用了H5注册的toUpper方法,同时传入'hello'参数。
3.H5拿到参数之后,对其进行操作,这里是大小写转换,
4.H5将转换后的数据return给到原生端,原生端拿到retValue值进行一些其他操作(这里进行Toast)
异步调用
1.前端注册异步方法
2.原生端调用H5注册的异步方法
逻辑梳理:
1.前些步骤与同步调用一样
2.差异在于前端注册异步方法是没有传递其他参数,直接传一个回调函数,前端自行决定执行一些其他操作逻辑
总结:以上便是通过原生端和H5几个简单的例子说明H5是如何通过DSBridge实现与原生端的交互的。接下来我将从H5端实际项目出发,讲讲我在进行hybrid app开发时,是如何使用DSBridge实现与原生端的通信的。
项目中的应用
引入DSBridge
我在项目中使用的是直接下载源码,放在js文件中,然后再项目入口文件中进行初始化(便可以全局引用),当然也可以使用下载依赖的方式,然后再main.js文件中使用node模块进行require导入。
方式1:
安装
npm install dsbridge@3.1.4
引入:在文件夹src\utils\dsbridge.js中引入(这里可以做一层封装)
var dsBridge = require('dsbridge')
export default {
callmethod (name, data, callback) {
callback(dsBridge.call(name, data, callback))
},
registermethod (tag, callback) {
dsBridge.register(tag, callback)
}
}
方式2
安装
下载deBridge.js源码放在src\utils\dsbridge.js文件下
引人:在main.js文件中引入
import dsBridge form '../src/utils/dsbridge'
使用
定制协议
1.【通用】跳转新页面 / gotoPag
方法名:gotoPage
是否同步:同步
功能:原生端提供给 H5,用于页面跳转
参数:{ url: '' }
返回值:无
H5 示例:
let url = "http://192.168.150.148:8000/device/timer" +
"?deviceId=" + devId;
dsBridge.call("gotoPage", { url: url })
2.获取账户信息 / getAccountInfo
方法名:getAccountInfo
是否同步:同步
功能:获取家庭名称、设备名称信息
参数:无
返回值:homeName 【家庭名称】, homeTimezone【时区】
H5 示例:
let homeRes = dsBridge.call("getAccountInfo", {}); // 返回
{ homeName: "test", homeTimezone: 0 }
3.局域网下图片相对路径处理 / imgSrcBase64
方法名:imgSrcBase64
功能:H5 传回图片相对路径,原生对缓存图片进行 base64 处理
是否同步:异步
传参: { src:'' }
4.返回:base64 字符串 / imgSrcBase64CallBack
方法名:imgSrcBase64CallBack
是否同步:异步
功能:原生返回缓存图片的 base64 字符串
返回:base64 内容
注:原生端将缓存图片转 png 格式,再进行 base64 处理,返回 base64 字符串,H5 自 行拼接前缀:data:image/png;base64,
调用原生注册的方法
一般在项目中需要调用到原生端注册的方法的业务方法,我会抽离到一个文件中集中管理。比如我将其放在utils/dsBridgeSend.js文件中
export default {
/**
* 定时界面跳转
* */
toTimePage(devId) {
// 测试用
let url = 'http://192.168.151.30:8001/device/timer' + '?deviceId=' + devId
dsBridge.call('gotoPage', {
url: url
})
}
/**
* 获取设备使能值信息
* */
getDevEnable() {
dsBridge.call('getDevEnable', {})
},
/**
* 导航栏配置公共方法
* */
pageBarSetting(devName) {
let config = {
hiddenBar: 0,
title: devName,
left: [
{
type: 2,
localImageIndex: 7
}
]
right: [
{
type: 2,
localImageIndex: 9
}
]
dsBridge.call('configTopBarWithParams', config)
},
/**
* 调用后台接口通用方法-通过原生端
* */
requestFunction(ind, params) {
let requestItem = requestItem.method,
url: requestItem.url,
callBackMethodName: requestItem.callBackMethodName,
errorCallBackName: requestItem.errorCallBackName,
params: params
}
dsBridge.call('requestFunction', query)
},
}
注册方法以供原生调用
H5这边注册方法以供原生调用, mounted() {
这个时机一般是发生在页面(或者说webview页面)加载时,所以一般是放在页面挂载的声明周期中【Vue2:mounted(),Vue3:onMounted()】
mounted(){
let _this = this;
dsBridge.registerAsyn("async", {
tag: "async",
queryDeviceInfoCallBack: function (response) {
if (_this.$dsBridgeSend.handleError(response)){
const res = JSON.parse(response).result;
// 解析状态值
_this.setStatus(res.status);
}
},
ctrlSucCallBack: function (response) {
const res = JSON.parse(response)
// 接收相同的deviceId才进行处理返回值
if (_this.$common.matchDeviceId(response, _this.commonObj.deviceId)) {
// 发送指令后 - 获取返回值
_this.changeIdList.map((i) => {
i.changeId++;
_this.updateStatus(res.deviceStates)
}
},
left_0_click: function (responseCallback) {
dsBridge.call("goBack", 4, function () {}
responseCallback();
},
right_0_click: function (responseCallback) {
if (Number(_this.isEdit) === 1) {
_this.deleteContact()
} else {
_this.addContact()
}
responseCallback();
},
});
},
总结:
混合开发的一个主要流程:
1.根据需求规划,哪些业务是H5负责,哪些是原生端负责
2.定制一份约束双端的协议以用来对应方法的一一对应性(什么方法做什么事,参数、方法名、同步异步)