[关闭]
@njy 2018-04-11T06:31:37.000000Z 字数 16388 阅读 1237

NativeAPI

前端 新浪彩通 NativeAPI


简介

NativeAPI 用于 JavaScript 与 Native Code 双向通信。


创建过程

1.在 APP 中打开一个 WebView。
2.Native Code 向 WebView 添加全局的 NativeAPI 对象,此对象应当包含 sendToNative 方法,JavaScript 通过调用此方法向 Native 发送消息。

  1. var u = navigator.userAgent;
  2. var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Adr') > -1; //android终端
  3. var isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端
  4. var send = function(message) {
  5. window.console.log("javascript -> native: " + JSON.stringify(message));
  6. if (isAndroid) {
  7. // android
  8. window.NativeAPI.sendToNative(JSON.stringify(message));
  9. } else if (isiOS) {
  10. // ios
  11. window.webkit.messageHandlers.NativeAPI.postMessage(JSON.stringify(message))
  12. }
  13. };
  14. exports.invoke = function(method, params, callback) {
  15. var message = {
  16. jsonrpc: "2.0",
  17. method: method,
  18. params: params
  19. };
  20. var id;
  21. if (callback) {
  22. id = "jsonp_" + idCounter;
  23. idCounter++;
  24. callbacks[id] = callback;
  25. message.id = id;
  26. }
  27. send(message);
  28. };

3.WebView 中的 JavaScript 在 NativeAPI 对象上注册 sendToJavaScript 方法,Native 通过调用此方法此方法向 JavaScript 发送消息。

  1. window.NativeAPI.sendToJavaScript = function(message) {
  2. window.console.log("native -> javascript: " + message);
  3. try {
  4. message = JSON.parse(message);
  5. } catch (ex) {
  6. return send({
  7. jsonrpc: "2.0",
  8. error: JSON_RPC_ERROR.PARSE_ERROR,
  9. id: null
  10. });
  11. }
  12. if (message.id) {
  13. handleCallback(message);
  14. }
  15. };

消息格式

JSON-RPC 2.0

语法:
--> 表示数据发送到服务器端
<-- 表示数据发送到客户端
位置参数形式的rpc调用:
rpc call with positional parameters:
--> {"jsonrpc":"2.0", "method": "subtract", "params":[42, 23], "id": 1}
<-- {"jsonrpc": "2.0", "result": 19,"id": 1}

  1. var message = {
  2. jsonrpc: "2.0",
  3. method: "confirm",
  4. params: { title: "", message: "", yes_btn_text: "", no_btn_text: ""},
  5. id: "jsonp_1"
  6. };

接口约定

params 为 0、1、123 之类数字时,类型全部用 String(即使用 "0"、"1"、"123" 这样的字符串)。


使用 Scheme URL 注入 Native 部分

  1. if ( /hbgj/i.test(appName) ) {
  2. window.location.href = "openetjs://start?type=nativeapi";
  3. } else if ( /gtgj/i.test(appName) ) {
  4. window.location.href = "gtgj://start?type=nativeapi";
  5. }

由 Native 提供的方法

confirm

method
params
result

JS -> Native: {method: "confirm", params: { title: "test", message: "hello world", yes_btn_text: "是的", no_btn_text: "不是" }, id: 1}
Native -> JS: {result {value: 0, YES:1, NO: 0, CLOSE: 2}, id: 1}

  1. // invoke 方法是对 sendToNative 的封装
  2. NativeAPI.invoke(
  3. "confirm", {
  4. title: "提示",
  5. message: "message",
  6. yes_btn_text: "~确定~",
  7. no_btn_text: "~取消~"
  8. },
  9. function(err, data) {
  10. if (err) {
  11. return handleError(err);
  12. }
  13. switch (data.value) {
  14. case data.YES:
  15. echo("你点了确定按钮");
  16. break;
  17. case data.NO:
  18. echo("你点了取消按钮");
  19. break;
  20. case data.CLOSE:
  21. echo("你使用其他方式关闭了弹窗");
  22. break;
  23. default:
  24. echo("未知动作,返回code是[" + data.value + "]");
  25. }
  26. }
  27. );

alert

method
params
result
  1. NativeAPI.invoke(
  2. "alert", {
  3. title: "这是标题",
  4. message: "这是消息",
  5. btn_text: "确定按钮"
  6. },
  7. function(err, data) {
  8. if (err) {
  9. return handleError(err);
  10. }
  11. switch (data.value) {
  12. case data.YES:
  13. echo("你点了确定按钮");
  14. break;
  15. case data.CLOSE:
  16. echo("你使用其他方式关闭了弹窗");
  17. break;
  18. default:
  19. echo("未知动作,返回code是[" + data.value + "]");
  20. }
  21. }
  22. );

close

method

closeAll

method

createWebView

method
params
  1. NativeAPI.invoke("createWebView", {
  2. url: window.location.origin + "xxx",
  3. controls: [
  4. {
  5. type: "title",
  6. text: "Native Page 2"
  7. }
  8. ]
  9. });

back

method

webViewCallback

method
params

login

method
params
result

getUserInfo

method
params
result
error

getDeviceInfo

method
result

makePhoneCall

method
params
result

updateTitle

method
params

storage

method
params
result

updateHeaderRightBtn

method
params

clearAppCache


isSupported

method
params
result

selectContact

method
params
result

setOrientation

method
params

setGestureBack

method
params

sendSMS

method
params
result

getCurrentPosition

method
result

scanBarcode

method
result

sharePage

method
params
result

trackEvent

method
params

startPay

method
params
result
  1. NativeAPI.invoke("startPay", {
  2. quitpaymsg: "您尚未完成支付,如现在退出,可稍后进入“个人中心->其他订单”完成支付。确认退出吗?, ",
  3. title: "商城订单",
  4. price: "145",
  5. orderid: "150513188189022",
  6. productdesc: "接机订单支付",
  7. subdesc: "专车接送",
  8. url: "http://jp.rsscc.com/Fmall/rest/client/v4/pay.htm?orderid=150513188189022",
  9. canceltype: -1
  10. }, function(err, data) {
  11. switch (data.value) {
  12. case data.SUCC:
  13. alert("支付成功, 跳转详情");
  14. break;
  15. case data.FAIL:
  16. alert("支付失败");
  17. break;
  18. case data.CANCEL:
  19. alert("您取消了支付");
  20. break;
  21. case data.PENDING:
  22. alert("目前为支付待确认状态");
  23. break;
  24. default:
  25. alert("支付异常");
  26. }
  27. });

loading

method
params
result

由 JavaScript 提供的方法

resume

method
params

back

method
result

Native -> JS: {method: "back", params:null, id: 1}
JS -> Native: {result: {preventDefault: false}, id: 1}


headerRightBtnClick

params
  1. // 先调用 updateHeaderRightBtn 设置右边按钮的文字或图片,再调用 headerRightBtnClick 设置点击了右侧按钮后做什么。
  2. rightButtonText: function() {
  3. var self = this;
  4. NativeAPI.invoke("updateHeaderRightBtn", {
  5. action: "show",
  6. text: "分享",
  7. data: {
  8. list: [1, 2, 3]
  9. }
  10. }, function(err, data) {
  11. if (err) {
  12. return handleError(err);
  13. }
  14. echo(JSON.stringify(data));
  15. });
  16. NativeAPI.registerHandler("headerRightBtnClick", function(data) {
  17. echo(JSON.stringify(data));
  18. self.share();
  19. });
  20. }


小炮NativeAPI前端实现

测试地址: http://lottery.sina.com.cn/test/NativeAPI.shtml?from=wap

native-api.js

  1. var _nativeAPI = {};
  2. (function(exports) {
  3. var JSON_RPC_ERROR = {
  4. PARSE_ERROR: {
  5. code: -32700,
  6. message: "Parse error"
  7. },
  8. INVALID_REQUEST: {
  9. code: -32600,
  10. message: "Invalid Request"
  11. },
  12. METHOD_NOT_FOUND: {
  13. code: -32601,
  14. message: "Method not found"
  15. },
  16. INVALID_PARAMS: {
  17. code: -32602,
  18. message: "Invalid params"
  19. },
  20. INTERNAL_ERROR: {
  21. code: -32603,
  22. message: "Internal error"
  23. }
  24. };
  25. var methods = {};
  26. var callbacks = {};
  27. var idCounter = 1;
  28. var send = function(message) {
  29. window.console.log("javascript -> native: " + JSON.stringify(message));
  30. window.NativeAPI.sendToNative(JSON.stringify(message));
  31. };
  32. var handleCallback = function(message) {
  33. var callback = callbacks[message.id];
  34. callbacks[message.id] = null;
  35. if (!callback) {
  36. return;
  37. }
  38. setTimeout(function() {
  39. callback(message.error || null, message.result);
  40. }, 0);
  41. };
  42. var handleInternalError = function(message) {
  43. try {
  44. message = JSON.parse(message);
  45. } catch (ex) {
  46. return;
  47. }
  48. if (message.id) {
  49. handleCallback({
  50. jsonrpc: "2.0",
  51. error: JSON_RPC_ERROR.INTERNAL_ERROR,
  52. id: message.id
  53. });
  54. }
  55. };
  56. window.NativeAPI = window.NativeAPI || {};
  57. // NativeAPI 会在 document.onload 的事件中被注册,而该事件会一直等到页面上图片加载结束之后才触发,所以 NativeAPI 速度非常慢。当发现身处 native 中的时候,将图片加载放在 NativeAPI 加载完毕之后进行。 客户端反馈iOS页面加载前会注入成功,android会在页面1/4左右加载完成
  58. if (!window.NativeAPI.sendToNative) {
  59. (function() {
  60. var buffer = [];
  61. var timer = setTimeout(function() {
  62. buffer.forEach(handleInternalError);
  63. window.NativeAPI.sendToNative = handleInternalError;
  64. }, 3000);
  65. document.addEventListener("WebViewJavascriptBridgeReady", function() {
  66. clearTimeout(timer);
  67. setTimeout(function() {
  68. // 注入的NativeAPI会覆盖前端定义的NativeAPI
  69. buffer.forEach(window.NativeAPI.sendToNative);
  70. }, 10);
  71. }, false);
  72. window.NativeAPI.sendToNative = function(message) {
  73. buffer.push(message);
  74. };
  75. try{
  76. // 加速 NativeAPI 注册
  77. // var appName = util.cookie.getItem('appName');
  78. // if (appName === 'hbgj') {
  79. // window.location.href = 'openetjs://start?type=nativeapi';
  80. // } else if (appName === 'gtgj') {
  81. // window.location.href = 'gtgj://start?type=nativeapi';
  82. // }
  83. }catch(e){}
  84. })();
  85. }
  86. window.NativeAPI.sendToJavaScript = function(message) {
  87. window.console.log("native -> javascript: " + message);
  88. try {
  89. message = JSON.parse(message);
  90. } catch (ex) {
  91. return send({
  92. jsonrpc: "2.0",
  93. error: JSON_RPC_ERROR.PARSE_ERROR,
  94. id: null
  95. });
  96. }
  97. if (message.id) {
  98. handleCallback(message);
  99. }
  100. };
  101. exports.invoke = function(method, params, callback) {
  102. var message = {
  103. jsonrpc: "2.0",
  104. method: method,
  105. params: params
  106. };
  107. var id;
  108. if (callback) {
  109. id = "jsonp_" + idCounter;
  110. idCounter++;
  111. callbacks[id] = callback;
  112. message.id = id;
  113. }
  114. send(message);
  115. };
  116. })(_nativeAPI);

需要实现的方法:

confirm

  1. _nativeAPI.invoke('confirm', confirmData, function(err, data) {
  2. if (data.value === data.YES) {
  3. ...
  4. } else {
  5. ...
  6. }
  7. });

alert

  1. _nativeAPI.invoke('alert', alertData, function(err, data) {
  2. ...
  3. });

close

  1. _nativeAPI.invoke('close');

createWebView

  1. _nativeAPI.invoke('createWebView', {url: mapUrl});

login

  1. _nativeAPI.invoke('login', null, function(err, data) {
  2. if (err === null && (data.succ == '1' || data.value === data.SUCC)) {
  3. ...
  4. }
  5. });

getUserInfo

  1. _nativeAPI.invoke('getUserInfo', {}, function(err, data) {
  2. // 管家自身已经登录
  3. if (err == null && data.jwt) {
  4. self.user = data;
  5. } else {
  6. // 没有登录,登录检测到此结束
  7. self.isLogin(false);
  8. }
  9. });
  10. /*
  11. * 因为安卓获取 userinfo 是异步的,导致登录之后 userInfo.userid 不一定能获取到,所以加个循环
  12. */
  13. var timeout = null;
  14. function getUserInfo() {
  15. _nativeAPI.invoke('getUserInfo', {}, function(err, data) {
  16. // alert(JSON.stringify(data));
  17. // iOS 3.2 全部返回了
  18. if (err == null && data.jwt) {
  19. self.user = data;
  20. if (timeout) {
  21. clearTimeout(timeout);
  22. timeout = null;
  23. }
  24. } else {
  25. timeout = setTimeout(getUserInfo, 500);
  26. }
  27. });
  28. }
  29. getUserInfo();

getDeviceInfo

  1. _nativeAPI.invoke('getDeviceInfo', null, function(err, data) {
  2. if (err === null) {
  3. ...
  4. } else {
  5. ...
  6. }
  7. });

updateTitle

  1. _nativeAPI.invoke('updateTitle', {text: title});

storage

  1. _nativeAPI.invoke('storage', {
  2. action: 'set',
  3. key: 'lastHotel',
  4. value: JSON.stringify(data.info)
  5. });

getCurrentPosition

  1. _nativeAPI.invoke('getCurrentPosition', null, function(err, result) {
  2. if (!result.errDesc) {
  3. try {
  4. util.currentPosition = result;
  5. var lat = util.currentPosition.latitude;
  6. var lng = util.currentPosition.longitude;
  7. if(lat && (lat > 90) ){
  8. util.currentPosition.latitude = lng;
  9. util.currentPosition.longitude = lat;
  10. }
  11. self.getAddressByCoor(currentPlatName);
  12. } catch(e) {
  13. util.currentPosition = null;
  14. util.alert({
  15. title: '获取位置失败',
  16. content: '请在系统设置中打开“定位服务”,并允许“'+currentPlatName+'”获取您的位置'
  17. });
  18. }
  19. } else{
  20. util.currentPosition = null;
  21. util.alert({
  22. title: '获取位置失败',
  23. content: '请在系统设置中打开“定位服务”,并允许“'+currentPlatName+'”获取您的位置'
  24. });
  25. }
  26. });

scanBarcode

  1. _nativeAPI.invoke('scanBarcode', {}, function(err, value) {
  2. // value扫描得到的字符串
  3. })

sharePage

  1. _nativeAPI.invoke('sharePage', {
  2. title: '分享',
  3. desc: '',
  4. link: '',
  5. imgUrl: '',
  6. type: 'all'
  7. }, function(err, value) {
  8. if (err === null && value) {
  9. ...
  10. } else {
  11. ...
  12. }
  13. })

startPay

  1. _nativeAPI.invoke('startPay', {
  2. title: '支付 - ' + self.name(),
  3. price: data.orderInfo.payPrice,
  4. orderid: data.orderInfo.orderId
  5. }, function(err, nativeData) {
  6. if (nativeData.value === nativeData.SUCC) {
  7. ...
  8. } else if(nativeData.value === nativeData.FAIL) {
  9. /*
  10. * 特殊说明: 支付模块会一直监听支付结果,直到支付成功;否则就是用户 cancel 了。不存在 FAIL 这个
  11. */
  12. ...
  13. } else if (nativeData.value === nativeData.CANCEL) {
  14. ...
  15. } else {
  16. ...
  17. }
  18. });

loading

  1. _nativeAPI.invoke('loading', {show: true, text: '加载中'}, function() {
  2. ...
  3. });

根据页面来源来执行run.js,所有业务逻辑都在run.js中执行

  1. ;(function(win, doc) {
  2. function run() {
  3. // 业务代码
  4. ...
  5. }
  6. util.PLATFORM = {
  7. BROWSER: 1,
  8. WEIXIN: 2,
  9. XIAOPAO: 3,
  10. CURRENT: 1,
  11. CURRENT_STR: '普通浏览器'
  12. };
  13. /*
  14. * 『微信支付』的界面, url 没有 from 参数的情况,从而导致判断失败。将当前 PLATFORM 记在 cookie 中,根据 cookie 来判断是否在 native 中。 该 cookie 名是__platform
  15. * NativeAPI 会在 document.onload 的事件中被注册,而该事件会一直等到页面上图片加载结束之后才触发,所以 NativeAPI 速度非常慢。当发现身处 native 中的时候,将图片加载放在 NativeAPI 加载完毕之后进行。
  16. */
  17. if (win.location.href.indexOf('from=xiaopao') >= 0) {
  18. util.PLATFORM.CURRENT = util.PLATFORM.XIAOPAO;
  19. util.PLATFORM.CURRENT_STR = '小炮';
  20. } else if (win.navigator.userAgent.toLowerCase().indexOf('micromessenger') >= 0) {
  21. util.PLATFORM.CURRENT = util.PLATFORM.WEIXIN;
  22. util.PLATFORM.CURRENT_STR = '微信';
  23. } else {
  24. if (util.cookie.getItem('__platform')) {
  25. util.PLATFORM.CURRENT = win.parseInt(util.cookie.getItem('__platform'));
  26. if (util.PLATFORM.CURRENT == util.PLATFORM.WEIXIN) {
  27. util.PLATFORM.CURRENT_STR = '微信';
  28. } else if (util.PLATFORM.CURRENT == util.PLATFORM.XIAOPAO) {
  29. util.PLATFORM.CURRENT_STR = '小炮';
  30. }
  31. }
  32. }
  33. util.cookie.setItem('__platform', util.PLATFORM.CURRENT);
  34. switch(util.PLATFORM.CURRENT) {
  35. case util.PLATFORM.BROWSER:
  36. run();
  37. break;
  38. case util.PLATFORM.WEIXIN:
  39. if (util.cookie.getItem('wx_open_id')) {
  40. m.loadScript('http://res.wx.qq.com/open/js/jweixin-1.0.0.js', function() {
  41. run();
  42. });
  43. } else {
  44. m.loadScript('http://res.wx.qq.com/open/js/jweixin-1.0.0.js', function() {
  45. run();
  46. });
  47. // 需要微信网页授权
  48. // window.location.href = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxa2f3f0d342b39a3a&redirect_uri='
  49. // + window.apiRootPath
  50. // + '&response_type=code&scope=snsapi_base#wechat_redirect';
  51. }
  52. break;
  53. case util.PLATFORM.XIAOPAO:
  54. m.loadScript(__uri('lib/native-api.js'), function() {
  55. // util.speedUpNativeAPI(); (window.location.href = "openetjs://start?type=nativeapi";)
  56. _nativeAPI.invoke('getDeviceInfo', null, function(err, result) {
  57. try {
  58. // util.COMMON_PARAMS 对象存储APP提交给PHP所有的公共参数
  59. util.COMMON_PARAMS.comm_imei = result.imei;
  60. } catch(e) {
  61. util.COMMON_PARAMS.comm_imei = 'unknown';
  62. }
  63. run();
  64. });
  65. });
  66. break;
  67. }
  68. })(window, document);
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注