• 作者:老汪软件技巧
  • 发表时间:2024-08-25 15:11
  • 浏览量:

需要从应用商店下载安装

搜索引擎优化

易于被索引

不易被索引

性能

通常不如原生应用,特别是在图形渲染和动画方面

直接访问设备硬件,性能更优

用户体验

受限于浏览器和HTML5标准,无法提供与原生应用相同的流畅体验和交互效果

更流畅的动画和丰富的交互效果

功能访问

特有的硬件功能,如摄像头、GPS等,H5页面无法充分利用

可访问设备硬件功能,如摄像头、GPS、语音、短信、蓝牙和拨号等

离线支持

需要网络连接

可支持离线

安全性

较低,依赖浏览器安全措施

更好

发布流程

无需经过应用商店审核

需要通过应用商店审核,审核过程耗时

更新维护

快速,可即时更新

用户需要手动下载新版本,更新率受影响

平台限制

受限于Web标准和浏览器兼容性

受限于特定平台的规范和限制

推广难度

较低,易于在社交网络上传播

较高,需要用户下载安装

举个

h5面板:分为纯h5面板和原生容器实现半屏

非纯h5面板:面板是原生的popwindow控件 ,控件里面放了一个webview,加载了一个内容网页纯h5面板:半屏容器和内容均由h5实现

纯原生面板:容器和内容均由原生实现

通信时机

什么时候通信就看双端的现有能力就行,端不知道H5的页面功能细节,H5的变动需要响应到端的变化时就需要通信,反过来一样

其实H5和NA的交互,本质上就2种调用:

NA 调用 H5H5 调用 NAJS 调用 Native

JS 调用 NA 的方式主要有拦截URL Scheme、重写 prompt 、注入 API 等方法

Android

js调NA,需要对WebView设置以下属性

WebSettings webSettings = mWebView.getSettings();  
 //Android容器允许JS脚本
webSettings.setJavaScriptEnabled(true);
//Android容器设置侨连对象
mWebView.addJavascriptInterface(getJSBridge(), "JSBridge");

通过addJavascriptInterface添加暴露出来的JSBridge,然后在对象内部声明对应的API

private Object getJSBridge(){  
    Object insertObj = new Object(){  
        @JavascriptInterface
        public String foo(){  
            return "jsbrdige";  
        }
    };  
    return insertObj;  
}  

H5调用NA方法:

window.JSBridge.foo(); //返回:'jsbrdige'

说明:

PS:WebView中接口曾出现过隐患,可以参考WebViw漏洞利用

iOS

NA通过引入官方提供的库,可以将API绑定到JSContext上(JS默认通过可调用)。

NA注册API函数:

-(void)webViewDidFinishLoad:(UIWebView *)webView{
    [self hideProgress];
    [self setJSInterface];
}
-(void)setJSInterface{
    JSContext *context =[_wv valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    // 注册名为foo的api方法
    context[@"foo"] = ^() {
        //获取参数
        NSArray *args = [JSContext currentArguments];
        NSString *title = [NSString stringWithFormat:@"%@",[args objectAtIndex:0]];
        //返回一个值  'foo:'+title
        return [NSString stringWithFormat:@"foo:%@", title];
    };    
}               

JS调用NA方法

window.top.foo('test');

PS:iOS原生无法被JS调用,通过引入官方提供的第三方”JavaScriptCore”,即可开放API给JS调用

实现方式URL Scheme

适用于应用间导航通信,当需要从Web页面触发某些应用内操作或跳转到其他应用时使用。在移动开发中,它允许一个应用通过定义的URL格式启动另一个应用,并传递数据。常用于:

Scheme格式如下

:///?<qeury>#fragment

你可以在浏览器里面直接输入 weixin://,系统就会提示你是否要打开微信。输入 mqq:// 就会帮你唤起手机 QQ。

Scheme必须原生APP注册后才生效,许多应用程序定义了自己的Scheme,如微信 weixin://、twitter://,它们可以被浏览器或其他应用识别并启动相应应用程序

而在我们实际开发中,APP不会注册对应的Scheme,而是由H5通过某种方式触发scheme(如用iframe.src),然后NA捕获URL触发事件拿到当前触发URL,联调已有协议

我们可以自定义 JSB 通信的 URL Schema,比如:jsbridge://showToast?text=hello

NA 加载 WebView之后,Web 发送的所有请求都会经过 WebView 组件,所以 NA 可以重写 WebView 里的方法,拦截 Web发起的请求,我们判断请求的格式:

Web发送URL请求的方法:

a标签:需要用户操作location.href:可能引起页面的跳转丢失iframe.src:常用,需要控制 URL 的长度ajax请求:Android没有相应的拦截方法

Android 和 iOS 都可以拦截 URL Scheme 并解析 Scheme 决定是否执行对应 NA 代码:

拦截 URL Scheme 不存在漏洞问题、使用灵活,可以实现 H5 和 Native 页面的无缝切换

例如在某一页面需要快速上线的情况下,先开发出 H5 页面。某一链接填写的是 H5 链接,在对应的 NA 页面开发完成前先跳转至 H5 页面,待 NA 页面开发完后再拦截,跳转NA 页面,此时 H5 的链接无需修改

JavaScript Interface

通过 webView 提供API,App 将 NA 的接口注入到 JS 的 Context(window)的对象中,Web 端可直接在全局window使用这个暴露的全局 JS 对象,进而调用NA方法

重写 prompt 等原生 JS 方法

和NA约定好传参格式:

H5无需识别客户端,传入不同参数直接调用 NA 即可,剩下的交给客户端拦截相同方法,识别参数,可实现多端一致能起到隔离的作用Native 调用 JS

NA 调用 JS 比较简单, H5 将 JS 方法暴露在 Window 上给 NA 调用即可

Android端

遵循:”javascript: 方法名(‘参数,需要转为字符串’)”的规则即可。

Android 4.4前的版本支持 loadUrl,使用方式类似我们在 a 标签的href里面写 JS 脚本一样,都是javascript:xxx的形式,这种方式无法直接获取返回值。

Android4.4之前,调用的方式:

// mWebView = new WebView(this);            
mWebView.loadUrl("javascript: 方法名('参数,需要转为字符串')"); 

 runOnUiThread(new Runnable() {  
        @Override  
        public void run() {  
            mWebView.loadUrl("javascript: 方法名('参数,需要转为字符串')");  
            Toast.makeText(Activity名.this, "调用方法...", Toast.LENGTH_SHORT).show();  
        }  
});  

Android 4.4及以后,使用evaluateJavascript调用:

通信原理涉及到哪些基础__通信原理的作用

mWebView.evaluateJavascript("javascript: 方法名('参数,需要转为字符串')", new ValueCallback() {
        @Override
        public void onReceiveValue(String value) {
            //这里的value即为对应JS方法的返回值
        }
});

说明:

iOS端

UIWebView:stringByEvaluatingJavaScriptFromString实现

WKWebview:evaluateJavaScript:javaScriptString实现

//Swift
webview.stringByEvaluatingJavaScriptFromString("方法名(参数)")
//OC
[webView stringByEvaluatingJavaScriptFromString:@"方法名(参数);"];

PS:NA调用JS方法时,能拿到JS方法的返回值

JSBridge原理(JSB)

Bridge是一种在H5页面和原生APP之间建立通信通道的机制,它通常涉及在原生端暴露一个API接口,H5页面可以通过调用这个接口来触发原生代码。

容器一旦接到网页的请求,就根据请求去调用底层系统的 API,然后再返回结果给网页。API Bridge 往往以 JavaScript 语言提供,方便网页调用,这时又称为 JSbridge。

JSB的流程如下所示:

其实原生的WebView/UIWebView控件已经能够和JS通信了,为什么还要JSBridge呢?

其实使用JSBridge有很多方面的考虑:

Android 4.2以下,addJavascriptInterface方式有安全问题iOS 7以下,JS无法调用NAH5 调用 NA

H5 调用 NA,会调用 invokeSchema 方法

先生成一个 message,为这条message生成一个callbackId执行registerCallback方法注册回调,将回调push到responseCallbacks更改 iframe 的 src

当 NA 检测到 iframe src 的变化,执行_handleMessageFromNative方法,获取到 JS 侧的 responseCallbacks 中的所有 message

从NA获取到的message中获取responseId有2种情况:

取不到 responseId :说明是第一次调用 bridge 传过来的,此时会生成一个返回给调用方的 message,其 reponseId 是传过来的 message 的 callbackId,当 native 执行 responseCallbacks 时,会将生成的包含 responseId 的 message 返回给H5有 responseId:说明这个 message 是 JS 调 Native 之后回调接收的 message,所以从一开始responseCallbacks中根据 responseId(一开始存的时候是用的callbackId,两个值是相同的)取出这个回调函数并执行

这样就完成了一次 JS 调用 Native 的流程

NA 调用 H5

NA 调用 webview 注册的 JSBridge 的逻辑是相似的,不过就不是通过触发 iframe 的 src 触发执行的了,因为NA可以主动调用 _handleMessageFromNative 方法,在webview中执行对应逻辑

JSBridge封装

基本每个公司内自己会封装一套Hybrid解决方案库,并且可能不同部门间的通信协议库还不同,这种封装提供了一套标准化、简化的接口,使得我H5端和NA端的交互更加高效和易于管理

自定义封装Hybrid的特点包括:安全性、易用性、扩展性、可维护性、统一接口、可定制性等

JSB流程

一个简易版JSB流程如下所示:

全局桥对象

const JSBridge = window.JSBridge || (window.JSBridge = {});

JSB是H5页面中定义在全局对象window的一个属性,该对象有以下方法:

_handleMessageFromNative:原生调用H5页面注册的方法,或通知H5页面执行回调方法registerCallback:H5调用注册方法供NA调用invokeSchema:H5调用NA的方法,调用后实际是本地通过url scheme触发,调用时会将callbackId存放到本地变量responseCallbacks中

JS调用NA

我们定义好了全局JSB对象,可以通过registerCallback方法调用原生API,经历了以下步骤:

判断是否有回调函数,有则生成callbackId,并将callbackId和对应回调添加进入回调函数集合responseCallbacks列表

/**
 * 注册回调方法
 */
 const registerCallback = (name, responseCallback) => {
    if (!responseCallbacks[name]) {
        responseCallbacks[name] = [];
    }
    responseCallbacks[name].push(responseCallback);
};


通过特定方法,将传入的数据、方法名拼接成一个url scheme,原生捕获到这个scheme后会分析

let uri = 'xxxxx://xxxJS_'; // API_Name:callbackId/handlerName?data

H5创建隐藏iframe触发scheme

//创建隐藏iframe
let ifr = document.createElement('iframe');
ifr.style.display = 'none';
//触发scheme
ifr.src = uri;
document.documentElement.appendChild(ifr);

NA获悉api被调用

我们已经成功在H5页面中触发scheme,NA如何捕获scheme被触发呢?

Android捕获url scheme

通过shouldoverrideurlloading捕获到url scheme的触发

public boolean shouldOverrideUrlLoading(WebView view, String url){
	//读取到url后自行进行分析处理
	//如果返回false,则WebView处理链接url,如果返回true,代表WebView根据程序来执行url
	return true;
}

iOS捕获url scheme

在UIWebView内发起的所有网络请求,都可以通过delegate函数在Native层得到通知。这样,我们可以在webview中捕获url scheme的触发(原理是利用 shouldStartLoadWithRequest)

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    NSURL *url = [request URL];
     
    NSString *requestString = [[request URL] absoluteString];
    //获取url scheme后自行进行处理

参数和回调的格式

Native已经接收到了JS调用的方法,接下来NA就应该按照规定的数据格式解析数据了,Native接收到URL后,可以将回调参数id、api名、参数提取出来:

根据api名,在本地找寻对应的api方法,并且记录该方法执行完后的callbackId

根据提取出来的参数将定义好的参数进行转化

NA本地执行对应的api方法

找到这次api调用对应的callbackId,连同需要传递的参数组装成JSON格式的参数,JSON格式为:{responseId:回调id,responseData:回调数据}

responseId:H5页面中对应需要执行的callbackId,在H5注册回调时就已经产生responseData:NA需要传递给H5的回调数据, {code:(整型,调用是否成功,1成功,0失败),result:结果信息

通过JSBridge通知H5页面回调

NA调用JS

NA通过JSBridge调用H5的JS方法或者通知H5回调

//将回调信息传给H5
JSBridge._handleMessageFromNative(messageJSON);	

messageJSON数据格式根据两种不同的类型:

NA通知H5回调: 上述Native通知H5回调的JSON格式NA主动调用H5:{handlerName:api名,data:数据,callbackId:回调id}H5中API注册

h5怎么注册供原生调用的api呢?

data:原生传过来的数据callback:内部封装,执行callback后会触发url scheme,通知NA获取回调信息

Hybrid.register('test', (resData: MsgRes) => {
    const {code, msg, result} = resData;
    if (code === 1) {}
});

Hybrid开发库

这些框架在web基础上包装一层Native,通过JSB实现和NA的交互

Cordova/PhoneGap/Lonic

/docs/en/12.…

流行的H5与原生APP交互框架,能使用JS开发跨平台的移动应用,提供了一系列的插件来实现H5和NA的交互

React Native

/docs/intro-…

虽然主要用于开发原生应用,但RN也支持Web视图,可用于嵌入H5页面,并与NA通信。

Flutter

/get-started…

Flutter是一个跨平台的UI工具集, 类似于RN,用于从单一的Dart代码库构建原生应用。也支持通过平台通道与H5内容通信。

写在最后

我们主要学习了H5页面与客户端应用之间的通信过程、主要方式、优缺点及应用场景;同时我们还探讨了JSB的原理和简单实现,希望对你有所帮助

参考

/post/725366…

/blog/2019/1…

/developer/a…

开源项目参考: