@mlibai/native.js

3.3.2 • Public • Published

native.js 文档

一、简介

native.js 旨在规范 JavaScript 与原生 native 的交互过程,以方便研发和维护。由于平台及环境的不同,原生与 JavaScript 交互的方式也可能不同。native.js 封装了几种常用的交互方式, 并定义了统一的接口,供 JavaScript 和原生交互使用。

二、HTML

1. 安装

native.js 支持使用 npm 安装,也可以直接下载 Products 目录下已压缩好源码文件

终端命令:

$ npm install @mlibai/native.js

引用到项目中:

import native from '@mlibai/native.js'
// 或者
import { Native, native } from '@mlibai/native.js'

2. 如何使用

2.1 定义消息

native.js 实现交互的过程类似于消息机制:JavaScript 调用原生方法的过程,称为发送 Native.Method 消息; 原生调用 JavaScript 方法的过程,称为发送 Native.Action 消息。在进行交互前,建议两端先协定好交互文档, 将可访问的原生方法和可访问的 JavaScript 方法列成文档,方便查看维护。

// Native.Method.doc
// 原生支持的方法
function nativeCustomMethod(arg1: string, arg2: string, callback: (value: string) => void): void;

// Native.Action.doc
// H5 支持的方法
function nativeCustomAction(arg1: string): void;

2.1 JavaScript 调用原生方法

比如,访问原生提供的 nativeCustomMethod 方法,使用 native.js 只需要下面这样写。

native.performMethod("nativeCustomMethod", "参数1", "参数2", function(arg1) {
    // 回调函数。
});

由于平台及交互方式的不同,交互操作可能并非随 JavaScript 环境创建就可以立即进行。为了解决此问题,native.js 引入了 ready 机制。

native.ready(function() {
    Native.log("原生 App 已完成初始化,可以进行交互了。");

    native.performMethod("nativeCustomMethod", "参数1", "参数2", function(arg1) {
        // 回调函数。
    });
});

注意:native.ready(fn) 注册的是 native 对象初始化完成后执行的函数,表示原生端已准备好接受交互操作; 而原生收到 ready 方法的调用,则是 document 加载完成的 DOM 事件,原生最迟要在此时完成 native 的初始化操作; 两端的 ready 触发的时机不一定相同,一般情况下,除使用拦截 URL 交互方式外,方法 native.ready(fn) 的回调函数在 DOM 事件之前调用。

为了方便使用,可以对 native 对象进行方法拓展,比如。

// define.js
// 在 Native.Method 中注册方法,可以帮助检查方法定义冲突。
Native.Method("nativeCustomMethod", "nativeCustomMethod");

// 拓展 native 。
native.extend(function(appInfo) {
    
    function _nativeCustomMethod(arg1, arg2, callback) {
        return this.performMethod(Native.Method.nativeCustomMethod, arg1, arg2, callback);
    }

    return {
        "nativeCustomMethod": {
            get: function() {
                return _nativeCustomMethod;
            }
        }
    }
});

// main.js
native.ready(function() {
    Native.log("原生 App 已完成初始化,可以进行交互了。");
    // 在业务逻辑中直接使用已拓展的方法。
    native.nativeCustomMethod("参数1", "参数2", function(arg1) {
        // 回调函数。
    });
});

2.3 Native.Action(原生可访问的方法)的实现

原生对 JavaScript 方法的访问被 native.js 抽象为原生行为,即 Native.Action,所以原生可访问的方法的实现,就变成了处理 Native.Action 消息。

// 注册 Native.Action 可以帮助检查定义冲突。
Native.Action("nativeCustomAction", "nativeCustomAction");

// 注册原生行为触发时的函数。
native.addActionTarget(Native.Action.nativeCustomAction, function(arg1) {
    log("原生行为 " + Native.Action.nativeCustomAction + " 触发了:" + arg1);
});

建议为方法拓展便利函数。

native.extend(function() {
    function _nativeCustomAction(arg1) {
        return this.sendAction(Native.Action.nativeCustomAction, arg1);
    }
    return {
        "nativeCustomAction": {
            get: function() {
                return _nativeCustomAction;
            }
        }
    }
});

2.4 与其它框架兼容

native.js 的 ready 机制可以保证交互操作能够正常进行,但是如果每次交互操作都放在 ready 方法中进行的话就太过繁琐。 为了解决这个问题,可以通过调整各框架的执行顺序,以保证多框架混合开发时的兼容问题。

  1. JQuery

因为 JQuery 提供了暂停的方法,所以可以在不改变原有编程风格的前提下,通过暂停来实现 native 与 JQuery 的顺序执行。

// 使用 JQuery 实现业务逻辑
$(function() {
    // 业务逻辑、交互逻辑。
});

// 暂停 JQuery.ready 事件执行。。
$.holdReady(true);

 // 在 native 初始化后,恢复 JQuery.ready
native.ready(function() {
    $.holdReady(false); 
});
  1. AngularJS

AngularJS 没有暂停执行的方法,但是可以设置它不自动执行,然后在 native 初始化后再启动即可。

// 1. 禁止 AngularJS 自动执行:在 HTML 代码中,去掉标签上的 `ng-app` 属性。

// 2. 使用 AngularJS 实现业务逻辑(模块为 App)。
var app = angular.module('App', []);
app.controller('main', function($scope) {
    // 业务逻辑、交互逻辑。
});

// 3. 使用 `angular.bootstrap` 方法在 `native.ready` 中启动 AngularJS 模块。
native.ready(function () {
    // 在 angular.ready 中启动模块
    angular.element(document).ready(function () {
        // 第一个参数表示模块的根 DOM 元素。
        // 第二个参数就是模块名字。
        angular.bootstrap(document.body, ['App']);
    });
});

三、原生

1. 调用 JavaScript 方法

不论使用何种交互方式,调用 JavaScript 方法都是执行一段类似如下的代码。

// 原生调用 JavaScript 的 nativeCustomAction 方法 。
native.sendAction("nativeCustomAction", "参数1");

如果 H5 实现了便利方法,则可访问便利方法。

native.nativeCustomAction("参数1");

2. 实现 JavaScript 可访问的方法

由于平台和交互方式的不同,因此实现方式也各不同。

2.1 通过拦截 URL 实现的交互。

默认的交互方式,安卓、iOS通用,具体怎么拦截这里就不说了,主要说下 native.js 的 URL 规则:

native://method?paramēters=["arg1", "arg2"]

URL的协议名称默认为 native ,可以自定义;只有一个查询字段 parameters ,值为 JSON 数组通过 URL 编码后的字符串。 通过 URL 进行交互,只需要拦截并解析 URL 即可,其中,方法 ready 肯定为第一个拦截到的方法,开发者必须在拦截到此方法后,执行初始化 native 的操作

let method = url.host!

switch method {
case "ready": // 拦截到 ready 方法,执行 native 初始化。
    let appInfo = [String: String]()
    let js = String(format: "native.ready(%@);", String(json: appInfo))
    webView.stringByEvaluatingJavaScript(from: js);

case "nativeCustomMethod": // 使用上面 HTML 部分定义的交互文档。
    let parameters = Array<String>(json: url.queryValue(forKey: "parameters") as! String)
    let result = self.nativeCustomMethod(parameters[0], parameters[1]) // 假定回调函数是获取原生方法 nativeCustomMethod 的返回值。
    let callback = String(format: "native.callback('\(parameters[2])')('%@');", result); // 执行回调函数
    webView.stringByEvaluatingJavaScript(from: callback)

default:
    print("JavaScript 访问了尚未实现的方法 \(method) !")
}

示例中使用的关于 URL 解析方法来自 XZKit 框架中的类目拓展,下同。

2.2 安卓平台注入对象的交互方式

2.3 iOS 平台注入对象的交互方式

2.4 iOS 平台 WKWebView 注入 JS 的交互方式

  1. 创建处理交互的对象。
protocol NativeHandlerDelegate: AnyObject {
    
    func ready()
    func nativeCustomMethod(arg1: String, arg2: String, completion: ((String) -> Void)?)
    
}

class NativeHandler: NSObject, WKScriptMessageHandler {
    
    weak var delegate: NativeHandlerDelegate?
    
    init(_ userContentController: WKUserContentController, delegate: NativeHandlerDelegate?) {
        super.init()
        
        self.delegate = delegate
        
        userContentController.add(self, name: "native")
        let info = String.init(json: [:])
        let script = WKUserScript.init(source: """
            native.ready(function(method, parameters) {
                window.webkit.messageHandlers.native.postMessage({"method": method, "parameters": parameters});
            }, Native.Mode.javascript, \(info));
            """, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
        userContentController.addUserScript(script)
    }
    
    func userContentController(_ userContentController: WKUserContentController, didReceive scriptMessage: WKScriptMessage) {
        guard let message = scriptMessage.body as? [String: Any] else { return }
        guard let method = message["method"] as? String else { return }
        
        switch method {
        case "ready":
            delegate?.ready()
            
        case "nativeCustomMethod":
            guard let parameters = message["parameters"] as? [String] else { return }
            guard parameters.count >= 2 else {
                return
            }
            switch parameters.count {
            case 0, 1:
                print("参数错误:方法 nativeCustomMethod 需要三个参数,详情请查看接口文档!")
            case 2:
                delegate?.nativeCustomMethod(arg1: parameters[0], arg2: parameters[1], completion: nil)
            default:
                delegate?.nativeCustomMethod(arg1: parameters[0], arg2: parameters[1], completion: { (result) in
                    let encoded = result.addingPercentEncoding(withAllowedCharacters: .alphanumerics)!
                    let js = "native.callback(\(parameters[2]))(decodeURIComponent('\(encoded)'))"
                    scriptMessage.webView?.evaluateJavaScript(js, completionHandler: nil)
                })
            }
            
        default:
            print("执行错误:方法 \(method) 尚未实现!")
        }
    }
    
}
  1. 在需要交互的页面注入 JS 。
class WebViewController: UIViewController, NativeHandlerDelegate {

    override func loadView() {
        self.view = WKWebView.init(frame: UIScreen.main.bounds)
    }
    
    var webView: WKWebView {
        return view as! WKWebView
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // 注入 JS
        NativeHandler.init(webView.configuration.userContentController, delegate: self)
    }
    
    func ready() {
        print("document was ready.")
    }
    
    func nativeCustomMethod(arg1: String, arg2: String, completion: ((String) -> Void)?) {
        print("do someting.")
        completion?("The result of nativeCustomMethod.")
    }
    
}

Package Sidebar

Install

npm i @mlibai/native.js

Weekly Downloads

0

Version

3.3.2

License

ISC

Unpacked Size

47 kB

Total Files

8

Last publish

Collaborators

  • mlibai