H5与App的交互

H5 与App的相互调用/传值

h5 与 App 交互有以下几种方式:

方法一:URL 传参

App 通过 URL 传递参数,例如: https://xxxx?id=123

这种方式有很大的局限,比如:

  1. URL 的长度有最大限制:在传递参数的时候,需要保证 URL 的长度不能超过最大限制。
  2. 考虑编码:在传递参数的时候,需要考虑编码的问题,例如在传递中文时需要对参数进行 URL 编码
  3. 单向:只适合 App 向 H5 传值
  4. 不能定制:App 只能将 H5 所有可能需要用到的参数传递过去,不能按需传递

方法二:拦截 URL Schemes

H5 和 App 首先约定一个特定的 URL Schemes,然后 App 将 H5 中约定的 URL Schemes 进行拦截。

例如:约定的 URL Schemes 为 zoneyet ,当 H5 向 App 传值时,H5 向 zoneyet://jumpToHomePage?id=123 地址进行跳转,App 将 URL Schemes 为 zoneyet 的地址进行拦截,然后分析其中的 URL 和传递的参数。

可以实现的功能:

  1. H5 向 App 传递参数,App 接收后进行处理或页面跳转

前提条件:

  1. App 和 H5 需要提前约定好 URL Schemes,URL 地址,参数名称,然后 App 才能对指定的 URL Schemes 进行拦截

局限:只能单向传值,即只能 H5 向 App 传递参数。所以一般用于 H5 控制 App 的跳转

方法三:JavaScriptBridge

使用 WebViewJavaScriptBridge(Start 13.5K), DSBridge(Android(Start 2.9K),iOS(Start 1.5K) ) 可以实现 H5 与 App 的双向调用和双向传值。

可以实现下面的功能:

  1. H5 调用 App 的方法,可以用于实现:
    1. H5 向 App 传递参数
    2. H5 控制 App 从一个页面跳转到另一个页面
  2. App 调用 H5 的方法,可以用于实现:
    1. App 修改 H5 的参数
    2. App 控制 H5 的逻辑
  3. H5 调用 App 的方法,App 处理结束后,将结果传递给 H5,可以用于实现:
    1. H5 将复杂的运算交给 App 进行处理,然后将处理结果传递给 H5
    2. H5 将网络请求的参数交给 App,然后将请求结果传递给 H5

前提条件:

  1. 在使用 WebViewJavaScriptBridge 时,H5 与 App 需要提前约定好相互调用的方法名称(和参数名称),然后才可以相互调用。

实现原理:

(1)App 调用 H5 的方法时,首先将传递的数据转换成一个字符串 messageJSON

// message 为 App 向 H5 传递的一个字典数据
- (NSString *)_serializeMessage:(id)message pretty:(BOOL)pretty{
    return [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:message options:(NSJSONWritingOptions)(pretty ? NSJSONWritingPrettyPrinted : 0) error:nil] encoding:NSUTF8StringEncoding];
}

NSString *messageJSON = [self _serializeMessage:message pretty:NO];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\'" withString:@"\\\'"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\r" withString:@"\\r"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\f" withString:@"\\f"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2028" withString:@"\\u2028"];
messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2029" withString:@"\\u2029"];

然后在 WebView 执行这段字符串

NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
 
[_webView evaluateJavaScript:javascriptCommand completionHandler:nil];

(2)H5 调用 App 的方法的实现原理是:在 WKWebView 跳转代理中,判断是不是特定的 url(https://__wvjb_queue_message__),如果是的话,将其拦截,然后从特定的地方获取 H5 调用的方法名和参数,再交给 App 进行处理。

// WKWebView 的代理
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
    if (webView != _webView) { return; }
    NSURL *url = navigationAction.request.URL;

    if ([_base isWebViewJavascriptBridgeURL:url]) {
        if ([_base isBridgeLoadedURL:url]) {
            [_base injectJavascriptFile];
        } else if ([_base isQueueMessageURL:url]) {
            [self WKFlushMessageQueue];
        } else {
            [_base logUnkownMessage:url];
        }
        decisionHandler(WKNavigationActionPolicyCancel);
        return;
    }
    
    // *** 删除了非核心代码 **
}

// _base 中判断是否拦截 url 的方法
- (BOOL)isWebViewJavascriptBridgeURL:(NSURL*)url {
    if (![self isSchemeMatch:url]) {
        return NO;
    }
    return [self isBridgeLoadedURL:url] || [self isQueueMessageURL:url];
}

- (BOOL)isSchemeMatch:(NSURL*)url {
    NSString* scheme = url.scheme.lowercaseString;
    return [scheme isEqualToString:kNewProtocolScheme] || [scheme isEqualToString:kOldProtocolScheme];
}

- (BOOL)isQueueMessageURL:(NSURL*)url {
    NSString* host = url.host.lowercaseString;
    return [self isSchemeMatch:url] && [host isEqualToString:kQueueHasMessage];
}

- (BOOL)isBridgeLoadedURL:(NSURL*)url {
    NSString* host = url.host.lowercaseString;
    return [self isSchemeMatch:url] && [host isEqualToString:kBridgeLoaded];
}

方法四:JSCore

在iOS 7之后,苹果将 JSCore 作为一个系统级 Framework 提供给开发者。可以通过 JSCore 传递参数。可以通过 深入理解JSCore 了解技术原理。

实现方法:

H5:

function scan() {
    // 判断iOS 、Android
    const isAndroid = navigator.userAgent.indexOf('Android') > -1 || navigator.userAgent.indexOf('Linux') > -1;
    const isIOS = !!navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
    // 传递的数据 
    data = {'id' : 123}
    if (isAndroid) {
        // 'scan'为标志符 app定义
        window.WebViewJavascriptBridge.callHandler('scan', data,
                                                   function(responseData) {});
    } else if (isIOS) {
        // iOS 如果不需要传值 则data要传null
        window.webkit.messageHandlers.scan.postMessage(data)
    }
}

iOS 定义 JSCoderViewController 并实现 WKScriptMessageHandler 协议:

enum ScriptMessageName {
    case scan //
    
    var name: String {
        switch self {
        case .scan:
            return "scan"
        }
    }
}

class JSCoderViewController: UIViewController {

  // 定义 WKWebView
  lazy var webview: WKWebView = {
      let configuration = WKWebViewConfiguration()
      let userContentController = WKUserContentController()
      userContentController.add(self, name: ScriptMessageName.scan.name)

      configuration.userContentController = userContentController
      return WKWebView(frame: .zero, configuration: configuration)
  }()
  // ....
}


extension JSCoderViewController: WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        switch message.name {
        case ScriptMessageName.scan.name:
            // 参数从 message.body 中获取
        default:
            debugPrint("undefined message name:\(message.name)")
        }
    }
}

提升 H5 的加载速度

方法一:对 H5 中的静态资源进行缓存

在用户打开 H5 页面之后,App 对 H5 中需要加载的静态资源进行缓存,比如:对 js、css、image 资源进行缓存。

实现方案有两种:

  1. App 利用 WebView 控件的缓存策略,对 H5 中的资源进行缓存,下次打开的时候,从缓存中读取数据。
  2. App 拦截 WebView 的网络请求,自己实现一套缓存机制

缓存策略为:App 将资源的 url 地址作为资源的唯一标识,对资源进行缓存。当 HTML 中引用的资源发生变动时,需要保证在 HTML 中的引用地址需要发生变化,比如 url 变更或者 url 中的参数变更。App 发现本地没有对应的资源时,需要再次缓存。

方法二: 预加载

对用户将来可能会访问到的 H5 页面,App 可以对 H5 中的资源进行预加载和缓存,当用户访问 H5 页面的时候,App 从本地加载 HTML 和相关资源的数据,然后进行显示,从而提高打开速度。

实现步骤:

  1. App 提前请求 H5 页面数据,获取对应的 HTML 文件,然后提取 HTML 中的静态资源(js,css, image),然后进行缓存。
  2. 当 WebView 加载 H5 时,App 拦截资源的请求,从本地进行加载

方法三:资源的打包下载

将用户将来会访问的 H5 资源进行打包(打包为 .zip 文件),实现版本控制,App 对资源进行下载、解压,放在缓存目录当中。当用户访问时,从本地加载对应的资源。当 H5 资源发生变更后,App 根据版本实现增量更新。

实现步骤:

  1. 将 H5 资源进行整体打包,实现版本控制
  2. App 启动后,查看是否有需要下载的 H5 资源,
  3. 如果有需要下载的资源,则首先查看 App 当前是否有缓存对应的资源,
    1. 如果没有缓存,请求资源时,不携带缓存版本号,服务器返回全量更新的资源和对应的版本号,App 缓存进行
    2. 如果有缓存,请求资源时,携带缓存的版本号,服务器返回增量更新的资源,App 对之前缓存的资源进行增加、替换,并保存当前版本号、

方法四:动态资源的处理

对 H5 中需要使用的动态数据,比如:H5 中的列表数据,可以使用下面的方法进行加速:

  1. H5 将通过网络请求到的动态数据传递给 App,App 进行缓存处理。下次打开 H5 时就可以从 App 的缓存中读取数据
  2. H5 将网络请求的交给 App,App 请求结束后,将结果返回给 H5。

原生应用中动态页面

在运营过程中,App 的一些活动页面需要在不发新版本的情况下,实现动态变动,比如:动态的弹窗页面,活动提醒页面。

为了实现这种需求,可以通过下面的方式进行实现:

通过 WebView 加载动态链接

动态的内容可以通过 WebView 进行动态展示。

实现方案:具体的展示效果通过 H5 完成,App 通过 WebView 加载动态的 URL 进行展示。

预设模板

在 App 中预设几种动态模板,在需要暂时动态内容时,从预设模板中选择一个模板,然后设置相关数据。

限制:必须先在 App 中完成可能会用到的模板,然后从中选择相应的模板,进行展示。

富文本

富文本中可以加载图片,文字,按钮,可以通过富文本来定制动态的 UI。

可以使用的富文本框架 YYKit

HTML 转原生页面

动态页面的样式由 HTML 设计完成,在需要展示动态的页面时,App 从 H5 或者服务获取 HTML、css 数据,App 将 HTML 数据翻译成原生 UI 进行展示。

实现步骤:

  1. App 将 HTML 文本翻译成 dom 树
  2. App 通过 CSS 设置 dom 树的属性
  3. App 通过 dom 树的信息构建原生 UI,然后进行展示

可以使用的布局框架 Texture(原名:AsyncDisplayKit)

JSON 配置原生页面

使用 JSON 来描述 UI 的样式,在需要展示动态的页面时,App 从 H5 或者服务获取 JSON 数据,App 将 JSON 数据翻译成原生 UI 进行展示。

实现步骤:

  1. 通过约定好的 JSON 格式,来配置需要展示的 UI
  2. App 通过解析 JSON 格式,来绘制原生的 UI

你可能感兴趣的:(H5与App的交互)