0.WebApp、原生App、混合App的区别
企业要开发一个App,那么对于前端开发者来说有三种流行开发技术,分别是WebApp、原生App和混合App。
原生App
在Android、iOS等移动平台上利用官方提供的开发语言、类库进行App开发。
- 优势:
- 具有高的稳定性和流畅性,应用性能和交互体验好。
- 原生应用大多数据都在本地,响应速度快。
- 劣势:
- 原生应用可移植性较差,一款原生App, Android和iOS都要各自开发,同样的逻辑、界面要写两套。
- 开发成本高,维护成本高。
WebApp
利用Web技术进行App开发。需要浏览器的支持才能进行展示和用户交互。
- 优势:
- 支持设备范围广,可以跨平台,编写的代码可以同时在Android、iOS、Windows上运行
- 开发成本低、周期短
- 用户可以直接使用最新版本(自动更新,不需要用户手动更新)。
- 劣势:
- H5移动应用不能直接访问设备硬件,所以体验和性能上有很大的局限性
- 对互联网要求高,离线不能做任何操作
- App反应速度慢,页面切换流畅性较差(访问页面需要下载对应的html/css/js)
混合App
介于WebApp和原生App两者之间的App。混合App(Hybrid App)开发就是一部分采用原生开发,一部分使用web开发,如此一来就兼具原生App开发良好用户体验的优势和WebApp开发跨平台的优势。
- 优势:
- 开发效率高节约时间。同一套代码,Android和iOS基本都可以使用
- 更新和部署比较方便,每次升级版本只需要在服务器端升级即可,不需要上传到App Store进行审核
- 代码维护方便,版本更新快,节约产品成本
- 比Web版实现功能多
- 劣势:
- 加载缓慢/网络要求高:混合App数据需要从服务器调取,每个页面都需要重新下载,因此打开速度慢,网络占用高,缓冲时间长,容易让用户反感。
1.比较流行的混合方案
Hybrid App,俗称混合应用,即混合了Native技术与Web技术,进行开发的移动应用。现在比较流行的混合方案主要有三种, 主要是UI渲染机制上的不同
- 基于
WebView UI
的基础方案,市面上大部分主流App都有采用,例如微信JS-SDK,通过JSBridge完成H5与Native的双向通讯,从而赋予H5一定程度的原生能力。 - 基于
Native UI
的方案,例如React-Native。在赋予H5原生API能力的基础上,进一步通过JSBridge将js解析成的虚拟节点树(Virtual DOM)传递到Native并使用原生渲染。 - 另外还有比较流行的
小程序方案
,也是通过更加定制化的JSBridge,并使用双WebView双线程的模式隔离了JS逻辑与UI渲染,形成了特殊的开发模式。加强了H5与Native混合程度,提高了页面性能及开发体验。
以上三种方案,其实同样都是基于JSBridge完成的通讯层,第二三种方案,其实可看作是在方案一的基础上,继续通过不同的新技术进一步提高了应用的混合程度。因此,JSBridge也是整个混合应用最关键的部分,例如我们在设置微信分享时用的JS-SDK,wx对象便是我们最常见的JSBridge
基本原理
:
运行在原生应用内部的H5页面,通过原生提供的通信方式,跟原生互相调用。
包括H5调用原生和原生调用H5
Hybrid App工作原理
:
虽然看上去是一个Native App,但只有一个UI WebView,里面访问的是一个Web App,比如百度App最开始的应用就是包了个客户端的壳,其实里面是HTML5的网页,后来才推出真正的原生应用。
与原生App/WebApp比较:
2.JS怎么调用Native方法
有两种方式
URL Schema
URL Schema 是一种类似URL的链接,是为了方便app直接互相调用设计的,形式和普通的url近似,主要区别是protocol和host一般是自定义的,例如:qunarhy://hy/url?url=ymfe.tech,protocol是qunarhy,host则是hy。拦截URL Schema的主要流程是:Web端通过某种方式(例如iframe.src)发送URL Schema请求,之后Native拦截到请求并根据URL Schema(包括所带的参数)进行相关操作。在实现过程中这种方式有一定的缺陷:- 使用iframe.src发送URL Scheme会有url长度的隐患
- 创建请求,需要一定的耗时,比注入API的方式调用同样的功能,耗时会较长
示例代码
JavaScript 调用
:JavaScript 可以通过如下代码发起URL Scheme
请求:location.href = "js-to-native://getData"
。
**Native 拦截
:在 Android 中,需要在WebViewClient
的shouldOverrideUrlLoading
方法中进行拦截和处理,示例代码如下:
typescript
myWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// 解析参数并执行相应逻辑
return true;
}
});
JS内容:
1.准备页面
2.准备元素
3.请求特殊的url
js
<input class="ios" type="button" value="使用iframe加载url">
// 加载url 通过iframe设置URL,目的是让ios拦截
function loadUrl(url){
//创建iframe
const iframe = document.createElement('iframe')
//设置url
iframe.src = url;
//设置尺寸(不希望它被看到)
iframe.style.height = 0;
iframe.style.width = 0;
//添加到页面上
document.body.appendChild(iframe)
//加载了url之后就没有用了
//移除iframe
iframe.parentNode.removeChild(iframe)
}
document.querySelector('ios').onClick = function (){
loadUrl('js-to-native://click')
}
iOS部分内容:
1.成为代理
2.实现代理方法
Java
//拦截url
func webView(_webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (NavigationActionPolicy) -> void){
//获取url
let url = navigationAction.request.url?.absoluteString;
if(url == "js-to-native://click"){
print("调用系统功能");
decisionHandler(.cancel)
}else{
decisionHandler(.allow)
}
}
注入API
注入API的主要原理是,通过WebView提供的接口,向JavaScript的Context(window)中注入对象或者方法,让JavaScript调用时,直接执行相应的Native代码逻辑,达到js调用Native的目的。 对于IOS的UIWebView,实例如下:
js
JSContext *context = [uiWebView valueForKeyPath:@"documentView.webView.mainFrame。jave.context@"postBridgeMessage"] = ^(NSArray<NSArray*>*calls){
//Native逻辑
}
前端调用方式
window.postBridgeMessage(message)
Android 平台
定义接口类
:创建一个 Java 类,在类中定义提供给 JavaScript 调用的方法,并使用@JavascriptInterface
注解声明这些方法。例如:
typescript
public class WebAppInterface {
@JavascriptInterface
public void showToast(String toast) {
Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
}
}
注入对象
:在WebView
中通过addJavascriptInterface
方法将该 Java 对象注入到 JavaScript 的window
对象中。示例代码如下:
scss
WebView myWebView = (WebView) findViewById(R.id.webview);
//允许Android执行JS脚本,必须要!!
myWebView.getSettings().setJavaScriptEnabled(true);
// 暴露一个JSBridge对象到webview的全局对象 即WebAppInterface实例化对象
myWebView.addJavascriptInterface(new WebAppInterface(this), "Android");
JavaScript 调用
:在 JavaScript 中就可以通过window.别名.方法
的形式来调用 Native 方法,如window.Android.showToast('Hello from JS')
。
iOS 平台
配置WKWebView
:在初始化WKWebView
时,创建WKWebViewConfiguration
对象,配置各个接口对应的MessageHandler
。示例代码如下:
java
// 1.iOS部分注册监听
wkWebView.configuration.userContentController.add(self, name: 方法名)
// 2.iOS部分遵守协议
func userContentController(_userContentController:WKUserContentController, didReceive message: WKScriptMessage){
// message.body 就是传递过来的数据
print("传来的数据为", message.body)
}
JavaScript 调用
:在 JavaScript 中使用window.webkit.messageHandlers.接口名.postMessage(参数)
来调用 Native 方法,如window.webkit.messageHandlers.接口名.postMessage({data: 'Hello from JS'})
。
代码实操:
3.Native怎么调用JS方法
相比于JavaScript调用Native, Native调用JavaScript较为简单,直接执行拼接好的JavaScript代码即可。 直接使用JS Context执行JavaScript代码 / evaluateJavaScript
Android 平台
4.4版本之前:
java
// mWebView = new WebView(this); //
// 通过loadUrl方法进行调用 参数通过字符串的方式传递
mWebView.loadUrl("javascript: 方法名('参数1,参数2...')");
//也可以在UI线程中运行
runOnUiThread(new Runnable()){
@override
public void run(){
// 通过loadUrl方法进行调用 参数通过字符串的方式传递
mWebView.loadUrl("javascript: 方法名('参数1,参数2...')");
// Android 中原生的弹框
Toast.makeText(Activity名.this,"调用方法...", Toast.LENGTH_SHORT).show()
}
}
4.4版本之后(包括4.4)
java
// 通过异步的方式执行JS代码,并获取返回值
mWebView.evaluateJavascript("javascript:方法名('参数1,参数2...')", new ValueCallback(){
@Override
// 这个方法会在执行完毕之后触发,其中value就是js代码执行的返回值(如果有的话)
public void onReceiveValue(String value){
//触发回调
}
})
IOS
1.设计代理
2.实现代理方法
3.调用JS
java
class ViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler{
// 加载完毕会触发(类似vue的生命周期钩子)
func webView(_webView: WKWebView, didFinish navigation: WkNavigation!){
// 类似console.log()
print("触发啦");
// wkWebView调用js代码,其中doSomething()会被当作js解析
webView.evaluateJavaScript("doSomething()");
}
}
代码实操:
4.常见的混合式App框架
混合框架的职责:
- 提供前端运行环境
- 实现前端和原生交互
- 封装原生功能,提供插件机制
以下是常用的混合App框架:
打包工具:Cordova, PhoneGap
- Cordova:实际上做的是封装的作用,它把我们原生App里面的WebView,提供给我们的前端代码,让它可以进行Html解析和js的运行,因为WebView自带有一个JS引擎。Cordova提供了一些插件,前端代码可以通过这些插件访问到底层的硬件设备
- 安装Cordova,并基于Cordova创建项目,基于Cordova安装平台和插件。默认情况下 cordova create 会生成基于Web的骨架应用程序,其起始页是项目的www/index.html文件。
云平台:DCloud(Uni-app基于Vue), AppCan, ApiCloud, wex5
框架:
- Ionic: 免费和开源,Ionic提供了一个移动和桌面优化的HTML,CSS和JS
组件库
,用于构建高度交互的应用程序。与Angular, React, Vue或纯JavaScript一起使用
- Flutter:是一款移动应用SDK,可帮助开发人员和设计人员构建适用于iOS和Android的现代移动应用。
- React Native:使您能够使用基于JavaScript和React一致开发人员体验在本机平台上构建世界级的应用程序体验。学习一次,到处写作。
- Xamarin:Xamarin的基于Mono的产品使.NET开发人员能够使用它们现有的代码,库和工具(包括Visual Studio * )以及.NET和C#编程语言的技能,为业界最广泛使用的移动设备创建移动应用程序设备,包括基于Android的智能手机和平板电脑,iPhone, iPad和iPod Touch
跨端应用:Electron: 由Github出品、维护的跨平台桌面应用开发框架。
5.移动开发写meta标签和不写meta标签的区别
设备独立像素DPI。设备独立像素的出现使得即便在高清屏下,也可以让元素有正常的尺寸,让代码不受设备的影响,它是设备厂商根据屏幕特性设置的,无法更改。
设备独立像素和物理像素的关系:
- 普通屏幕下,一个设备独立像素对应一个物理像素
- 高清屏幕下,一个设备独立像素对应N个物理像素
设备独立像素和css像素关系:
- 在无缩放的情况下(标准情况):1css像素=1设备独立像素
像素比(dpr)单一方向上物理像素 和设备独立像素的比例,即:dpr=物理像素/设备独立像素 可以通过以下方式获取:
js
window.devicePixelRatio
开启理想视口的方法:
js
// 让布局视口的宽度和设备独立像素的宽度一致
<meta name="viewport" content="width=device-width" />
不写meta标签(不符合理想视口标准):
1.描述屏幕:物理像素:750px、设备独立像素:375px、css像素:980px
2.优点:元素在不同设备上呈现效果几乎一样,因都是通过布局容器等比缩放的,例如200宽的盒子:200/980
3.缺点:元素太小,页面文字不清楚,用户体验不好。
写meta标签(符合理想视口标准):
1.描述屏幕:物理像素750px、设备独立像素375px、css像素375
2.优点:
- 页面清晰展现,内容不再小到难以观察,用户体验较好
- 更清晰的像素关系:布局视口=视觉视口=设备独立像素=375px
- 更清晰的dpr,即dpr = 物理像素/设备独立像素 = 物理像素/css像素。例如:dpr=2的设备,1 * 1css像素 = 1 * 1设备独立像素 = 2 * 2物理像素
3.缺点同一个元素,在不同屏幕(设备)上,呈现效果不一样,例如375宽的盒子:375/375和375/414(不是等比显示)
4.解决缺点:做适配。
6.主流的适配方式
前提都是开启理想视口
因为开启了理想视口,布局视口宽度才等于设备独立像素值宽度。
1.viewport 适配(有些问题,比如边框变粗,比如有些浏览器不支持width=375具体的值)
js
<meta name="viewport" content="width=375" />
- 方法:拿到设计稿之后,设置布局视口的宽度为设计稿宽度,然后直接按照设计稿给宽高进行布局即可。
- 优点:不用复杂的计算,直接使用图稿上标注的px值
- 缺点:
- 不能使用完整的meta标签,会导致某些android手机上有兼容性的问题
- 不希望适配的东西,如边框,也强制参与了适配
- 图片会失真
2.rem适配(主流方式,几乎完美适配)
- 方案一
- 根字体 =(手机横向设备独立像素值 * 100)/ 设计稿宽度
- 编写样式时:直接以rem为单位,值为:设计值 / 100
- 方案二
-
根字体 = 设备横向独立像素值 / 10
-
编写样式时:以rem为单位,值为:设计值 / (设计稿宽度 / 10) 通过EaseLess等工具,帮忙计算:
-
3.vw适配(一定是以后的趋势,但目前兼容性不太好)
1vw = 布局视口宽度的1%
1wh = 布局视口高度的1%
7. 1物理像素边框
8.移动端事件与事件对象
移动端事件
列表
- touchstart 元素上触摸开始时触发
- touchmove 元素上触摸移动时触发
- touchend 手指从元素上离开时触发
- touchcancel 触摸被打断时触发
这几个事件最早在IOS safari中,为了向开发人员传达一些特殊的信息。
注意:
- touchmove 事件触发后,即使手指离开了元素,touchmove 事件也会持续触发
- 触发 touchmove 与 touchend 事件,一定要先触发touchstart
- 事件的作用在于实现移动端的界面交互
- 移动端touchstart 事件结束后会默认触发元素的 click事件,touchstart 和 click 之间的时间差大小跟是否开启理想视口有关,如果开启了理想视口,则时间差在50ms - 80ms之间,但如果没有开启理想视口,则时间差有300ms之多。
事件对象事件对象
事件对象里边的关键字段:
- touches:屏幕上出现的触点数
- targetTouches:当前元素上的触点数
- changedTouches:同时按下几个手指
注意:点击穿透
touch事件触发后会默认的触发元素的click事件
。如果touch事件隐藏了元素
并且该元素底下有其他元素能承接这个click事件
,则click动作将作用到新的元素,触发新元素的click事件或页面跳转,此现象称为事件穿透。
解决点击穿透方案:
- 阻止默认行为,即调用事件的e.preventDefault()
- 使背后元素不具备click特性,用touchXXX代替,如touchstart
- 让背后的元素暂时失去click事件,300ms左右再复原,例如style 的 pointerEvents为none,setTimeout设置300ms后pointerEvents设置为auto
- 让隐藏的元素延迟300ms左右后再隐藏