Hybrid

Hybrid

为什么需要这种设计,或者技术方案,我们就不多说了,但是我还是要强调几点个人的见解

  • 最好不要大面积使用此方案,以免影响用户体验,最好别超过项目的30%。
  • 在某些页面变化比较大,而又不太需要性能的页面,采取此设计非常合适
  • 还有一个非常重要的,一定要注意,此交互方案在app内是异步线程,注意要回到主线程处理UI。安卓和iOS都是如此!!!

在安卓上面

我们有三种方案,分别是

  1. Native提供方法,js调用Native方法
  2. Native在本地注入js方法并且调用
  3. Webview拦截Ajax请求,并做业务处理

Native提供方法,js调用Native方法

  • native注入对象(有一系列供js使用的方法)以及提供给js调用的名称。

    1
    webView.addJavascriptInterface(new HybridJsInterface(),"HybridJSInterface");
  • native提供可以供js调用的方法。

    1
    2
    3
    4
    5
    public class HybridJsInterface {
    @JavascriptInterface
    final public void hello(String text)
    Log.i("method hello","text="+text);
    }
  • js调用native的方法。

    1
    2
    3
    function hybrid(){
   
    window.HybridJSInterface.hello("hello hybrid");

    }

Native在本地注入js方法并且调用

1
2
3
4
5
6
7
8
9
10
11
//    function helloJs(){
// alert("hello js");
// }
String script = "function helloJs(){ alert('hello js');}";
//script是js方法对应的字符串
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mWebContent.evaluateJavascript(script, null);
} else {
mWebContent.loadUrl("javascript:" + script);
}
}
1
2
3
4
5
6
String handle = "helloJs()";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mWebContent.evaluateJavascript(handle, null);
} else {
mWebContent.loadUrl("javascript:" + handle);
}

Webview拦截Ajax请求,并做业务处理

  1. native拦截h5发过来的请求协议(自定义协议)。
    webview位置拦截的WebViewClient对象。
    1
    webView.setWebViewClient(new HybridWebViewClient());

HybridWebViewClient重写shouldOverrideUrlLoading方法

1
2
3
4
5
public boolean shouldOverrideUrlLoading(WebView view, String url) {Uri parse = Uri.parse(url);
String path = parse.getPath();witch (path) {
//TODO
}
}
  1. js那边发起请求协议。

方案分析

这三种方案单独使用都比较有局限性。

  • 方案一的思路是native提前给h5实现好h5需要使用的方法,方法调用完全由h5控制,缺陷是h5调用的功能实现都由native实现,当h5页面中需要新功能的时候需要修改native才能支持。

  • 方案二的思路是js提前给native提供方法,方法调用完全由native控制,缺陷是当js提供了新的方法那么需要修改native主动调用才能使用新功能。

  • 方案三的思路是native拦截h5发过了的请求进行分发控制,js需要使用的功能是native已经提前准备好的,使用不同的url进行分发来使用,缺陷是h5使用新的功能那么需要修改native提供新功能。

单独使用这三种方案的共同缺点就是js和native都是一个单向的调用,相互太过依赖。要是三种方案都用上是不是可以解决问题呢,其实根本问题不在这里,而在于这些方案都没有办法动态扩展,也就是说native一旦完成之后那么单纯靠js是很难完成功能扩展的。我们需要一种可以适应一定程度动态扩展的方案,那就让h5作为项目主导,native提供服务。

hybrid设计

总体设计思路是h5控制整个业务流程以及交互流程。h5那边负责发命令并且回调,native负责响应命令。我是采用方案三来实现,客户端需要做的就是预先处理好这些命令(url)。既然是响应命令那么首先就需要把和业务无关的命令给整理出来,比较通用的包括下面内容(不一定全):

  • header控制。

heade的样式可以参考新闻类app的详情页(这里不截图),包括内容:左边按钮(多个),右边按钮(多个),主标题,副标题。

需要做的控制是左、右按钮是否显示、显示的文本及图标以及点击按钮的回调,主、副标题是否显示及显示内容。

  • 页面刷新。

页面刷新用于内容改变之后h5主动通知native进行刷新。

  • 页面跳转。

页面跳转分成两种一种是页面跳转到一个新的native页面,另一种是在webview内部做跳转。native的跳转包括内容:跳转动画、跳转目标页、目标页需要的参数。

  • loadingview/progressbar。

通常情况建议直接使用h5的进度显示。loadingview的控制包括:loadingview是否显示,loadingview的显示样式(通常只有一种样式)。

  • 传感器数据。

传感器这部分不一定每个应用都需要。比如某些h5页面需要做活动,那么里面可能会用的摇一摇这样的功能。传感器的控制包括:地理位置、方向、震动、运动量。

  • h5离线包更新。

离线包的更新对于h5来说是一条更新命令。不过在native实现上面需要包括:离线包更新检查(版本比较)、离线包下载、离线包解压保存。

  • 离线包开关。

是否使用离线数据。native需要做的是开启离线包命令之后需要把请求的url映射到本地文件缓存。

  • 数据请求。

数据请求是指h5需要请求数据不通过直接网络访问,而是通过native自己的网络服务获取数据,尤其是在跨域的情况下很方便。

iOS上面

同样的,iOS也具备上述三个方案,分别是

  • JavaScriptCore
  • WebviewLoad
  • Url Schema

JavaScriptCore

在ios7后,Apple新增了一个JavaScriptCore让Native可以与H5更好的交互(Android早就有了),我们这里主要讨论js如何与Native通信,这里举一个简单的例子:
① 首先定义一个js方法,这里注意其中调用了一个没有声明的方法:

1
2
3
4
function printHello() {
//未声明方法
print("Hello, World!");
}

然后,上述未声明方法事实上是Native注入给window对象的:

1
2
3
4
5
6
7
8
9
10
11
12
NSString * scriptPath = [[NSBundle mainBundle] pathForResource:@"hello" ofType:@"js"];
NSString * scriptString = [NSString stringWithContentsOfFile:scriptPath encoding:NSUTF8StringEncoding error:nil];

JSContext *context = [[JSContext alloc] init];
[context evaluateScript:scriptString];

self.context[@"print"] = ^(NSString *text) {
NSLog(@"%@", text");
};

JSValue *function = self.context[@"printHello"];
[function callWithArguments:@[]];

这个样子,JavaScript就可以调用Native的方法了,这里Native需要注意方法注入的时机,一般是一旦载入页面便需要载入变量

  • 此方案需要特别注意的就是注入时机,要保证JS能调用到注入的方法

WebviewLoad

此方案和安卓一样

1
[self.wkWebView loadHTMLString:@"javascriptStr" baseURL:@"urlstr"];

Url Schema

H5与Native交互的桥梁为Webview,而“联系”的方式是以url schema的方式做的,在用户安装app后,app可以自定义url schema,并且把自定义的url注册在调度中心, 例如

  • dongfangdi://open 打开懂房帝App
  • weixin:// 打开微信

事实上Native能捕捉webview发出的一切请求,所以就算这里不是这种协议,Native也能捕捉,这个协议的意义在于可以在浏览器中直接打开APP,
我们在H5获取Native方法时一般是会构造一个这样的请求,使用iframe发出(设置location会有多次请求覆盖的问题):

1
2
3
4
5
6
7
8
9
10
11
requestHybrid({
//创建一个新的webview对话框窗口
tagname: 'dongfangdiApi',
//请求参数,会被Native使用
param: {},
//Native处理成功后回调前端的方法
callback: function (data) {
}
});
//=====>
dongfangdi://dongfangdi?callback=dongfangdiAction&param=%7B%22data1%22%3A1%2C%22data2%22%3A2%7D

总结

由于一开始我就强调过,不要完全依赖Hybrid来做app主要框架,它只是为我们提供更多的页面热更新,页面获取Native信息的一种技术。

  • 在iOS8.0以后,WebKit框架又给我们提供了一套方案
  • 在WKUserContentController类中,我们可以看到这个方法
    1
    2
    3
    4
    5
    6
    7
    8
    /*! @abstract Adds a script message handler.
    @param scriptMessageHandler The message handler to add.
    @param name The name of the message handler.
    @discussion Adding a scriptMessageHandler adds a function
    window.webkit.messageHandlers.<name>.postMessage(<messageBody>) for all
    frames.
    */
    - (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;

然后我们可以在下面这个方法中处理对应的name

1
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{}

强烈推荐此方法!!!

相关demo代码已放在github上

  • https://github.com/Jack-Ving/Hybrid_Interaction 这个是用Vue写的对应的js代码
  • https://github.com/Jack-Ving/Hybrid 这个是采用拦截的方式写的OC代码

由于仓促,如果遇到什么问题,尽管联系我.

QQ:727881945 微信:Q727881945

希望对您有所帮助,您的支持将是我莫大的动力!