Hybrid App

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 中,需要在WebViewClientshouldOverrideUrlLoading方法中进行拦截和处理,示例代码如下:

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左右后再隐藏
相关推荐
南屿欣风1 分钟前
解决 Gin Web 应用中 Air 热部署无效的问题
前端·gin
猿大师办公助手4 分钟前
Web网页内嵌福昕OFD版式办公套件实现在线预览编辑PDF、OFD文档
前端·pdf·word
幼儿园技术家1 小时前
什么是RESTful 或 GraphQL?
前端
echola_mendes1 小时前
LangChain 结构化输出:用 Pydantic + PydanticOutputParser 驯服 LLM 的“自由发挥”
服务器·前端·数据库·ai·langchain
拉不动的猪2 小时前
刷刷题46(常见的三种js继承类型及其优缺点)
前端·javascript·面试
关注我:程序猿之塞伯坦2 小时前
JavaScript 性能优化实战:突破瓶颈,打造极致 Web 体验
开发语言·前端·javascript
兰德里的折磨5502 小时前
对于后端已经实现逻辑了,而前端还没有设置显示的改造
前端·vue.js·elementui
hikktn2 小时前
【开源宝藏】30天学会CSS - DAY9 第九课 牛顿摆动量守恒动画
前端·css·开源
申朝先生3 小时前
面试的时候问到了HTML5的新特性有哪些
前端·信息可视化·html5
在下千玦3 小时前
#前端js发异步请求的几种方式
开发语言·前端·javascript