前言
我们大前端团队内部 ????每周一练 的知识复习计划还在继续,本周主题是 《Hybird APP 混合应用专题》,这期内容比较多,篇幅也相对较长,每个知识点内容也比较多。
之前分享的每周内容,我都整理到掘金收藏集 ????《EFT 每周一练》上啦,欢迎点赞收藏咯????????。
注:本文整理资料来源网络,有些图片 / 段落找不到原文出处,如有侵权,联系删除。
一、什么是 Hybird App,与 Native App 及 Web App 有什么区别
参考文章:
- 《Web App Hybrid App 和 Native App 的区别》
- 《Hybrid APP 基础篇(二) -> Native、Hybrid、React Native、Web App 方案的分析比较》
1.1 主流应用类型
随着现在移动互联网的快速发展,市面上目前主流移动应用程序主要分三类:Web App、Native App 和 Hybrid App。
三者大致关系如下:
1.2 Web App
Web App,即 移动端网站 ,一般指的是基于 Web 的应用,基于 浏览器运行 , 无需下载安装,基本上可以说是触屏版的网页应用。这类应用基本上是一个网页或一系列网页,旨在在移动屏幕上工作。
Web 网站 一般分为两种:
- MPA(Multi-page Application)
- SPA(Single-page Application)
一般的 Web App 是指 SPA 形式开发的网站。
优点:
- 开发和维护成本低,可以跨平台,调试方便;
前端人员开发的代码,可应用于各大主流浏览器(特殊情况可以代码进行下兼容),没有新的学习成本,而且可以直接在浏览器中调试。
- 更新最为快速;
由于 web app 资源是直接部署在服务器端的,所以只需替换服务器端文件,用户访问是就已经更新了(当然需要解决一些缓存问题)。
- 无需安装 App,不会占用手机内存;
通过浏览器即可访问,无需安装,用户使用成本更低。
缺点:
- 性能低,用户体验差;
由于是直接通过的浏览器访问,所以无法使用原生的 API,操作体验不好。
- 依赖于网络,页面访问速度慢,耗费流量;
Web App 每次访问都必须依赖网络,从服务端加载资源,当网速慢时访问速度很不理想,特别是在移动端,对网站性能优化要求比较高。
- 功能受限,大量功能无法实现;
只能使用 HTML5 的一些特殊 API,无法调用原生 API,所以很多功能存在无法实现情况。
- 临时性入口,用户留存率低;
这既是它的优点,也是缺点,优点是无需安装,确定是用完后有时候很难再找到,或者说很难专门为某个 web app 留存一个入口,导致用户很难再次使用。
1.3 Native App
Native APP 指的是原生程序,需要用户下载安装使用,一般依托于操作系统,有很强的交互,是一个完整的 App,可拓展性强,能发布应用商店。
目前市面上主流的平台有:Android 和 iOS。
优点:
- 直接依托于操作系统,用户体验好 , 操作流畅 , 性能稳定;
- 用户留存率高;
- 功能最为强大,特别是在与系统交互中,几乎所有功能都能实现;
由于 Native APP 是直接依托于系统,所以可以直接调用官方提供的 API,功能最为全面(比如本地资源操作,通知,动画等)。
缺点:
- 开发和维护成本高,无法跨平台,需要各平台各自独立开发;
Android 上基于 Java 开发,iOS 上基 OC 或 Swift 开发,相互之间独立,必须要有各自的开发人员。
- 门槛较高,原生人员有一定的入门门槛,人才较少;
原生的一个很大特点就是独立,所以不太容易入门,而且 Android,iOS都需要独立学习。
- 分发成本高,更新缓慢,特别是发布应用商店后,需要等到审核周期;
原生应用更新是一个很大的问题,Android中还能直接下载整包 APK 进行更新,但是 iOS中,如果是发布 AppStore,必须通过 AppStore地址更新,而每次更新都需要审核,所以无法达到及时更新。
1.4 Hybrid App
Hybrid App 指的是 混合开发,也就是半原生半 Web 的开发模式,有跨平台效果,当然了,实质最终发布的仍然是独立的原生 APP(各种的平台有各种的 SDK)。
优点:
- 学习和开发成本较低,可以跨平台,调试方便;
Hybrid 开发模式下,由原生提供统一的 API 给 JS 调用,实际的主要逻辑由 HTML 和 JS 完成,最终放在 webview 中显示,这样只需要写一套代码即可,达到跨平台效果,另外也可以直接在浏览器中调试,很方便。
一般 Hybrid 中的跨平台最少可以跨三个平台: Android App,iOS App,普通 webkit 浏览器。
需要前端人员关注一些原生提供的 API,具体的实现无需关心,没有新的学习内容。
- 维护成本低,功能可复用,并且更容易更新;
虽然没有 web app 更新那么快速,但是 Hybrid 中也可以通过原生提供 api,进行资源主动下载,达到只更新资源文件,不更新 apk(ipa) 的效果。
- 功能更加完善,性能和体验要比起 web app 好太多;
因为可以调用原生 api,所以很多功能只要原生提供出就可以实现,另外性能也比较接近原生。
- 部分性能要求的页面可用原生实现;
这种模式是原生混合 web,所以我们完全可以将交互强,性能要求高的页面用原生写,然后一些其它页面用 JS 写,嵌入 webview 中,达到最佳体验。
缺点:
- 相比原生,性能仍然有较大损耗;
这种模式受限于 webview 的性能,相比原生而言有不少损耗,体验无法和原生相比。
- 不适用于交互性较强的 app;
这种模式的主要适用:一些新闻阅读类,信息展示类的 app,不适用于一些交互较强或者性能要求较高的 app (比如动画较多就不适合)。
1.5 三者区别
三者使用场景对比:
三者技术特征对比:
另外增加 ReactNative 一起放入作对比。
NativeApp | WebApp | HybridApp | ReactNativeApp | |
---|---|---|---|---|
原生功能体验 | 优秀 | 差 | 良好 | 接近优秀 |
渲染性能 | 非常快 | 慢 | 接近快 | 快 |
是否支持设备底层访问 | 支持 | 不支持 | 支持 | 支持 |
网络要求 | 支持离线 | 依赖网络 | 支持离线(资源存本地情况) | 支持离线 |
更新复杂度 | 高(几乎总是通过应用商店更新) | 低(服务器端直接更新) | 较低(可以进行资源包更新) | 较低(可以进行资源包更新) |
编程语言 | Android(Java),iOS(OC/Swift) | js+html+css3 | js+html+css3 | 主要使用 JS 编写,语法规则 JSX |
社区资源 | 丰富(Android,iOS 单独学习) | 丰富(大量前端资源) | 有局限(不同的 Hybrid 相互独立) | 丰富(统一的活跃社区) |
上手难度 | 难(不同平台需要单独学习) | 简单(写一次,支持不同平台访问) | 简单(写一次,运行任何平台) | 中等(学习一次,写任何平台) |
开发周期 | 长 | 短 | 较短 | 中等 |
开发成本 | 昂贵 | 便宜 | 较为便宜 | 中等 |
跨平台 | 不跨平台 | 所有 H5 浏览器 | Android,iOS,h5 浏览器 | Android,iOS |
APP 发布 | AppStore | Web 服务器 | AppStore | AppStore |
1.6 三者如何选择
这里简单介绍几种情况,具体还是要以实际项目技术评估结果为主。
- 选择纯 Native App 模式的情况:
性能要求极高,体验要求极好,不追求开发效率。
- 选择 Web App 模式的情况:
不追求用户体验和性能,对离线访问没要求,正常来说,如果追求性能和体验,都不会选用 web app。
- 选择 Hybrid App 模式的情况
大部分情况下的 App 都推荐采用这种模式 ,这种模式可以用原生来实现要求高的界面,对于一些比较通用型,展示型的页面完全可以用 web 来实现,达到跨平台效果, 提升效率 。一般好一点的 Hybrid 方案,都会 把资源放在本地 的,可以 减少网络流量消耗。
- 选择 React Native App 模式的情况
追求性能,体验,同时追求开发效率,而且有一定的技术资本,舍得前期投入。
React Native 这种模式学习成本较高,所以需要前期投入不少时间才能达到较好水平,但是有了一定水准后,开发起来它的优势就体现出来了,性能不逊色原生,而且开发速度也很快
二、什么是 Cordova,它的优缺点是什么
参考文章:《浅谈 Cordova 框架》
2.1 Cordova 简介
Cordova 是一个用基于 HTML、CSS 和 JavaScript 的,用于创建跨平台移动应用程序的快速开发平台。它使开发者能够利用 iPhone、Android、Palm、Symbian、WP7、Bada 和 Blackberry 等智能手机的核心功能——包括地理定位、加速器、联系人、声音和振动等,此外 Cordova 拥有丰富的插件,可以调用。
也可以用来开发 原生和 WebView 组件之间的插件接口。
来源:
Cordova 是 PhoneGap 贡献给 Apache 后的开源项目,是从 PhoneGap 中抽出的核心代码,是驱动 PhoneGap 的核心引擎。可以把它们的关系想象成类似于 Webkit 和 Google Chrome 的关系。
2.2 Cordova 架构图
架构图介绍:
- Web App
用于存放我们程序的代码,包括业务逻辑,还有一些运行需要的资源(如:CSS,JavaScript,图片,媒体文件等)。
应用的实现是通过 web 页面,默认的本地文件名称是 index.html
,应用执行在原生应用包装的 WebView 中,这个原生应用是你分发到应用商店中的。
- WebView
Cordova 用的 WebView 可以给应用提供完整用户访问界面,使得应用混合了 Webview 和原生的应用组件。
- Cordova Plugins
插件是 Cordova 生态系统的重要组成部分。它 提供了 Cordova 和原生组件相互通信的接口 ,并绑定到了标准的设备 API 上,这使你能够 通过 JavaScript 调用原生代码。
2.3 优缺点
优点:
- 跨平台,开发简单,学习成本低;
- 框架多,插件多,可自定义插件;
- 发展最早,社区资源丰富;
缺点:
- WebView 性能低下时,用户体验差,反应慢;
- 中文文档资源少;
- 调试不方便,既不像原生那么好调试,也不像纯 web 那种调试;
三、Cordova 插件的原理是什么
Cordova 插件就是一些附加代码用来提供原生组件的 JavaScript 接口,它允许你的 App 可以使用原生设备的能力,超越了纯粹的 Web App。
Cordova 在 iOS 上的实现原理:
3.1 工作流程
- Cordova 发起对原生的请求:
cordova.exec(successCallback, failCallback, service, action, actionArgs);
// successCallback: 成功回调方法
// failCallback: 失败回调方法
// server: 所要请求的服务名字
// action: 所要请求的服务具体操作
// actionArgs: 请求操作所带的参数
-
这五个参数并不是直接传给原生,Cordova JS 端会做以下处理:
- 为每个请求生成一个唯一标识(
callbackId
),并传给原生端,原生端处理完后,会把callbackId
连同处理结果一起返回给 JS 端; - 以
callbackId
为key
,{success:successCallback, fail:failCallback}
为value
,把这个键值对保存在 JS 端的字典里,successCallback
与failCallback
这两个参数不需要传给原生,原生返回结果时带上callbackId
,JS 端就可以根据callbackId
找到回调方法; - 每次 JS 请求,最后发到原生的数据包括:
callbackId
,service
,action
,actionArgs
;
- 为每个请求生成一个唯一标识(
-
原生代码拿到
callbackId
、service
、action
及actionArgs
后,会做以下处理:- 根据
service
参数找到对应插件类; - 根据
action
参数找到插件类中对应的处理方法,并把actionArgs
作为处理方法请求参数的一部分传给处理方法; - 处理完成后,把处理结果及
callbackId
返回给 JS 端,JS 端收到后会根据callbackId
找到回调方法,并把处理结果传给回调方法;
- 根据
- JS 端根据
callbackId
回调cordova.js
// 根据 callbackId 及是否成功标识,找到回调方法,并把处理结果传给回调方法
callbackFromNative: function(callbackId, success, status, args, keepCallback) {var callback = cordova.callbacks[callbackId];
if (callback) {if (success && status == cordova.callbackStatus.OK) {callback.success && callback.success.apply(null, args);
} else if (!success) {callback.fail && callback.fail.apply(null, args);
}
// Clear callback if not expecting any more results
if (!keepCallback) {delete cordova.callbacks[callbackId];
}
}
}
四、什么是 JS Bridge,它的作用是什么
参考文章:《JSBridge 的原理》
4.1 JS Bridge 介绍
JSBridge 简单来讲,主要是 给 JavaScript 提供调用 Native 功能的接口 ,让混合开发中的 前端部分 可以方便地使用地址位置、摄像头甚至支付等 Native 功能。
JSBridge 就像其名称中的 “Bridge” 的意义一样,是 Native 和非 Native 之间的桥梁 ,它的核心是 构建 Native 和非 Native 间消息通信的通道,而且是 双向通信的通道。
JSBridge 另一个叫法及大家熟知的 Hybrid app 技术。
所谓 双向通信的通道:
- JS 向 Native 发送消息 :
调用相关功能、通知 Native 当前 JS 的相关状态等。
- Native 向 JS 发送消息 :
回溯调用结果、消息推送、通知 JS 当前 Native 的状态等。
4.2. JS Bridge 实现原理
参考文章:《Hybrid APP 基础篇(四)->JSBridge 的原理》
Android 和 iOS 的 JSBridge 实现方式:
4.2.1 基本流程
- H5 页面通过某种方式触发一个
url scheme
; - Native 捕获到
url scheme
,并进行分析和处理; - Native 调用 H5 的 JSBridge 对象传递回调;
原生的 WebView/UIWebView 控件已经能够和 JS 实现数据通信了,那为什么还要 JSBridge 呢?
其实使用 JSBridge 有很多方面的考虑:
- Android4.2 以下,
addJavascriptInterface
方式有安全漏掉。 - iOS7 以下,JS 无法调用 Native。
-
url scheme
交互方式是一套现有的成熟方案,可以完美兼容各种版本,对以前老版本技术的兼容。
4.2.1 实现流程(Android 为例)
- 拟定协议,参考 http 制定的协议为:
jsbridge://className:port/methodName?jsonObj
;
className // Android 端实现暴露给前端的类名
port // Android 返回结果给前端的端口
methodName // 前端需要调用的函数
jsonObj // 前端给 Android 传递的参数
- 新建 HTML 文件命名为
index.html
, 编写一个button
绑定click
事件;
<button onclick="JSBridge.call('bridge','showToast',
{'msg':'Hello JSBridge'},
function(res){alert(JSON.stringify(res))
}
)">
测试 showToast
</button>
- 新建 JS 文件命名为
JSBridge.js
,第 2 步中的JSBridge.call
即为调用JSBridge.js
中的call
方法,后面带了四个参数;
call: function (obj, method, params, callback) {console.log(obj+""+method+" "+params+" "+callback);
var port = Util.getPort();
console.log(port);
this.callbacks[port] = callback;
var uri=Util.getUri(obj,method,params,port);
console.log(uri);
window.prompt(uri, "");
},
JSBridge.js
中的 call
方法,最后调用了 window.prompt
方法,这个方法就是触发 Android 端 webChromeClient
的回调函数用的。
-
window.prompt
触发了WebChromeClient
(这个需要使用函数WebView.setWebChromeClient
(new WebChromeClietn()
) 进行设定);
类中的如下回调 onJsPrompt
。这时就完成了前端与 Android 端 的通信了,因为前端的信息都顺利通过这个函数传递给 Android 了。
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {result.confirm(JSBridge.callJava(view,message));
return true;
}
- Android 中会定义一个类
JSBridge.java
来管理暴露给前端使用的函数;
这个类有两个功能:
- 暴露给前端的函数的动态注册功能。
- 解析前端信息,调用了 Android 端对应的函数,这个示例中是:
showToast
函数。
解析前端的信息,获取前端调用的函数名:
Uri uri = Uri.parse(uriString);
className = uri.getHost();
param = uri.getQuery();
port = uri.getPort() + "";
String path = uri.getPath();
HashMap< String, Method> methodHashMap = exposedMethod.get(className);
Method method = methodHashMap.get(methodName);
通过获取的函数名,这里是 showToast
,调用 Android 端的 showToast
函数。
method.invoke(null,webView,new JSONObject(param),new Callback(webView,port));
- 定义类
BridgeImpl.java
来具体的实现暴露给前端的所有函数。这里的showToast
函数如下:
public static void showToast(WebView webView, JSONObject param, final JSBridge.Callback callback){String message = param.optString("msg");
Toast.makeText(webView.getContext(),message,Toast.LENGTH_LONG).show();
if(null != callback){
try {JSONObject object = new JSONObject();
object.put("key","value");
object.put("key1","vaule1");
callback.apply(getJSONObject(0,"ok",object));
}catch (Exception e){e.printStackTrace();
}
}
}
五、请列举 Android 与 iOS 平台下 JS Bridge 的实现方式
这边代码比较多,我使用图片来展示,大家可以放大来查看。
5.1 Android 实现方式
5.1.1 Android 调用 JS 的 2 种方式
-
通过
WebView
的loadUrl()
:
JS 代码调用一定要在 onPageFinished()
回调之后才能调用,否则不会调用。
Web 端代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title> 前端代码 </title>
<script>
// Android 需要调用的方法
function callJS(){alert("Android 调用了 JS 的 callJS 方法");
}
</script>
</head>
</html>
Android 端代码:
-
通过
WebView
的evaluateJavascript()
:
// 只需要将第一种方法的 loadUrl()换成下面该方法即可
mWebView.evaluateJavascript("javascript:callJS()",
new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {// 此处为 js 返回的结果}
});
}
5.1.2 JS 调用 Android 的 3 种方式
-
通过
WebView
的addJavascriptInterface()
进行对象映射:
Android 映射:
// 继承自 Object 类
public class AndroidtoJs extends Object {
// 定义 JS 需要调用的方法
// 被 JS 调用的方法必须加入 @JavascriptInterface 注解
@JavascriptInterface
public void hello(String msg) {System.out.println("JS 调用了 Android 的 hello 方法");
}
}
Web:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title> 前端代码 </title>
<script>
function callAndroid(){
// 由于对象映射,所以调用 test 对象等于调用 Android 映射的对象
test.hello("js 调用了 android 中的 hello 方法");
}
</script>
</head>
<body>
// 点击按钮则调用 callAndroid 函数
<button type="button" id="button1" "callAndroid()"></button>
</body>
</html>
Android 端:
- 通过
WebViewClient
的shouldOverrideUrlLoading ()
方法回调拦截url
:
Web 端:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title> 前端代码 </title>
<script>
function callAndroid(){
/* 约定的 url 协议为:js://webview?arg1=111&arg2=222*/
document.location = "js://webview?arg1=111&arg2=222";
}
</script>
</head>
<!-- 点击按钮则调用 callAndroid()方法 -->
<body>
<button type="button" id="button1"
onclick="callAndroid()"
> 点击调用 Android 代码 </button>
</body>
</html>
Android 端:
- 通过 WebChromeClient 的方法回调拦截 JS 对话框方法:
通过 WebChromeClient 的 onJsAlert()
、onJsConfirm()
、onJsPrompt()
方法回调拦截 JS 对话框 alert()
、confirm()
、prompt()
消息。
Web 端:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title> 前端代码 </title>
<script>
function clickprompt(){
// 调用 prompt()var result=prompt("js://demo?arg1=111&arg2=222");
alert("demo" + result);
}
</script>
</head>
<!-- 点击按钮则调用 clickprompt() -->
<body>
<button type="button" id="button1"
onclick="clickprompt()"
> 点击调用 Android 代码 </button>
</body>
</html>
Android 端:
5.2 iOS 实现方式
5.2.1 JS 调用 iOS 的 2 种方式
- 使用
XMLHttpRequest
发起请求的方式:
Web 端:
XMLHttpRequest bridge:
JS 端使用 XMLHttpRequest
发起了一个请求:execXhr.open('HEAD', "/!gap_exec?" + (+new Date()), true);
,请求的地址是 /!gap_exec
;并把请求的数据放在了请求的 header 里面,见这句代码:execXhr.setRequestHeader('cmds', iOSExec.nativeFetchMessages());
。
而在 Objective-C 端使用一个 NSURLProtocol
的子类来检查每个请求,如果地址是 /!gap_exec
的话,则认为是 Cordova 通信的请求,直接拦截,拦截后就可以通过分析请求的数据,分发到不同的插件类 (CDVPlugin 类的子类) 的方法中:
Cordova 中优先使用这种方式, Cordova.js
中的注释有提及为什么优先使用 XMLHttpRequest
的方式,及为什么保留第二种 iframe bridge
的通信方式:
// XHR mode does not work on iOS 4.2, so default to IFRAME_NAV for such devices.
// XHR mode’s main advantage is working around a bug in -webkit-scroll, which
// doesn’t exist in 4.X devices anyways123
iframe bridge:
在 JS 端创建一个透明的 iframe
,设置这个 ifame
的 src
为自定义的协议,而 ifame
的 src
更改时,UIWebView
会先回调其 delegate
的 webView:shouldStartLoadWithRequest:navigationType:
方法,关键代码如下:
- 通过设置透明的
iframe
的src
属性:
5.2.2 iOS 调用 JS 的方式
UIWebView
有一个这样的方法 stringByEvaluatingJavaScriptFromString:
,这个方法可以让一个 UIWebView
对象执行一段 JS 代码,这样就可以达到 Objective-C 跟 JS 通信的效果,在 Cordova 的代码中多处用到了这个方法,其中最重要的两处如下:
- 获取 JS 的请求数据:
- 把 JS 请求的结果返回给 JS 端:
结语
对于初入混合应用开发的小伙伴,这些会有点难度,但是好好理解下那几张流程图,再理一理思路,相信会有帮助????
给大家加加油~~
关于我
本文首发在 pingan8787 个人博客,如需转载请保留个人介绍
Author | 王平安 |
---|---|
pingan8787@qq.com | |
博 客 | www.pingan8787.com |
微 信 | pingan8787 |
每日文章推荐 | https://github.com/pingan8787… |
ES 小册 | js.pingan8787.com |
微信公众号