Package Exports
- @mlibai/native.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@mlibai/native.js) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
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 方法中进行的话就太过繁琐。
为了解决这个问题,可以通过调整各框架的执行顺序,以保证多框架混合开发时的兼容问题。
- JQuery
因为 JQuery 提供了暂停的方法,所以可以在不改变原有编程风格的前提下,通过暂停来实现 native 与 JQuery 的顺序执行。
// 使用 JQuery 实现业务逻辑
$(function() {
// 业务逻辑、交互逻辑。
});
// 暂停 JQuery.ready 事件执行。。
$.holdReady(true);
// 在 native 初始化后,恢复 JQuery.ready
native.ready(function() {
$.holdReady(false);
});- 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 的交互方式
- 创建处理交互的对象。
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) 尚未实现!")
}
}
}- 在需要交互的页面注入 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.")
}
}