咱们大前端团队内部 ?每周一练 的知识复习计划还在继续,本周主题是 《Hybird APP 混合应用专题》 ,这期内容比较多,篇幅也相对较长,每一个知识点内容也比较多。javascript
以前分享的每周内容,我都整理到掘金收藏集 ?《EFT每周一练》 上啦,欢迎点赞收藏咯??。css
注:本文整理资料来源网络,有些图片/段落找不到原文出处,若有侵权,联系删除。html
参考文章:前端
随着如今移动互联网的快速发展,市面上目前主流移动应用程序主要分三类:Web App、 Native App 和 Hybrid App。css3
三者大体关系以下:git
Web App,即移动端网站,通常指的是基于 Web 的应用,基于浏览器运行,无需下载安装,基本上能够说是触屏版的网页应用。这类应用基本上是一个网页或一系列网页,旨在在移动屏幕上工做。github
Web 网站通常分为两种:web
MPA(Multi-page Application)
SPA(Single-page Application)
通常的 Web App 是指 SPA 形式开发的网站。
优势:
前端人员开发的代码,可应用于各大主流浏览器(特殊状况能够代码进行下兼容),没有新的学习成本,并且能够直接在浏览器中调试。
因为web app资源是直接部署在服务器端的,因此只需替换服务器端文件,用户访问是就已经更新了(固然须要解决一些缓存问题)。
经过浏览器便可访问,无需安装,用户使用成本更低。
缺点:
因为是直接经过的浏览器访问,因此没法使用原生的API,操做体验很差。
Web App每次访问都必须依赖网络,从服务端加载资源,当网速慢时访问速度很不理想,特别是在移动端,对网站性能优化要求比较高。
只能使用 HTML5 的一些特殊 API ,没法调用原生 API ,因此不少功能存在没法实现状况。
这既是它的优势,也是缺点,优势是无需安装,肯定是用完后有时候很难再找到,或者说很难专门为某个web app留存一个入口,致使用户很难再次使用。
Native APP 指的是原生程序,须要用户下载安装使用,通常依托于操做系统,有很强的交互,是一个完整的App,可拓展性强,能发布应用商店。
目前市面上主流的平台有:Android 和 iOS。
优势:
直接依托于操做系统,用户体验好,操做流畅,性能稳定;
用户留存率高;
功能最为强大,特别是在与系统交互中,几乎全部功能都能实现;
因为 Native APP 是直接依托于系统,因此能够直接调用官方提供的API,功能最为全面(好比本地资源操做,通知,动画等)。
缺点:
Android 上基于 Java 开发,iOS 上基 OC 或 Swift 开发,相互之间独立,必需要有各自的开发人员。
原生的一个很大特色就是独立,因此不太容易入门,并且 Android, iOS都须要独立学习。
原生应用更新是一个很大的问题, Android中还能直接下载整包APK进行更新,可是 iOS中,若是是发布 AppStore ,必须经过 AppStore地址更新,而每次更新都须要审核,因此没法达到及时更新。
Hybrid App 指的是混合开发,也就是半原生半 Web 的开发模式,有跨平台效果,固然了,实质最终发布的仍然是独立的原生APP(各类的平台有各类的SDK)。
优势:
Hybrid 开发模式下,由原生提供统一的 API 给 JS 调用,实际的主要逻辑由 HTML 和 JS 完成,最终放在 webview 中显示,这样只须要写一套代码便可,达到跨平台效果,另外也能够直接在浏览器中调试,很方便。
通常 Hybrid 中的跨平台最少能够跨三个平台: Android App ,iOS App ,普通 webkit 浏览器。
须要前端人员关注一些原生提供的API,具体的实现无需关心,没有新的学习内容。
虽然没有 web app 更新那么快速,可是 Hybrid 中也能够经过原生提供 api ,进行资源主动下载,达到只更新资源文件,不更新 apk(ipa) 的效果。
由于能够调用原生api,因此不少功能只要原生提供出就能够实现,另外性能也比较接近原生。
这种模式是原生混合 web ,因此咱们彻底能够将交互强,性能要求高的页面用原生写,而后一些其它页面用 JS 写,嵌入 webview 中,达到最佳体验。
缺点:
这种模式受限于 webview 的性能,相比原生而言有很多损耗,体验没法和原生相比。
这种模式的主要适用:一些新闻阅读类,信息展现类的 app ,不适用于一些交互较强或者性能要求较高的 app (好比动画较多就不适合)。
三者使用场景对比:
三者技术特征对比:
另外增长 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 |
这里简单介绍几种状况,具体仍是要以实际项目技术评估结果为主。
性能要求极高,体验要求极好,不追求开发效率。
不追求用户体验和性能,对离线访问没要求,正常来讲,若是追求性能和体验,都不会选用web app。
大部分状况下的App都推荐采用这种模式,这种模式能够用原生来实现要求高的界面,对于一些比较通用型,展现型的页面彻底能够用web来实现,达到跨平台效果,提高效率。通常好一点的Hybrid方案,都会把资源放在本地的,能够减小网络流量消耗。
追求性能,体验,同时追求开发效率,并且有必定的技术资本,舍得前期投入。
React Native这种模式学习成本较高,因此须要前期投入很多时间才能达到较好水平,可是有了必定水准后,开发起来它的优点就体现出来了,性能不逊色原生,并且开发速度也很快
参考文章: 《浅谈Cordova框架》
Cordova 是一个用基于 HTML、CSS 和 JavaScript 的,用于建立跨平台移动应用程序的快速开发平台。它使开发者可以利用iPhone、Android、Palm、Symbian、WP七、Bada和Blackberry等智能手机的核心功能——包括地理定位、加速器、联系人、声音和振动等,此外 Cordova 拥有丰富的插件,能够调用。
也能够用来开发原生和WebView组件之间的插件接口。
来源:
Cordova 是 PhoneGap 贡献给 Apache 后的开源项目,是从 PhoneGap 中抽出的核心代码,是驱动 PhoneGap 的核心引擎。能够把它们的关系想象成相似于 Webkit 和 Google Chrome 的关系。
架构图介绍:
用于存放咱们程序的代码,包括业务逻辑,还有一些运行须要的资源(如:CSS,JavaScript,图片,媒体文件等)。
应用的实现是经过 web 页面,默认的本地文件名称是 index.html
,应用执行在原生应用包装的 WebView 中,这个原生应用是你分发到应用商店中的。
Cordova 用的 WebView 能够给应用提供完整用户访问界面,使得应用混合了 Webview 和原生的应用组件。
插件是 Cordova 生态系统的重要组成部分。它提供了 Cordova 和原生组件相互通讯的接口,并绑定到了标准的设备API上,这使你可以经过 JavaScript 调用原生代码。
优势:
缺点:
Cordova 插件就是一些附加代码用来提供原生组件的 JavaScript 接口,它容许你的 App 可使用原生设备的能力,超越了纯粹的 Web App。
Cordova 在 iOS 上的实现原理:
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
找到回调方法;callbackId
, service
, action
, actionArgs
;原生代码拿到 callbackId
、service
、action
及 actionArgs
后,会作如下处理:
service
参数找到对应插件类;action
参数找到插件类中对应的处理方法,并把 actionArgs
做为处理方法请求参数的一部分传给处理方法;callbackId
返回给 JS 端,JS 端收到后会根据 callbackId
找到回调方法,并把处理结果传给回调方法;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]; } } }
参考文章:《JSBridge的原理》
JSBridge 简单来说,主要是 给 JavaScript 提供调用 Native 功能的接口,让混合开发中的前端部分能够方便地使用地址位置、摄像头甚至支付等 Native 功能。
JSBridge 就像其名称中的 “Bridge” 的意义同样,是 Native 和非 Native 之间的桥梁,它的核心是 构建 Native 和非 Native 间消息通讯的通道,并且是 双向通讯的通道。
JSBridge 另外一个叫法及你们熟知的 Hybrid app 技术。
所谓 双向通讯的通道:
调用相关功能、通知 Native 当前 JS 的相关状态等。
回溯调用结果、消息推送、通知 JS 当前 Native 的状态等。
参考文章:《Hybrid APP基础篇(四)->JSBridge的原理》
Android 和 iOS 的 JSBridge 实现方式:
url scheme
;url scheme
,并进行分析和处理;原生的 WebView/UIWebView 控件已经可以和 JS 实现数据通讯了,那为何还要 JSBridge呢?
其实使用JSBridge有不少方面的考虑:
addJavascriptInterface
方式有安全漏掉。url scheme
交互方式是一套现有的成熟方案,能够完美兼容各类版本,对之前老版本技术的兼容。jsbridge://className:port/methodName?jsonObj
;className // Android端实现暴露给前端的类名 port // Android返回结果给前端的端口 methodName // 前端须要调用的函数 jsonObj // 前端给Android传递的参数
index.html
, 编写一个 button
绑定 click
事件;<button onclick="JSBridge.call( 'bridge', 'showToast', {'msg':'Hello JSBridge'}, function(res){ alert(JSON.stringify(res)) } )"> 测试showToast </button>
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; }
JSBridge.java
来管理暴露给前端使用的函数;这个类有两个功能:
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(); } } }
这边代码比较多,我使用图片来展现,你们能够放大来查看。
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 返回的结果 } }); }
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 的 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 端:
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
属性:UIWebView
有一个这样的方法 stringByEvaluatingJavaScriptFromString:
,这个方法可让一个 UIWebView
对象执行一段 JS 代码,这样就能够达到 Objective-C 跟 JS 通讯的效果,在 Cordova 的代码中多处用到了这个方法,其中最重要的两处以下:
对于初入混合应用开发的小伙伴,这些会有点难度,可是好好理解下那几张流程图,再理一理思路,相信会有帮助?
给你们加加油~~
本文首发在 pingan8787我的博客,如需转载请保留我的介绍
Author | 王平安 |
---|---|
pingan8787@qq.com | |
博 客 | www.pingan8787.com |
微 信 | pingan8787 |
每日文章推荐 | https://github.com/pingan8787/Leo_Reading/issues |
ES小册 | js.pingan8787.com |