From 977bb08c3f63d7a1223a76677ca18b235b6cc6f8 Mon Sep 17 00:00:00 2001 From: DCloud_LXH <283700113@qq.com> Date: Sat, 22 Jan 2022 16:16:27 +0800 Subject: [PATCH] =?UTF-8?q?update:=201.=20=E4=BF=AE=E5=A4=8D=E4=BE=A7?= =?UTF-8?q?=E8=BE=B9=E6=A0=8F=E9=81=AE=E6=8C=A1=E5=AF=BC=E8=88=AA=E4=B8=8B?= =?UTF-8?q?=E8=BE=B9=E7=BA=BF=202.=20=E6=B7=BB=E5=8A=A0=E5=BA=95=E9=83=A8f?= =?UTF-8?q?ooter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/.vuepress/config.js | 10 +- docs/.vuepress/theme/components/Footer.vue | 316 +++ docs/.vuepress/theme/components/Navbar.vue | 4 +- docs/.vuepress/theme/layouts/Layout.vue | 5 +- docs/.vuepress/theme/styles/footer copy.styl | 323 ++++ docs/.vuepress/theme/styles/footer.styl | 211 ++ docs/.vuepress/theme/styles/index.styl | 3 + docs/tutorial/CORS.md | 114 +- docs/tutorial/native-js.md | 1802 +++++++++++++++++- 9 files changed, 2782 insertions(+), 6 deletions(-) create mode 100644 docs/.vuepress/theme/components/Footer.vue create mode 100644 docs/.vuepress/theme/styles/footer copy.styl create mode 100644 docs/.vuepress/theme/styles/footer.styl diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index c2cda23b9..7afb4aa80 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -65,4 +65,12 @@ const config = { } } -module.exports = config \ No newline at end of file +module.exports = config + +/** + * 1. 路由映射:由于文档文件变动,将之前的链接重定向到新的链接 + * 2. 修复文档渲染错误的 Bug + * 3. 搜索 配置更新 + * 4. 更新 slugify 配置规则 + * 5. 合并文档 + */ \ No newline at end of file diff --git a/docs/.vuepress/theme/components/Footer.vue b/docs/.vuepress/theme/components/Footer.vue new file mode 100644 index 000000000..da38bb3c9 --- /dev/null +++ b/docs/.vuepress/theme/components/Footer.vue @@ -0,0 +1,316 @@ + + + + + diff --git a/docs/.vuepress/theme/components/Navbar.vue b/docs/.vuepress/theme/components/Navbar.vue index bd9e2dbdc..cbf06ea0f 100644 --- a/docs/.vuepress/theme/components/Navbar.vue +++ b/docs/.vuepress/theme/components/Navbar.vue @@ -162,7 +162,7 @@ export default { { let sideTop = this.navbarHeight - scrollTop sideTop <= this.subNavBarHeight && (sideTop = this.subNavBarHeight) - this.sideBar && (this.sideBar.style.top = `${sideTop}px`) + this.sideBar && (this.sideBar.style.top = `${sideTop + 1}px`) } if (scrollTop >= this.mainNavBarHeight) { @@ -195,7 +195,7 @@ export default { if(!os.pc) return this.navbarHeight = this.navbar.clientHeight this.subNavBarHeight = this.subNavBar.clientHeight - this.sideBar.style.top = `${this.navbarHeight}px` + this.sideBar.style.top = `${this.navbarHeight + 1}px` }) } } diff --git a/docs/.vuepress/theme/layouts/Layout.vue b/docs/.vuepress/theme/layouts/Layout.vue index 408ee028f..d1486ceca 100644 --- a/docs/.vuepress/theme/layouts/Layout.vue +++ b/docs/.vuepress/theme/layouts/Layout.vue @@ -38,6 +38,7 @@ @@ -48,6 +49,7 @@ import Home from '@theme/components/Home.vue' import Navbar from '@theme/components/Navbar.vue' import Page from '@theme/components/Page.vue' import Sidebar from '@theme/components/Sidebar.vue' +import Footer from '@theme/components/Footer.vue'; import { resolveSidebarItems, forbidScroll } from '../util' import navProvider from '../mixin/navProvider'; @@ -58,7 +60,8 @@ export default { Home, Page, Sidebar, - Navbar + Navbar, + Footer }, data () { return { diff --git a/docs/.vuepress/theme/styles/footer copy.styl b/docs/.vuepress/theme/styles/footer copy.styl new file mode 100644 index 000000000..bce593954 --- /dev/null +++ b/docs/.vuepress/theme/styles/footer copy.styl @@ -0,0 +1,323 @@ +$w3cColor = #007aff +$html5Color = #fb6f18 + +.footNav + width 90% + display flex + justify-content center + +/* 通用底部导航栏---移动端 */ +@media screen and (max-width $MQMobile) + #aboutusBox + max-height 650px + + + .footNavItem + margin-right 10px + + .navLine + width 90% + + .companyBox + flex-direction column + align-items center + + .anbaoInfo + width 330px + text-align center + padding-bottom 30px + + .companyInfo, .anbaoInfo + margin-left 0px + + .beianBox + flex-direction column + + .beian + margin 5px 0 + + .domainImgBox + display flex + flex-direction row + align-items center + + .hbLogo + margin 40px 0 20px 0 + + .navItemDetail + width 100px + margin-right 0px + + .navItemDetailBox + display flex + flex-direction row + flex-wrap wrap + align-items flex-start + justify-content flex-start + + #moreInfo + flex-direction column + margin 20px 20px 0 20px + +/* 通用底部导航栏---ipad */ +@media screen and (min-width $MQMobile) and (max-width 1190px) + #footNavClassify + min-width 145px + + .footNav + margin 30px 10px + + #aboutusBox + flex-direction row + max-height 465px + + .footNavItem + width 20% + + .footNavItem + margin-right 5% + + .navLine + width 70% + + .beianBox + flex-direction column + + .beian + margin 10px 0 + + .domainImgBox + display flex + flex-direction row + align-items center + + .companyInfo, .anbaoInfo + margin-left 0px + + .anbaoInfo + text-align center + padding-bottom 30px + + .footNav + margin 0 20px + + .navItemDetail + width 140px + margin-right 25px + + .footNavItem + width 100% + + .hbLogo + margin 50px 0 30px 0 + + #moreInfo + flex-direction column + margin 20px 20px 0 20px + +/* 小屏笔记本---pc */ +@media only screen and (min-width 1191px) and (max-width 1500px) + #footNavClassify + min-width 280px + margin-right 2% + + #footNavBox + padding-bottom 45px + + .footNav + margin 30px 10px + justify-content space-around + + .footNavItem + margin-right 5% + + .navLine + width 90% + + .navItemDetailBox + max-height 285px + + #aboutusBox + flex-direction row + width 70% + + .footNavItem + width 20% + + .companyInfo, .anbaoInfo + margin-left 20px + + .beianBox + flex-direction row + + .beian + margin 10px 0 + + .hbLogo + margin 50px 0 30px 0 + + .navItemDetail + width 140px + + #moreInfo + flex-direction row + margin 5px 20px 0 20px + +/* 通用底部导航栏---pc */ +@media screen and (min-width 1501px) + #footNavClassify + width 20% + margin-right 20px + min-width 350px + + #footNavBox + padding-bottom 45px + + .footNav + margin 30px 10px + + .footNavItem + margin-right 50px + + .navLine + width 100% + + .navItemDetailBox + max-height 285px + + #aboutusBox + flex-direction column + width 80% + max-height 370px + + .companyInfo, .anbaoInfo + margin-left 20px + + .beianBox + flex-direction row + + .beian + margin 10px 0 + + .hbLogo + margin 50px 0 30px 0 + + .navItemDetail + width 140px + + #moreInfo + flex-direction row + margin 5px 20px 0 20px + +/* 通用底部导航栏---公共 */ +#footNavBox + display flex + flex-direction column + width 100% + flex-wrap wrap + /* justify-content left */ + justify-content center + align-items center + margin-top 30px + background #fbfbfb + box-shadow #00000021 0px -20px 15px -15px + + .w3c + text-decoration none + color $w3cColor !important + font-weight 800 !important + + .html5 + text-decoration none + color $html5Color !important + font-weight 800 !important + + .hbLogo + display inline-block + width 160px + height 25px + + background url('https//bjetxgzv.cdn.bspapp.com/VKCEYUGU-dc-site/d141de00-607f-11eb-8a36-ebb87efcf8c0.svg') + no-repeat center + + .beianBox + display flex + align-items center + + .domainImgBox + display flex + flex-direction row + align-items center + + .domainImg + width 20px + height 20px + margin-left 20px + margin-right 5px + + .beian + font-size 14px + text-decoration none + color $w3cColor !important + cursor pointer + + &:hover + color #333333 !important + transition all 0.3s + + .footNavItem + display flex + flex-direction column + align-items flex-start + margin-top 20px + + .navLine + height 1px + background #e1e1e1 + margin-bottom 15px + + .navItemTitle + display flex + width 100px + height 35px + line-height 35px + font-size 16px + font-weight bold + font-family SourceHanSansCN-Regular serif + color #414141 + + .navItemDetail + display inline-block + cursor pointer + text-decoration none + color #3c3c3c !important + text-align left + margin-bottom 12px + font-size 14px + /* font-family CambayDevanagari arial,sans-serif */ + font-family SourceHanSansCN-Regular serif + font-weight 400 + line-height 22px + + .navItemDetail:hover + color $accentColor !important + transition all 0.2s + + .companyBox + display flex + justify-content center + + .companyInfo, .anbaoInfo + font-size 14px + color #565656 !important + font-family SourceHanSansCN-Regular serif + + .navItemDetailBox + width 100% + display flex + flex-direction column + flex-wrap wrap + +#aboutusBox + display flex + flex-wrap wrap diff --git a/docs/.vuepress/theme/styles/footer.styl b/docs/.vuepress/theme/styles/footer.styl new file mode 100644 index 000000000..318dc6b91 --- /dev/null +++ b/docs/.vuepress/theme/styles/footer.styl @@ -0,0 +1,211 @@ +$w3cColor = #007aff +$html5Color = #fb6f18 + +.footNav + width 90% + display flex + justify-content center + +/* 通用底部导航栏---公共 */ +#footNavBox + width 100% + margin-top 30px + padding-bottom 30px + display flex + flex-direction column + flex-wrap wrap + justify-content center + align-items center + background #fbfbfb + box-shadow #00000021 0px -20px 15px -15px + + .w3c + text-decoration none + color $w3cColor !important + font-weight 800 !important + + .html5 + text-decoration none + color $html5Color !important + font-weight 800 !important + + .hbLogo + display inline-block + width 160px + height 25px + margin-top 30px + background url('https://bjetxgzv.cdn.bspapp.com/VKCEYUGU-dc-site/d141de00-607f-11eb-8a36-ebb87efcf8c0.svg') no-repeat center + + .beianBox + display flex + align-items center + + .domainImgBox + display flex + flex-direction row + align-items center + + .domainImg + width 20px + height 20px + margin-left 20px + margin-right 5px + + .beian + font-size 14px + text-decoration none + color $w3cColor !important + cursor pointer + + &:hover + color #333333 !important + transition all 0.3s + + .footNavItem + display flex + flex-direction column + align-items flex-start + margin 20px 50px 0 0 + + .navLine + width 100% + height 1px + background #e1e1e1 + margin-bottom 15px + + .navItemTitle + display flex + width 100px + height 35px + line-height 35px + font-size 16px + font-weight bold + font-family SourceHanSansCN-Regular serif + color #414141 + + .navItemDetail + display inline-block + cursor pointer + text-decoration none + color #3c3c3c !important + text-align left + margin-bottom 12px + font-size 14px + /* font-family CambayDevanagari arial,sans-serif */ + font-family SourceHanSansCN-Regular serif + font-weight 400 + line-height 22px + + .navItemDetail:hover + color $accentColor !important + transition all 0.2s + + .companyBox + display flex + justify-content center + margin 20px 0 20px 0 + + .companyInfo, .anbaoInfo + font-size 14px + color #565656 !important + font-family SourceHanSansCN-Regular serif + + .navItemDetailBox + width 100% + display flex + flex-direction column + flex-wrap wrap + +#aboutusBox + display flex + flex-direction column + flex-wrap wrap + +@media screen and (max-width $MQMobile) + #footNavBox + #footNavClassify + width 20% + + #aboutusBox + width 70% + + .footNavItem + margin-right 20px + +@media screen and (max-width 880px) + #footNavBox + .companyBox + height 50px + flex-direction column + justify-content space-around + + .footNav + #aboutusBox + height 655px !important + +@media screen and (max-width 1150px) + #footNavBox + .beianBox + height 80px + flex-direction column + justify-content space-around + +/* 通用底部导航栏---移动端 */ +@media screen and (max-width 1050px) + #footNavBox + .footNav + justify-content flex-start + + #footNavClassify + width 30% + + #aboutusBox + height 555px + + .footNavItem + max-width 100px + + margin-right 10px + +@media screen and (max-width 1400px) + #footNavBox + #footNavClassify + width 20% + + .footNavItem + min-width 150px + + .navItemDetailBox + height 550px + + .navItemDetail + margin-right 0px + + #aboutusBox + height 420px + + .footNavItem + margin-right 20px + +@media screen and (min-width $MQMobile) + #footNavClassify + width 30% + + .footNavItem + min-width 290px + + .navItemDetailBox + height 300px + display flex + justify-content center + flex-direction column + + .navItemDetail + margin-right 70px + + #aboutusBox + width 70% + height 400px + + .footNavItem:last-child + margin-right 0 diff --git a/docs/.vuepress/theme/styles/index.styl b/docs/.vuepress/theme/styles/index.styl index 4437340c7..138989df5 100644 --- a/docs/.vuepress/theme/styles/index.styl +++ b/docs/.vuepress/theme/styles/index.styl @@ -78,6 +78,9 @@ body.forbid_scroll height 100% overflow hidden +main.page + padding-bottom 0px + {$contentClass}:not(.custom) > *:first-child margin-top 0 diff --git a/docs/tutorial/CORS.md b/docs/tutorial/CORS.md index 3e4066a54..43d19c170 100644 --- a/docs/tutorial/CORS.md +++ b/docs/tutorial/CORS.md @@ -1 +1,113 @@ -#### 跨域 \ No newline at end of file +# 什么是跨域 +跨域是浏览器的专用概念,指js代码访问自己来源站点之外的站点。比如A站点网页中的js代码,请求了B站点的数据,就是跨域。 +A和B要想被认为是同域,则必须有相同的协议(比如http和https就不行)、相同域名、和相同端口号(port)。 + +如果你是做App、小程序等非H5平台,是不涉及跨域问题的。 +稍微例外的是iOS的wkWebview,在5+App,或uni-app的web-view组件及renderjs中,由于WKWebview限制也会产生跨域,这方面另见专题文章:[https://ask.dcloud.net.cn/article/36348](https://ask.dcloud.net.cn/article/36348)。uni-app在App的普通js代码不运行在Webview下,不存在跨域问题。 + +由于uni-app是标准的前后端分离模式,开发h5应用时如果前端代码和后端接口没有部署在同域服务器,就会被浏览器报跨域。 + +# 如果前端要callfunction连接unicloud云函数 + +在h5页面里callfunction会跨域,此时需在unicloud的web控制台配置域名白名单,被加白的域名可以跨域callfunction。详见:[https://uniapp.dcloud.net.cn/uniCloud/quickstart?id=useinh5](https://uniapp.dcloud.net.cn/uniCloud/quickstart?id=useinh5) + +另外运行期间在HBuilderX的内置浏览器里是不存在跨域的。 + +# 如果前端要连接传统后台服务器 + +分部署时的跨域方案和调试时的跨域方案,具体见下: + +## 部署时的跨域解决方案 + +- 方案1:最利索的,当然还是将前端代码和后端接口部署在同域的web服务器上 +- 方案2:由后台服务器配置策略,设为允许跨域访问。 + +例如:前端页面部署在uniCloud的前端页面托管里,但是需要访问自己服务器的接口,这时候需要在服务端允许前端页面托管的域名跨域访问。不同的服务端框架允许跨域的配置不一样,这里不再一一列举仅以eggjs为例。 + +(1)安装egg-cors包 + +```shell +npm i egg-cors --save +``` + +(2)在plugin.js中设置开启cors + +```js +exports.cors = { + enable: true, + package: 'egg-cors', +}; +``` + +(3)在config.default.js中配置 + +```js +config.security = { + domainWhiteList: [ '前端网页托管的域名' ], +}; +``` + +## 调试时的跨域解决方案 +前端工程师调试时,运行起来的前端代码在uni-app自带的web服务器中,而不是部署在后台业务服务器上,此时就会遇到跨域。 +除了协调后端配置允许跨域,其实也可以自己解决跨域问题。共3种方案可选。 + +### 方案1 使用HBuilderX内置浏览器 + +这个内置浏览器经过官方处理,不存在跨域问题,简单易用,推荐使用。(需HBuilderX 2.6以上) +在打开页面后,点HBuilderX右上角的预览,即可打开内部浏览器。或者在运行菜单里选择运行到内置浏览器也可以。 +[attach]38169[/attach] + +### 方案2 配置webpack-dev-server代理 +下面是一篇比uni官网文档更详细的配置指南,这里就直接贴地址了:[https://juejin.im/post/5e43b2645188254902765766](https://juejin.im/post/5e43b2645188254902765766) + +### 方案3 给浏览器安装跨域插件,禁止浏览器报跨域 + +> 本插件并非万能,请仔细阅读与学习浏览器安全策略相关知识,不懂这些知识在评论里瞎喷的,官方不会回复。 + +当我们使用谷歌浏览器调试ajax请求的时候可能会遇到这两个问题: + +* 跨域资源共享 详见:[CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) +* 跨源读取阻塞 详见:[CORB](https://www.chromestatus.com/feature/5629709824032768) + +最常见的就是关于**跨域资源共享**的问题,也就是我们通常说的跨域。当我们本地服务器预览页面,使用ajax访问远程服务器的内容时就会请求失败,比如:本地预览的地址是:http://localhost:8080/,访问的接口地址是http://dcloud.io/api。 + +如果仅仅是为了本地预览,可以使用Chrome浏览器插件来协助调试。 +**!!!** 本插件只能解决**简单请求**的跨域调试([点击搜索什么是简单请求](https://www.baidu.com/s?wd=%E7%AE%80%E5%8D%95%E8%AF%B7%E6%B1%82&tn=84053098_3_dg&ie=utf-8))。对于非简单请求的OPTION预检([点击搜索什么是预检请求](https://www.baidu.com/s?ie=utf-8&f=3&rsv_bp=1&tn=84053098_3_dg&wd=%E9%A2%84%E6%A3%80%E8%AF%B7%E6%B1%82&oq=OPTION%25E9%25A2%2584%25E6%25A3%2580&rsv_pq=a0831c7c0000a93c&rsv_t=0313nBZdJJqdOJUR7zNSs%2BMXe8O6I0B9hizxu4eiVIV%2BBy5DUc%2FsouJj%2BQH2dyTBn%2BfLQg&rqlang=cn&rsv_enter=1&inputT=2653&rsv_sug3=3&rsv_sug1=2&rsv_sug7=100&rsv_sug2=1&prefixsug=%25E9%25A2%2584%25E6%25A3%2580&rsp=1&rsv_sug4=2654))以及线上服务器也有跨域需求的用户,可以[服务端配合解决](https://www.baidu.com/s?wd=%E6%9C%8D%E5%8A%A1%E7%AB%AF%E8%B7%A8%E5%9F%9F&tn=84053098_3_dg&ie=utf-8)。 + +#### Chrome插件名称:Allow-Control-Allow-Origin: * + +#### 安装方式: +- 在线安装 +使用谷歌浏览器直接打开插件地址[https://chrome.google.com/webstore/detail/allow-control-allow-origi/nlfbmbojpeacfghkpbjhddihlkkiljbi](https://chrome.google.com/webstore/detail/allow-control-allow-origi/nlfbmbojpeacfghkpbjhddihlkkiljbi)安装即可 +- 离线安装 +国内用户如果无法在线安装,可在本页面底部下载附件,离线安装 +1. 下载得到:Allow-Control-Allow-Origin.crx +2. 点击浏览器右上角的菜单按钮打开谷歌浏览器的扩展管理页面 + +[attach]27247[/attach] + +[attach]27248[/attach] + +3. 将下载的扩展插件拖入扩展管理页面 + +[attach]27257[/attach] + +[attach]27258[/attach] + + +#### 使用方式 +1. 打开待调试的页面 +2. 在扩展栏目找到安装的插件,点击打开插件配置 +3. 输入想要进行跨域调试的接口的地址,点击添加即可 + +[attach]27250[/attach] + +#### 注意事项 +* 此插件适合本地调试使用,线上部署如果和接口不同域还需要服务端配合。 +* 如果实际响应的内容与浏览器预期的内容有差异还可能被[CORB](https://www.chromestatus.com/feature/5629709824032768)策略所阻止。 + +#### firefox跨域插件 +[firefox跨域插件(注意firefox的css兼容问题)](https://addons.mozilla.org/zh-CN/firefox/addon/access-control-allow-origin/) + +## 其他历史问题 +HBuilderX 2.3.0版在某些情况下会报跨域,请升级2.3.1+解决 \ No newline at end of file diff --git a/docs/tutorial/native-js.md b/docs/tutorial/native-js.md index 52479fb11..2faa195dd 100644 --- a/docs/tutorial/native-js.md +++ b/docs/tutorial/native-js.md @@ -1 +1,1801 @@ -#### Native.js \ No newline at end of file +## 概述 +Native.js技术,简称NJS,是一种将手机操作系统的原生对象转义,映射为JS对象,在JS里编写原生代码的技术。 +如果说Node.js把js扩展到服务器世界,那么Native.js则把js扩展到手机App的原生世界。 +HTML/JS/Css全部语法只有7万多,而原生语法有几十万,Native.js大幅提升了HTML5的能力。 +NJS突破了浏览器的功能限制,也不再需要像Hybrid那样由原生语言开发插件才能补足浏览器欠缺的功能。 +NJS编写的代码,最终需要在HBuilder里打包发行为App安装包,或者在支持Native.js技术的浏览器里运行。目前Native.js技术不能在普通手机浏览器里直接运行。 + +- NJS大幅扩展了HTML5的能力范围,原本只有原生或Hybrid App的原生插件才能实现的功能如今可以使用JS实现。 +- NJS大幅提升了App开发效率,将iOS、Android、Web的3个工程师组队才能完成的App,变为1个web工程师就搞定。 +- NJS不再需要配置原生开发和编译环境,调试、打包均在HBuilder里进行。没有mac和xcode一样可以开发iOS应用。 +- 如果不熟悉原生API也没关系,我们汇总了很多NJS的代码示例,复制粘贴就可以用。[http://ask.dcloud.net.cn/article/114](http://ask.dcloud.net.cn/article/114) + +再次强调,Native.js不是一个js库,不需要下载引入到页面的script中,也不像nodejs那样有单独的运行环境,Native.js的运行环境是集成在5+runtime里的,使用HBuilder打包的app或流应用都可以直接使用Native.js。 + +## 注意事项: +Uni-app不支Native.js执行UI相关操作的API调用及webview相关API调用。将失效无法正常使用。Uni-app不推荐使用Native.js + +### 技术要求 +由于NJS是直接调用Native API,需要对Native API有一定了解,知道所需要的功能调用了哪些原生API,能看懂原生代码并参考原生代码修改为JS代码。 +否则只能直接copy别人写好的NJS代码。 + +## 开始使用 +### 判断平台 +Native API具有平台依赖性,所以需要通过以下方式判断当前的运行平台: +``` javascript +function judgePlatform(){ + switch ( plus.os.name ) { + case "Android": + // Android平台: plus.android.* + break; + case "iOS": + // iOS平台: plus.ios.* + break; + default: + // 其它平台 + break; + } +} +``` + +### 类型转换 +在NJS中调用Native API或从Native API返回数据到NJS时会自动转换数据类型。 +#### 类型转换表 +| 类型 | Objective-C | Java | JavaScript | +| :-------- | --------:| :--: | +| 基本数据 | byte/short/int/long/float/double/... | byte/short/int/long/float/double/... | Number | +| 字符 | char | char | String | +| 字符串 | NSString/@"" | String/"" | String | +| 数组 | @[1,2,3]/NSArray | new XXX[] | InstanceObject | +| 类 | @interface | class | ClassObject | +| 对象(实例)| * | * | InstanceObject | +| 空对象 | nil | null | null | +| 其它 | Protocol | Interface | Object(JSON) | + +### 其他转换 +- Android原生应用的主Activity对象 转为plus.android.runtimeMainActivity() +Android的主Activity对象是启动应用时自动创建的,不是代码创建,此时通过plus.android.runtimeMainActivity()方法获取该Activity对象 +- Objective-C方法冒号剔除 +[pos setPositionX:(int)x Y:(int)y;] 转为 pos.setPositionXY(x,y); +OC语法中方法的定义格式为: +“(返回值类型) 函数名: (参数1类型) 形参1 参数2名称: (参数2类型) 形参2” +方法的完整名称为: “函数名:参数2名称:”。 +如:“(void)setPositionX:(int)x Y:(int)y;”,方法的完整名称为“setPositionX:Y:”,调用时语法为:“[pos setPositionX:x Y:y];”。 +在JS语法中函数名称不能包含“:”字符,所以OC对象的方法名映射成NJS对象方法名时将其中的“:”字符自动删除,上面方法名映射为“setPositionXY”,在NJS调用的语法为:“pos.setPositionXY(x,y);”。 +- 文件路径转换 +Web开发里使用的image/1.png是该web工程的相对路径,而原生API中经常需要使用绝对路径,比如/sdcard/apptest/image/1.png,此时使用这个扩展方法来完成转换:plus.io.convertLocalFileSystemURL("image/1.png") + +### 概念 +#### 类对象 +由于JavaScript中本身没有类的概念,为了使用Native API层的类,在NJS中引入了类对象(ClassObject)的概念,用于对Native中的类进行操作,如创建类的实例对象、访问类的静态属性、调用类的静态方法等。其原型如下: +``` javascript +Interface ClassObject { + function Object plusGetAttribute( String name ); + function void plusSetAttribute( String name, Object value ); +} +``` + +**获取类对象** +在iOS平台我们可以通过plus.ios.importClass(name)方法导入类对象,参数name为类的名称;在Android平台我们可以通过plus.android.importClass(name)方法导入类对象,其参数name为类的名称,必须包含完整的命名空间。 + +**示例:** +``` javascript +// iOS平台导入NSNotificationCenter类 +var NSNotificationCenter = plus.ios.importClass("NSNotificationCenter"); + +// Android平台导入Intent类 +var Intent = plus.android.importClass("android.content.Intent"); +``` +获取类对象后,可以通过类对象“.”操作符获取类的静态常量属性、调用类的静态方法,类的静态非常量属性需通过plusGetAttribute、plusSetAttribute方法操作。 + +#### 实例对象 +在JavaScript中,所有对象都是Object,为了操作Native层类的实例对象,在NJS中引入了实例对象(InstanceObject)的概念,用于对Native中的对象进行操作,如操作对象的属性、调用对象的方法等。其原型如下: +``` javascript +Interface InstanceObject { + function Object plusGetAttribute( String name ); + function void plusSetAttribute( String name, Object value ); +} +``` +**获取实例对象** +有两种方式获取类的实例对象,一种是调用Native API返回值获取,另一种是通过new操作符来创建导入的类对象的实例,如下: +``` javascript +// iOS平台导入NSDictionary类 +var NSDictionary = plus.ios.importClass("NSDictionary"); +// 创建NSDictionary的实例对象 +var ns = new NSDictionary(); + +// Android平台导入Intent类 +var Intent = plus.android.importClass("android.content.Intent"); +// 创建Intent的实例对象 +var intent = new Intent(); +``` +获取实例对象后,可以通过实例对象“.”操作符获取对象的常量属性、调用对象的成员方法,实例对象的非常量属性则需通过plusGetAttribute、plusSetAttribute方法操作。 + +#### 操作对象的属性方法 +- 常量属性 +获取对象后就可以通过“.”操作符获取对象的常量属性,如果是类对象则获取的是类的静态常量属性,如果是实例对象则获取的是对象的成员常量属性。 + +- 非常量属性 +如果Native层对象的属性值在原生环境下被更改,此时使用“.”操作符获取到对应NJS对象的属性值就可能不是实时的属性值,而是该Native层对象被映射为NJS对象那一刻的属性值。 +为获取获取Native层对象的实时属性值,需调用NJS对象的plusGetAttribute(name)方法,参数name为属性的名称,返回值为属性的值。调用NJS对象的plusSetAttribute(name,value)方法设置Native层对象的非常量属性值,参数name为属性的名称,value为要设置新的属性值。 +`注意:使用plusGetAttribute(name)方法也可以获取Native层对象的常量属性值,但不如直接使用“.”操作符来获取性能高。` + +- 方法 +获取对象后可以通过“.”操作符直接调用Native层方法,如果是类对象调用的是Native层类的静态方法,如果是实例对象调用的是Native层对象的成员方法。 +`注意:在iOS平台由于JS语法的原因,Objective-C方法名称中的“:”字符转成NJS对象的方法名称后将会被忽略,因此在NJS中调用的方法名需去掉所有“:”字符。` + +- 类的继承 +Objective-C和Java中类如果存在继承自基类,在NJS中对应的对象会根据继承关系递归将所有基类的公有方法一一换成NJS对象的方法,所有基类的公有属性也可以通过其plusGetAttribute、plusSetAttribute方法访问。 + +### 开始写NJS +使用NJS调用Native API非常简单,基本步骤如下: +1. 导入要使用到的类; +2. 创建类的实例对象(或者调用类的静态方法创建); +3. 调用实例对象的方法; + +以下例子使用NJS调用iOS和Android的原生弹出提示框(类似但不同于js的alert)。 + +#### Android +以下代码在Android平台展示调用Native API显示系统提示框。 +首先是Android原生 Java代码,用于比对参考: +``` java +import android.app.AlertDialog; +//... +// 创建提示框构造对象,Builder是AlertDialog的内部类。参数this指代Android的主Activity对象,该对象启动应用时自动生成 +AlertDialog.Builder dlg = new AlertDialog.Builder(this); +// 设置提示框标题 +dlg.setTitle("自定义标题"); +// 设置提示框内容 +dlg.setMessage("使用NJS的原生弹出框,可自定义弹出框的标题、按钮"); +// 设置提示框按钮 +dlg.setPositiveButton("确定(或者其他字符)", null); +// 显示提示框 +dlg.show(); +//... +``` +Native.js代码: +``` javascript +/** + * 在Android平台通过NJS显示系统提示框 + */ +function njsAlertForAndroid(){ + // 导入AlertDialog类 + var AlertDialog = plus.android.importClass("android.app.AlertDialog"); + // 创建提示框构造对象,构造函数需要提供程序全局环境对象,通过plus.android.runtimeMainActivity()方法获取 + var dlg = new AlertDialog.Builder(plus.android.runtimeMainActivity()); + // 设置提示框标题 + dlg.setTitle("自定义标题"); + // 设置提示框内容 + dlg.setMessage("使用NJS的原生弹出框,可自定义弹出框的标题、按钮"); + // 设置提示框按钮 + dlg.setPositiveButton("确定(或者其他字符)",null); + // 显示提示框 + dlg.show(); +} +//... +``` +`注意:NJS代码中创建提示框构造对象要求传入程序全局环境对象,可通过plus.android.runtimeMainActivity()方法获取应用的主Activity对象,它是HTML5+应用运行期自动创建的程序全局环境对象。` + +Android设备上运行效果图: +![Android Native.js示例运行效果图](http://www.dcloud.io/docs/a/njs/android_alert.png) +`注意:其实HTML5+规范已经封装过原生提示框消息API:plus.ui.alert( message, alertCB, title, buttonCapture)。此处NJS的示例仅为了开发者方便理解,实际使用时调用plus.ui.alert更简单,性能也更高。** + +#### iOS +以下代码在iOS平台展示调用Native API显示系统提示对话框。 +iOS原生Objective-C代码,用于比对参考: +``` object-c +#import +//... +// 创建UIAlertView类的实例对象 +UIAlertView *view = [UIAlertView alloc]; +// 设置提示对话上的内容 +[view initWithTitle:@"自定义标题" // 提示框标题 + message:@"使用NJS的原生弹出框,可自定义弹出框的标题、按钮" // 提示框上显示的内容 + delegate:nil // 点击提示框后的通知代理对象,nil类似js的null,意为不设置 + cancelButtonTitle:@"确定(或者其他字符)" // 提示框上取消按钮的文字 + otherButtonTitles:nil]; // 提示框上其它按钮的文字,设置为nil表示不显示 +// 调用show方法显示提示对话框,在OC中使用[]语法调用对象的方法 +[view show]; +//... +``` +Native.js代码: +``` javascript +/** + * 在iOS平台通过NJS显示系统提示框 + */ +function njsAlertForiOS(){ + // 导入UIAlertView类 + var UIAlertView = plus.ios.importClass("UIAlertView"); + // 创建UIAlertView类的实例对象 + var view = new UIAlertView(); + // 设置提示对话上的内容 + view.initWithTitlemessagedelegatecancelButtonTitleotherButtonTitles("自定义标题" // 提示框标题 + , "使用NJS的原生弹出框,可自定义弹出框的标题、按钮" // 提示框上显示的内容 + , null // 操作提示框后的通知代理对象,暂不设置 + , "确定(或者其他字符)" // 提示框上取消按钮的文字 + , null ); // 提示框上其它按钮的文字,设置为null表示不显示 + // 调用show方法显示提示对话框,在JS中使用()语法调用对象的方法 + view.show(); +} +//... +``` +`注意:在OC语法中方法的定义格式为: +“(返回值类型) 函数名: (参数1类型) 形参1 参数2名称: (参数2类型) 形参2” +方法的完整名称为: “函数名:参数2名称:”。 +如:“(void)setPositionX:(int)x Y:(int)y;”,方法的完整名称为“setPositionX:Y:” +调用时语法为:“[pos setPositionX:x Y:y];”。 +在JS语法中函数名称不能包含“:”字符,所以OC对象的方法名映射成NJS对象方法名时将其中的“:”字符自动删除,上面方法名映射为“setPositionXY”,在NJS调用的语法为:“pos.setPositionXY(x,y);”。` +iOS设备上运行效果图: +![iOS Native.js示例运行效果图](http://www.dcloud.io/docs/a/njs/ios_alert.png) +`注意:其实HTML5+规范已经封装过原生提示框消息API:plus.ui.alert( message, alertCB, title, buttonCapture)。此处NJS的示例仅为了开发者方便理解,实际使用时调用plus.ui.alert更简单、性能也更高。 + +在HBuilder自带的Hello H5+模板应用中“Native.JS”(plus/njs.html)页面有完整的源代码,可真机运行查看效果。 + +## 常用API +### API on Android +为了能更好的理解NJS调用Java Native API,我们在Android平台用Java实现以下测试类,将在会后面API说明中的示例来调用。 +文件NjsHello.java代码如下: +``` java +package io.dcloud; + +// 定义类NjsHello +public class NjsHello { + // 静态常量 + public static final int CTYPE = 1; + // 静态变量 + public static int count; + // 成员常量 + public final String BIRTHDAY = "2013-01-13"; + // 成员变量 + String name; //定义属性name + NjsHelloEvent observer; + public void updateName( String newname ) { //定义方法updateName + name = newname; + } + public void setEventObserver( NjsHelloEvent newobserver ) { + observer = newobserver; + } + public void test() { //定义方法test + System.out.printf( "My name is: %s", name ); + observer.onEventInvoked( name ); + } + public static void testCount() { + System.out.printf( "Static count is:%d", count ); + } + static{ // 初始化类的静态变量 + NjsHello.count = 0; + } +} +``` +文件NjsHelloEvent.java代码如下: +``` java +package io.dcloud; + +// 定义接口NjsHelloEvent +public interface NjsHelloEvent { + public void onEventInvoked( String name ); +} +``` +`注:此NjsHello示例仅为了说明原生代码与NJS代码之间的映射关系,以下示例代码无法直接在HBuilder里真机运行,必须在以后HBuilder开放自定义打包后方可把NjsHello类打入app并被NJS调用。实际使用中,这种需要并非必要,大多数情况可以通过直接写NJS代码调用操作系统API,而无需由原生语言二次封装类供JS调用。` + +#### plus.android.importClass +导入Java类对象,方法原型如下: +``` js +ClassObject plus.android.importClass( String classname ); +``` +导入类对象后,就可以通过“.”操作符直接调用对象(类对象/实例对象)的常量和方法。 +classname:要导入的Java类名,必须是完整的命名空间(使用"."分割),如果指定的类名不存在,则导入类失败,返回null。 + +`注意:导入类对象可以方便的使用“.”操作符来调用对象的属性和方法,但也会消耗较多的系统资源。因此导入过多的类对象会影响性能,此时可以使用“高级API”中提供的方法在不导入类对象的情况下调用Native API。` + +**示例:** +1. 导入类对象 +Java代码: +``` java +import io.dcloud.NjsHello; +//... +public class Test { +public static void main( String args[] ) { + // 创建对象的实例 + NjsHello hello = new NjsHello(); + //... +} +//... +} +``` +NJS代码: +``` javascript +// 导入测试类NjsHello +var NjsHello = plus.android.importClass("io.dcloud.NjsHello"); +// 创建NjsHello的实例对象 +var hello = new NjsHello(); +// ... +``` + +#### ClassObject +调用plus.android.importClass()方法导入类并返回ClassObject类对象,通过该类对象,可以创建类的实例对象。在Java中类的静态方法会转换成NJS类对象的方法,可通过类对象的“.”操作符调用;类的静态常量会转换为NJS类对象的属性,可通过类对象的“.”操作符访问;类的静态属性则需通过NJS类对象的plusGetAttribute、plusSetAttribute方法操作。 +**示例:** +1. 导入类后获取类的静态常量属性 +Java代码: +``` java +import io.dcloud.NjsHello; +//... +public class Test { +public static void main( String args[] ) { + // 获取类的静态常量属性 + int type = NjsHello.CTYPE; + System.out.printf( "NjsHello Final's value: %d", type ); // 输出“NjsHello Final's value: 1” + //... +} +//... +} +``` +NJS代码: +``` javascript +// 导入测试类NjsHello +var NjsHello = plus.android.importClass("io.dcloud.NjsHello"); +// 获取类的静态常量属性 +var type = NjsHello.CTYPE; +console.log( "NjsHello Final's value: "+type ); // 输出“NjsHello Final's value: 1” +// ... +``` + +2. 导入类后调用类的静态方法 +Java代码: +``` java +import io.dcloud.NjsHello; +//... +public class Test { +public static void main( String args[] ) { + // 调用类的静态方法 + NjsHello.testCount(); + //... +} +//... +} +``` +NJS代码: +``` javascript +// 导入测试类NjsHello +var NjsHello = plus.android.importClass("io.dcloud.NjsHello"); +// 调用类的静态方法 +NjsHello.testCount(); +// ... +``` + +##### ClassObject.plusGetAttribute +获取类对象的静态属性值,方法原型如下: +``` javascript +Object classobject.plusGetAttribute( String name ); +``` + +导入类对象后,就可以调用其plusGetAttribute方法获取类的静态属性值。 +- name:要获取的静态属性名称,如果指定的属性名称不存在,则获取属性失败,返回null。 + +`注意:如果导入的类对象中存在“plusGetAttribute”同名的静态方法,则必须通过plus.android.invoke()方法调用。` + +示例: +1. 导入类后获取类的静态属性值 +Java代码: +``` java +import io.dcloud.NjsHello; +//... +public class Test { +public static void main( String args[] ) { + // 获取类的静态属性 + int count = NjsHello.count; + System.out.printf( "NjsHello Static's value: %d", count ); // 输出“NjsHello Static's value: 0” + //... +} +//... +} +``` +NJS代码: +``` javascript +// 导入测试类NjsHello +var NjsHello = plus.android.importClass("io.dcloud.NjsHello"); +// 获取类的静态属性 +var count = NjsHello.plusGetAttribute( "count" ); +console.log( "NjsHello Static's value: "+count ); // 输出“NjsHello Static's value: 0” +// ... +``` + +##### ClassObject.plusSetAttribute +设置类对象的静态属性值,方法原型如下: +``` java +void classobject.plusSetAttribute( String name, Object value ); +``` + +导入类对象后,就可以调用其plusSetAttribute方法设置类的静态属性值。 +- name:要设置的静态属性名称,如果指定的属性名称不存在,则设置属性失败,返回null。 +- value:要设置的属性值,其类型必须与Native层类对象的静态属性区配,否则设置操作不生效,将保留以前的值。 + +`注意:如果导入的类对象中存在“plusSetAttribute”同名的静态方法,则必须通过plus.android.invoke()方法调用。` + +示例: +1. 导入类后设置类的静态属性值 +Java代码: +``` java +import io.dcloud.NjsHello; +//... +public class Test { +public static void main( String args[] ) { + // 设置类的静态属性值 + NjsHello.count = 2; + System.out.printf( "NjsHello Static's value: %d", NjsHello.count ); // 输出“NjsHello Static's value: 2” + //... +} +//... +} +``` +NJS代码: +``` javascript +// 导入测试类NjsHello +var NjsHello = plus.android.importClass("io.dcloud.NjsHello"); +// 设置类的静态属性值 +NjsHello.plusSetAttribute( "count", 2 ); +console.log( "NjsHello Static's value: "+NjsHello.plusGetAttribute( "count" ) ); // 输出“NjsHello Static's value: 2” +// ... +``` + +#### InstanceObject +NJS中实例对象与Java中的对象对应,调用plus.android.importClass()方法导入类后,通过new操作符可创建该类的实例对象,或直接调用plus.android.newObject方法创建类的实例对象,也可通过调用Native API返回实例对象。在Java中对象的方法会转换成NJS实例对象的方法,可通过实例对象的“.”操作符调用;对象的常量属性会转换NJS实例对象的属性,可通过实例对象的“.”操作符访问。对象的非常量属性则必须通过NJS实例对象的plusGetAttribute、plusSetAttribute方法操作。 +示例: +1. 导入类创建实例对象,调用对象的方法 +Java代码: +``` java +import io.dcloud.NjsHello; +//... +public class Test { +public static void main( String args[] ) { + // 创建NjsHello的实例对象 + NjsHello hello = new NjsHello(); + // 调用对象的方法 + hello.updateName( "Tester" ); + //... +} +//... +} +``` +NJS代码: +``` javascript +// 导入测试类NjsHello +var NjsHello = plus.android.importClass("io.dcloud.NjsHello"); +// 创建NjsHello的实例对象 +var hello = new NjsHello(); +// 调用对象的方法 +hello.updateName( "Tester" ); +// ... +``` + +2. 导入类创建实例对象,获取对象的常量属性 +Java代码: +``` java +import io.dcloud.NjsHello; +//... +public class Test { +public static void main( String args[] ) { + // 创建NjsHello的实例对象 + NjsHello hello = new NjsHello(); + // 访问对象的常量属性 + String birthday = hello.BIRTHDAY; + System.out.printf( "NjsHello Object Final's value: %s", birthday ); // 输出“NjsHello Object Final's value: 2013-01-13” + //... +} +//... +} +``` +NJS代码: +``` javascript +// 导入测试类NjsHello +var NjsHello = plus.android.importClass("io.dcloud.NjsHello"); +// 创建NjsHello的实例对象 +var hello = new NjsHello(); +// 访问对象的常量属性 +var birthday = hello.BIRTHDAY; +console.log( "NjsHello Object Final's value: "+birthday ); // 输出“NjsHello Object Final's value: 2013-01-13” +// ... +``` + +##### InstanceObject.plusGetAttribute +获取实例对象的属性值,方法原型如下: +``` javascript +Object instancebject.plusGetAttribute( String name ); +``` +获取实例对象后,就可以调用其plusGetAttribute方法获取对象的属性值。 +name:要获取对象的属性名称,如果指定的属性名称不存在,则获取属性失败,返回null。 + +`注意:如果实例对象中存在“plusGetAttribute”同名的方法,则必须通过plus.android.invoke()方法调用。` + +示例: +1. 导入类创建实例对象,获取对象的属性值 +Java代码: +``` java +import io.dcloud.NjsHello; +//... +public class Test { +public static void main( String args[] ) { + // 创建对象的实例 + NjsHello hello = new NjsHello(); + hello.updateName( "Tester" ); + // 获取其name属性值 + String name = hello.name; + System.out.printf( "NjsHello Object's name: %s", name ); // 输出“NjsHello Object's name: Tester” + //... +} +//... +} +``` +NJS代码: +``` javascript +// 导入测试类NjsHello +var NjsHello = plus.android.importClass("io.dcloud.NjsHello"); +// 创建对象的实例 +var hello = new NjsHello(); +hello.updateName( "Tester" ); +// 获取其name属性值 +var name = hello.plusGetAttribute( "name" ); +console.log( "NjsHello Object's name: "+name ); // 输出“NjsHello Object's name: Tester” +// ... +``` + +##### InstanceObject.plusSetAttribute +设置类对象的静态属性值,方法原型如下: +``` javascript +void instanceobject.plusSetAttribute( String name, Object value ); +``` +导入类对象后,就可以调用其plusSetAttribute方法设置类的静态属性值。 +- name:要设置的静态属性名称,如果指定的属性名称不存在,则设置属性失败,返回null。 +- value:要设置的属性值,其类型必须与Native层类对象的静态属性区配,否则设置操作不生效,将保留以前的值。 + +`注意:如果导入的类对象中存在“plusSetAttribute”同名的静态方法,则必须通过plus.android.invoke()方法调用。` + +示例: +1. 导入类创建实例对象,设置对象的属性值 +Java代码: +``` java +import io.dcloud.NjsHello; +//... +public class Test { +public static void main( String args[] ) { + // 创建对象的实例 + NjsHello hello = new NjsHello(); + // 设置其name属性值 + hello.name = "Tester"; + System.out.printf( "NjsHello Object's name: %s", hello.name ); // 输出“NjsHello Object's name: Tester” + //... +} +//... +} +``` +NJS代码: +``` javascript +// 导入测试类NjsHello +var Hello = plus.android.importClass("NjsHello"); +// 创建对象的实例 +var hello = new NjsHello(); +// 设置其name属性值 +hello.plusSetAttribute( "name", "Tester" ); +console.log( "NjsHello Object's name: "+hello.plusGetAttribute("name") ); // 输出“NjsHello Object's name: Tester” +// ... +``` + +#### plus.android.implements +在Java中可以通过定义新类并实现Interface的接口,并创建出新类对象作为其它接口的参数,在NJS中则可快速创建对应的Interface对象,方法原型如下: +Object plus.android.implements( String name, Object obj ); + +此方法创建Native层中Java的接口实现对象,作为调用其它Native API的参数。 +- name:接口的名称,必须是完整的命名空间(使用"."分割),如果不存在此接口,则创建接口实现对象失败,返回null。 +- obj:JSON对象类型,接口实现方法的定义,JSON对象中key值为接口方法的名称;value值为Function,方法参数必须与接口中方法定义的参数区配。 + +示例: +1. Test类中实现接口NjsHelloEvent的方法,并调用NjsHello对象的test方法触发接口中函数的运行。 +Java代码: +``` java +import io.dcloud.NjsHello; +import io.dcloud.NjsHelloEvent; +//... +// Test类实现NjsHelloEvent接口 +public class Test implements NjsHelloEvent { +public static void main( String args[] ) { + // 创建对象的实例 + NjsHello hello = new NjsHello(); + // 调用updateName方法 + hello.updateName( "Tester" ); + // 设置监听对象 + hello.setEventObserver( this ); + // 调用test方法,触发接口事件 + hello.test(); // 触发onEventInvoked函数运行 + //... +} +// 实现接口NjsHelloEvent的onEventInvoked方法 +@Override +public void onEventInvoked( String name ) { + System.out.printf( "Invoked Object's name is: %s", name ); +} +//... +} +``` +NJS代码: +``` javascript +// 导入测试类NjsHello +var NjsHello = plus.android.importClass("io.dcloud.NjsHello"); +// 实现接口“NjsHelloEvent”对象 +var hevent = plus.android.implements( "io.dcloud.NjsHelloEvent", { + "onEventInvoked":function( name ){ + console.log( "Invoked Object’s name: "+name ); // 输出“Invoked Object’s name: Tester” + } +} ); +// 创建对象的实例 +var hello = new NjsHello(); +// 调用updateName方法 +hello.updateName( "Tester" ); +// 设置监听对象 +hello.setEventObserver( hevent ); +// 调用test方法,触发代理事件 +hello.test(); // 触发上面定义的匿名函数运行 +// ... +``` + +#### plus.android.runtimeMainActivity +获取运行期环境主Activity实例对象,方法原型如下: +``` javascript +InstanceObject plus.android.runtimeMainActivity(); +``` +此方法将获取程序的主Activity的实例对象,它是Html5+运行期环境主组件,用于处理与用户交互的各种事件,也是应用程序全局环境android.app.Activity的实现对象。android.app.Activity是一个特殊的类,需要在原生开发环境中注册后才能使用,所以使用new操作符创建对象无实际意义。 + +示例: +1. 调用Activity的startActivity方法来拨打电话 +Java代码: +``` java +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +//... +// 获取主Activity对象的实例 +Activity main = context; +// 创建Intent +Uri uri = Uri.parse("tel:10086"); +Intent call = new Intent("android.intent.action.CALL",uri); +// 调用startActivity方法拨打电话 +main.startActivity(call); +//... +``` +NJS代码: +``` javascript +// 导入Activity、Intent类 +var Intent = plus.android.importClass("android.content.Intent"); +var Uri = plus.android.importClass("android.net.Uri"); +// 获取主Activity对象的实例 +var main = plus.android.runtimeMainActivity(); +// 创建Intent +var uri = Uri.parse("tel:10086"); +var call = new Intent("android.intent.action.CALL",uri); +// 调用startActivity方法拨打电话 +main.startActivity( call ); +// ... +``` + +#### plus.android.currentWebview +获取当前Webview窗口对象的native层实例对象,方法原型如下: +``` javascript +InstanceObject plus.android.currentWebview(); +``` +Android平台完整Java类名为android.webkit.Webview,完整API请参考Android开发文档[android.webkit.Webview](http://developer.android.com/reference/android/webkit/WebView.html)。 + +示例: +1. 调用Webview的loadUrl方法跳转页面 +Java代码: +``` java +import android.webkit.Webview; +//... +// 获取Webview对象 +Webview wv = this; +// 跳转页面 +wv.loadUrl("http://www.dcloud.io/"); +//... +``` +NJS代码: +``` javascript +// 导入Webview类 +var Webview = plus.android.importClass("android.webkit.Webview"); +// 当前Webview对象的实例 +var wv = plus.android.currentWebview(); +// 跳转页面 +wv.loadUrl("http://www.dcloud.io/"); +// ... +``` +**完整API文档参考:[HTML5+ API - Native.js for Android](http://www.html5plus.org/doc/zh_cn/android.html)** + +### API on iOS +为了能更好的理解NJS调用Objective-C Native API,我们在iOS平台用Objective-C实现以下测试类,将会在后面API说明中的示例来调用。 +头文件njshello.h代码如下: +``` objective-c +// 定义协议 +@protocol NjsHelloEvent +@required +-(void) onEventInvoked:(NSString*)name; +@end +// ------------------------------------------------------------- +// 定义类NjsHello +@interface NjsHello : NSObject { + NSString *_name; + id _delegate; +} +@property (nonatomic,retain) NSString *name; +@property (nonatomic,retain) id delegate; +-(void)updateName:(NSString*)newname; +-(void)setEventObserver:(id)delegate; +-(void)test; ++(void)testCount; +@end +``` + +实现文件njshello.m源代码如下: +``` objective-c +#import "njshello.h" +// 实现类NjsHello +@implementation NjsHello +@synthesize name=_name; +-(void)updateName:(NSString*)newname{ + _name = [newname copy]; +} +-(void)setEventObserver:(id)delegate{ + _delegate = delegate; +} +-(void)test{ + NSLog(@"My name is: %@",_name); + [[self delegate]onEventInvoked:name]; +} +-(void)dealloc{ + [_name release]; + [supper dealloc]; +} ++(void)testCount{ + NSLog( @"Static test count" ); +} +@end +``` + +#### plus.ios.importClass +导入Objective-C类对象,方法原型如下: +``` javascript +ClassObject plus.ios.importClass( String classname ); +``` +导入类对象后,就可以通过“.”操作符直接调用对象(类对象/实例对象)的常量和方法。通过“.”操作符号调用方法时,不需要使用“:”来分割方法名。 +- classname:要导入的Objective-C类名,如果指定的类名不存在,则导入类失败,返回null。 + +`注意:导入类对象可以方便的使用“.”操作符来调用对象的属性和方法,但也会消耗较多的系统资源。因此导入过多的类对象会影响性能,此时可以使用“高级API”中提供的方法在不导入类对象的情况下调用Native API。` +示例: +1. 导入类并创建实例对象 +Objective-C代码: +``` objective-c +#import "njshello.h" +int main( int argc, char *argv[] ) +{ + // 创建对象的实例 + NjsHello* hello = [[NjsHello alloc] init]; + // ... +} +// ... +``` +NJS代码: +``` javascript +// 导入测试类NjsHello +var NjsHello = plus.ios.importClass("NjsHello"); +// 创建对象的实例 +var hello = new NjsHello(); +// ... +``` + +#### ClassObject +调用plus.ios.importClass()方法导入类并返回ClassObject类对象,通过该类对象,可以创建类的实例对象。在Objective-C中类的静态方法会转换成NJS类对象的方法,可通过类对象的“.”操作符调用; + +`注意:由于Objective-C中类没有静态变量,而是通过定义全局变量来实现,目前NJS中无法访问全局变量的值。对于全局常量,在NJS中也无法访问,对于原类型常量可在文档中找到其具体的值,在JS代码中直接赋值;对于非原类型常量目前还无法访问。` + +示例: +1. 导入类后调用类的静态方法 +Objective-C代码: +``` objective-c +#import "njshello.h" +// ... +int main( int argc, char *argv[] ) +{ + // 调用类的静态方法 + [NjsHello testCount]; + // ... +} +// ... +``` +NJS代码: +``` javascript +// 导入测试类NjsHello +var NjsHello = plus.ios.importClass("NjsHello"); +// 调用类的静态方法 +NjsHello.testCount(); +// ... +``` + +#### InstanceObject +NJS中实例对象与Objective-C中的对象对应,调用plus.ios.importClass()方法导入类后,通过new操作符可创建该类的实例对象,或直接调用plus.ios.newObject方法创建类的实例对象,也可通过调用Native API返回实例对象。在Objective-C中对象的方法会转换成NJS实例对象的方法,可通过实例对象的“.”操作符调用;对象的属性则必须通过NJS实例对象的plusGetAttribute、plusSetAttribute方法操作。 + +示例: +1. 导入类创建实例对象,调用对象的方法 +Objective-C代码: +``` objective-c +#import "njshello.h" +int main( int argc, char *argv[] ) +{ + // 创建对象的实例 + NjsHello* hello = [[NjsHello alloc] init]; + // ... +} +// ... +``` +NJS代码: +``` javascript +// 导入测试类NjsHello +var NjsHello = plus.ios.importClass("NjsHello"); +// 创建对象的实例 +var hello = new NjsHello(); +// ... +``` + +##### InstanceObject.plusGetAttribute +获取实例对象的属性值,方法原型如下: +``` javascript +Object instancebject.plusGetAttribute( String name ); +``` +获取实例对象后,就可以调用其plusGetAttribute方法获取对象的属性值。 +- name:要获取对象的属性名称,如果指定的属性名称不存在,则获取属性失败,返回null。 + +`注意:如果实例对象中存在“plusGetAttribute”同名的方法,则只能通过plus.ios.invoke()方法调用。` + +示例: +1. 导入类创建实例对象,获取对象的属性值 +Objective-C代码: +``` objective-c +#import "njshello.h" +int main( int argc, char *argv[] ) +{ + // 创建对象的实例 + NjsHello* hello = [[NjsHello alloc] init]; + [hello updateName:@"Tester"]; + // 获取其name属性值 + NSString* name = hello.name; + NSLog(@"NjsHello Object's name: %@",name); // 输出“NjsHello Object's name: Tester” + // ... +} +``` +NJS代码: +``` javascript +// 导入测试类NjsHello +var NjsHello = plus.ios.importClass("NjsHello"); +// 创建对象的实例 +var hello = new NjsHello(); +hello.updateName( "Tester" ); +// 获取其name属性值 +var name = hello.plusGetAttribute( "name" ); +console.log( "NjsHello Object’s name: "+name ); // 输出“NjsHello Object’s name: Tester” +// ... +``` + +##### InstanceObject.plusSetAttribute +设置类对象的静态属性值,方法原型如下: +``` javascript +void instanceobject.plusSetAttribute( String name, Object value ); +``` +导入类对象后,就可以调用其plusSetAttribute方法设置类的静态属性值。 +- name:要设置的静态属性名称,如果指定的属性名称不存在,则设置属性失败,返回null。 +- value:要设置的属性值,其类型必须与Native层类对象的静态属性区配,否则设置操作不生效,将保留以前的值。 + +`注意:如果导入的类对象中存在“plusSetAttribute”同名的静态方法,则只能通过plus.android.invoke()方法调用。` + +示例: +1. 导入类创建实例对象,设置对象的属性值 +Java代码: +``` java +#import "njshello.h" +int main( int argc, char *argv[] ) +{ + // 创建对象的实例 + NjsHello* hello = [[NjsHello alloc] init]; + // 设置其name属性值 + hello.name = @"Tester"; + NSLog(@"NjsHello Object's name: %@",hello.name); // 输出“NjsHello Object's name: Tester” + // ... +} +//... +``` +NJS代码: +``` javascript +// 导入测试类NjsHello +var NjsHello = plus.ios.importClass("NjsHello"); +// 创建对象的实例 +var hello = new NjsHello(); +// 设置其name属性值 +hello.plusSetAttribute( "name", "Tester" ); +console.log( "NjsHello Object’s name: "+hello.plusGetAttribute("name") ); // 输出“NjsHello Object’s name: Tester” +// ... +``` + +#### plus.ios.implements +在Objective-C中可以通过定义新类并实现Protocol的协议,并创建出新类对象作为代理对象,在NJS中则可实现协议快速创建代理对象,方法原型如下: +``` javascript +Object plus.ios.implements( String name, Object obj ); +``` +此方法返回一个NJS实例对象,映射到Native层中的代理对象,其父类为“NSObject”,并且实现obj中指定的协议方法。通常作为调用其它Native API的参数。 +- name:协议的名称,也可以是自定的字符串名称用于定义一个代理。 +- obj:JSON对象类型,代理实现方法的定义,JSON对象中key值为协议中定义的方法名称,必须保留方法名称中的“:”字符;value值为Function,方法参数必须与协议中定义方法的参数区配。 + +示例: +1. 实现一个代理,并调用test方法触发调用代理的方法 +Objective-C代码: +``` objective-c +#import "njshello.h" +// 定义代理类NjsDelegate +@interface NjsDelegate: NSObject { + -(void) onEventInvoked:(NSString*)name; +} +@end +// ------------------------------------------------------------- +// 实现代理类NjsDelegate +@implementation NjsDelegate +-(void) onEventInvoked:(NSString*)name{ + NSLog(@"Invoked Object's name:%@",name); // 输出“Invoked Object’s name: Tester” +} +@end +// ------------------------------------------------------------- +// 主函数 +int main( int argc, char *argv[] ) +{ + // 创建对象的实例 + NjsHello* hello = [[NjsHello alloc] init]; + // 调用updateName方法 + [hello updateName:@"Tester"]; + // 创建代理对象 + NjsDelegate* delegate = [[NjsDelegate alloc] init]; + // 设置监听对象 + [hello setEventObserver:delegate]; + // 调用test方法,触发代理事件 + [hello test]; // 触发上面代理对象定义的onEventInvoked运行 + // ... +} +``` +在NJS中不需要创建新的类对象,调用plus.ios.implements实现协议接口即可创建出代理对象,代码如下: +``` javascript +// 导入测试类NjsHello +var NjsHello = plus.ios.importClass("NjsHello"); +// 实现协议“NjsHelloEvent”的代理 +var hevent = plus.ios.implements( "NjsHelloEvent", { + "onEventInvoked:":function( name ){ + console.log( "Invoked Object’s name: "+name ); // 输出“Invoked Object’s name: Tester” + } +} ); +// 调用updateName方法 +hello.updateName( "Tester" ); +// 设置监听对象 +hello.setEventObserver( hevent ); +// 调用test方法,触发代理事件 +hello.test(); // 触发上面代理对象定义的匿名函数运行 +// ... +``` + +#### plus.ios.deleteObject +释放NJS中实例对象中映射的Native对象,方法原型如下: +``` javascript +void plus.ios.deleteObject( Object obj ); +``` +NJS中所有的实例对象(InstanceObject)都可以通过此方法释放,会将Native层的对象使用的资源进行释放。 +- obj:要释放的实例对象,如果obj对象不是有效的实例对象,则不执行对象的是否资源操作。 + +`注意:此方法是可选的,如果不调用此方法释放实例对象,则在页面关闭时会自动释放所有对象;若对象占用较多的系统资源,则在业务逻辑处理完成时应该主动调用此方法释放资源,以提到程序的运行效率。` + +示例: +1. 创建实例对象使用完成后,显式操作销毁对象 +Objective-C代码: +``` objective-c +#import "njshello.h" +int main( int argc, char *argv[] ) +{ + // 创建对象的实例 + NjsHello* hello = [[NjsHello alloc] init]; + // 调用updateName方法 + [hello updateName:@"Tester"]; + // ... + // 使用完后销毁对象的实例 + [hello release]; +} +``` +NJS代码: +``` javascript +// 导入测试类NjsHello +var NjsHello = plus.ios.importClass("NjsHello"); +// 创建对象的实例 +var hello = new NjsHello(); +// 调用updateName方法 +hello.updateName( "Tester" ); +// ... +// 使用完后销毁对象的实例 +plus.ios.deleteObject( hello ); +``` + +#### plus.ios.currentWebview +获取当前Webview窗口对象的native层UIWebview实例对象,方法原型如下: +``` javascript +InstanceObject plus.ios.currentWebview(); +``` +UIWebview对象的API请参考Apple开发文档[UIWebview](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIWebView_Class/) + +示例: +1. 创建实例对象使用完成后,显式操作销毁对象 +Objective-C代码: +``` objective-c +// 获取当前Webview对象的实例 +UIWebview* wv=self; +// 创建请求对象 +NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.dcloud.io/"]]; +// 跳转页面 +[web loadRequest:req]; +// 释放对象 +// 系统自动回收 +// ... +``` +NJS代码: +``` javascript +// 导入UIWebview、NSURLRequest、NSURL类 +var Webview = plus.ios.importClass("UIWebview"); +var NSURLRequest = plus.ios.import('NSURLRequest'); +var NSURL = plus.ios.import('NSURL'); +// 获取当前Webview对象的实例 +var wv = plus.ios.currentWebview(); +// 创建请求对象 +var req = NSURLRequest.requestWithURL(NSURL.URLWithString('http://www.dcloud.io/')); +// 跳转页面 +plus.ios.invoke(wv,"loadRequest:",req); +// 释放对象(可选) +plus.ios.deleteObject(req); +plus.ios.deleteObject(wv); +// ... +``` +**完整API文档参考:[HTML5+ API - Native.js for iOS](http://www.html5plus.org/doc/zh_cn/ios.html)** + +## 完整业务演示 +### Android +在Android手机桌面上创建快捷方式图标,这是原本只有原生程序才能实现的功能。即使使用Hybrid方案,也需要原生工程师来配合写插件。 +下面我们演示如何直接使用js在Android手机桌面创建快捷方式,在HelloH5+应用中Native.JS页面中“Shortcut (Android)”可以查看运行效果。 +这段代码是使用原生Java实现的创建快捷方式的代码,用于参考比对: +``` java +import android.app.Activity; +import android.content.Intent; +import android.graphics.BitmapFactory; +import android.graphics.Bitmap; +// 创建桌面快捷方式 +void createShortcut(){ + // 获取主Activity + Activity main = this; + // 创建快捷方式意图 + Intent shortcut = new Intent("com.android.launcher.action.INSTALL_SHORTCUT"); + // 设置快捷方式的名称 + shortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME, "HelloH5+"); + // 设置不可重复创建 + shortcut.putExtra("duplicate",false); + // 设置快捷方式图标 + Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/icon.png"); + shortcut.putExtra(Intent.EXTRA_SHORTCUT_ICON, bitmap); + // 设置快捷方式启动执行动作 + Intent action = new Intent(Intent.ACTION_MAIN); + action.setComponent( main.getComponentName() ); + shortcut.putExtra( Intent.EXTRA_SHORTCUT_INTENT, action ); + // 广播创建快捷方式 + main.sendBroadcast(shortcut); +} +``` + +使用NJS实现时首先导入需要使用到的android.content.Intent、android.graphics.BitmapFactory类,按照Java代码中的方法对应转换成JavaScript代码。 +其中快捷方式图标是通过解析本地png文件进行设置,在JavaScript中需要使用plus.io.* API转换成本地路径传递给Native API,完整代码如下: +``` javascript +var Intent=null,BitmapFactory=null; +var main=null; +document.addEventListener( "plusready", function() {//"plusready"事件触发时执行plus对象的方法 + // ... + if ( plus.os.name == "Android" ) { + // 导入要用到的类对象 + Intent = plus.android.importClass("android.content.Intent"); + BitmapFactory = plus.android.importClass("android.graphics.BitmapFactory"); + // 获取主Activity + main = plus.android.runtimeMainActivity(); + } +}, false); +/** + * 创建桌面快捷方式 + */ +function createShortcut(){ + // 创建快捷方式意图 + var shortcut = new Intent("com.android.launcher.action.INSTALL_SHORTCUT"); + // 设置快捷方式的名称 + shortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME, "测试快捷方式"); + // 设置不可重复创建 + shortcut.putExtra("duplicate",false); + // 设置快捷方式图标 + var iconPath = plus.io.convertLocalFileSystemURL("/icon.png"); // 将相对路径资源转换成系统绝对路径 + var bitmap = BitmapFactory.decodeFile(iconPath); + shortcut.putExtra(Intent.EXTRA_SHORTCUT_ICON,bitmap); + // 设置快捷方式启动执行动作 + var action = new Intent(Intent.ACTION_MAIN); + action.setClassName(main.getPackageName(), 'io.dcloud.PandoraEntry'); + shortcut.putExtra(Intent.EXTRA_SHORTCUT_INTENT,action); + // 广播创建快捷方式 + main.sendBroadcast(shortcut); + console.log( "桌面快捷方式已创建完成!" ); +} +``` + +注意:提交到云平台打包时需要添加Android权限才能在桌面创建快捷方式,在HBuilder工程中双击应用的“manifest.json”文件,切换到“代码视图”中在plus->distribute->google->permissions节点下添加权限数据: +``` xml +"google": { + // ... + "permissions": [ +"" + ] +} +``` +如下图所示: +![manifest.json中Android权限 permissions](http://www.dcloud.io/docs/a/njs/android_permissions.png) + +###iOS +在iOS手机上登录game center,一个游戏中心服务,这是原本只有原生程序才能实现的功能。即使使用Hybrid方案,也需要原生工程师来配合写插件。 +下面我们演示如何直接使用js在iOS手机上登录game center,在HelloH5+应用中Native.JS页面中的“Game Center (iOS)”可以查看运行效果。 +注意手机未开通game center则无法登陆,请先点击iOS自带的game center进行配置。 +这段代码是使用原生Objective-C实现的登录game center的代码,用于参考比对。原生Objective-C代码的头文件Test.h中代码如下: +``` object-c +@interface Test: NSObject +// 游戏玩家登录状态监听函数 +- (void)authenticationChanged:(NSNotification*)notification; +// 获取游戏玩家状态信息 +- (void)playerInformation:(GKPlayer *)player; +// 登录到游戏中心 +- (void)loginGamecenter; +// 停止监听登录游戏状态变化 +- (void)logoutGamecenter; +@end + +实现文件Test.m中代码如下: +@implementation Test +// 游戏玩家登录状态监听函数 +- (void)authenticationChanged:(NSNotification*)notification +{ + // 获取游戏玩家共享实例对象 + GKLocalPlayer *player = notification.object; + if ( player.isAuthenticated ) { + // 玩家已登录认证,获取玩家信息 + [self playerInformation:player]; + } else { + // 玩家未登录认证,提示用户登录 + NSLog(@"请登录!"); + } + // 释放使用的对象 + [player release]; +} +// 获取游戏玩家状态信息 +- (void)playerInformation:(GKPlayer *)player +{ + // 获取游戏玩家的名称 + NSLog(@"Name: %@",player.displayName); +} + +// 登录到游戏中心 +- (void)loginGamecenter +{ + // 监听用户登录状态变更事件 + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + [nc addObserver:self + selector:@selector(authenticationChanged) + name:@"GKPlayerAuthenticationDidChangeNotificationName" + object:nil]; + // 获取游戏玩家共享实例对象 + GKLocalPlayer *localplayer = [GKLocalPlayer localPlayer]; + // 判断游戏玩家是否已经登录认证 + if ( localplayer.isAuthenticated ) { + // 玩家已登录认证,获取玩家信息 + [self playerInformation:localplayer]; + } else { + // 玩家未登录认证,发起认证请求 + [localplayer authenticateWithCompletionHandler:nil]; + NSLog(@"登录中..."); + } + // 释放使用的对象 + [localplayer release]; + [nc release]; +} + +// 停止监听登录游戏状态变化 +- (void)logoutGamecenter +{ + // 取消监听用户登录状态变化 + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + [nc removeObserver:self + name:@"GKPlayerAuthenticationDidChangeNotificationName" + object:nil]; + // 释放使用的对象 + [nc release]; +} +@end +``` +使用NJS实现时可以按照Objective-C代码中的方法对应转换成JavaScript代码,最关键的代码是loginGamecenter方法中对用户登录状态的监听,需调用NSNotificationCenter对象的“addObserver:selector:name:object”方法, +1. addObserver:后要求传入一个实例对象用于查找selector参数中指定的方法,在Objective-C中通常将对象自身(self)传入,但在NJS中没有此概念,因此需使用plus.ios.implements方法来创建一个新的对象: +var delegate = plus.ios.implements("NSObject",{"authenticationChanged:":authenticationChanged}); +第一个参数“NSObject”表示对象的类型,第二个参数中的JSON对象表明对象拥有的方法,“authenticationChanged”方法是delegate对象的方法。 +2. selector:后要传入一个类函数指针,在Objective-C中通过“@selector”指令可选择函数指针,在NJS中则需使用plus.ios.newObject方法来创建一个函数对象: +plus.ios.newObject("@selector","authenticationChanged:") +第一个参数需固定值为“@selector”,表示创建的是类函数指针对象,第二个参数。 +在"plusready"事件中导入GKLocalPlayer和NSNotificationCenter类,并调用登录方法longinGamecenter()。 + +完整JavaScript代码如下: +``` js +// 处理"plusready"事件 +var bLogin=false; +document.addEventListener( "plusready", function() { + // ... + if ( plus.os.name == "iOS" ) { + GKLocalPlayer = plus.ios.importClass("GKLocalPlayer"); + NSNotificationCenter = plus.ios.importClass("NSNotificationCenter"); + longinGamecenter(); + } else { + alert("欢迎您"); + bLogin = true; + setTimeout( function(){ + plus.ui.toast( "此平台不支持Game Center功能!" ); + }, 500 ); + } +}, false); + +var GKLocalPlayer=null,NSNotificationCenter=null; +var delegate=null; + +// 游戏玩家登录状态监听函数 +function authenticationChanged( notification ){ + // 获取游戏玩家共享实例对象 + var player = notification.plusGetAttribute("object"); + if ( player.plusGetAttribute("isAuthenticated") ) { + // 玩家已登录认证,获取玩家信息 + playerInformation(player); + bLogin = true; + } else { + // 玩家未登录认证,提示用户登录 + alert("请登录"); + bLogin = false; + } + // 释放使用的对象 + plus.ios.deleteObject(player); +} + +// 获取游戏玩家状态信息 +function playerInformation( player ){ + var name = player.plusGetAttribute("displayName"); + alert( name+" 已登录!" ); +} + +// 登录到游戏中心 +function longinGamecenter(){ + if ( bLogin ){ + return; + } + // 监听用户登录状态变更事件 + var nc = NSNotificationCenter.defaultCenter(); + delegate = plus.ios.implements("NSObject",{"authenticationChanged:":authenticationChanged}); + nc.addObserverselectornameobject(delegate, + plus.ios.newObject("@selector","authenticationChanged:"), + "GKPlayerAuthenticationDidChangeNotificationName", + null); + // 获取游戏玩家共享实例对象 + var localplayer = GKLocalPlayer.localPlayer(); + // 判断游戏玩家是否已经登录认证 + if ( localplayer.isAuthenticated() ) { // localplayer.plusGetAttribute("isAuthenticated") + // 玩家已登录认证,获取玩家信息 + playerInformation( localplayer ); + bLogin = true; + } else { + // 玩家未登录认证,发起认证请求 + localplayer.authenticateWithCompletionHandler(null); + alert( "登录中..." ); + } + // 释放使用的对象 + plus.ios.deleteObject(localplayer); + plus.ios.deleteObject(nc); +} + +// 停止监听登录游戏状态变化 +function stopGamecenterObserver() +{ + // 取消监听用户登录状态变化 + var nc = NSNotificationCenter.defaultCenter(); + nc.removeObservernameobject(delegate,"GKPlayerAuthenticationDidChangeNotificationName",null); + plus.ios.deleteObject(nc); + plus.ios.deleteObject(delegate); + delegate = null; +} +``` +**注意** +1. 提交到云平台打包时需要添加Game Center API的系统库(framework)才能正确调用,在HBuilder工程中双击应用的“manifest.json”文件,切换到“代码视图”中在plus->distribute->apple->frameworks节点下添加要引用的系统Framework: +``` xml +"apple": { + "devices": "universal", + "frameworks": [ + "GameKit.framework" + ] +} +``` +,如下图所示: +![HBuilder frameworks](http://www.dcloud.io/docs/a/njs/ios_frameworks.png) +2. 正式发布提交到AppStore时,在配置苹果开发者网站上配置App ID需要选中“Game Center”服务: +![Game Center](http://www.dcloud.io/docs/a/njs/ios_gamecenter.png) + +##开发注意和建议用途 +Native.js的运行性能仍然不比纯原生应用;JS与Native之间的数据交换效率并不如在js内部的数据交换效率;基于如上原因,有几点开发建议: +- 以标准web 代码为主,当遇到web能力不足的时候,调用Native.js。 +- 以标准web 代码为主,当遇到web性能不足的时候,需要分析, +if ((原生进行运算的效率-js与原生通讯的损耗)>纯web的效率){ +使用Native.js +}else{ +还应该使用纯js + } +- 应避免把程序设计为在短时间内并发触发Native.js代码 + +##调试 +使用safari和chrome的控制台调试HBuilder的5+App时,一样可以调试NJS对象,即可以在浏览器控制台中查看各种原生对象的属性和方法,如下图所示,57行设了断点,watch了Intent对象,并在右边展开了该对象的所有属性方法: +![Chrome Debug](http://www.dcloud.io/docs/a/njs/chrome_debug.png) +关于如何在浏览器控制台调试HBuilder的5+App,请参考HBuilder的5+App开发入门教程。 + +##开发资源 +iOS 官方在线文档:[https://developer.apple.com/library/ios/navigation/](https://developer.apple.com/library/ios/navigation/) +Android 官方在线文档:[https://developer.android.com/reference/packages.html](https://developer.android.com/reference/packages.html) +演讲视频:[http://v.youku.com/v_show/id_XNzYzNTcwNDI4.html](http://v.youku.com/v_show/id_XNzYzNTcwNDI4.html) + +##高级API +有前述的常用API,已经可以完成各项业务开发。此处补充的高级API,是在熟悉NJS后,为了提升性能而使用的API。高级API无法直接用“.”操作符使用原生对象的方法,在debug时也无法watch原生对象,但高级API性能高于常规API。 +虽然导入类对象(plus.android.importClass和plus.ios.importClass)后,可以方便的通过“.”操作符来访问对象的常量、调用对象的方法,但导入类对象也需要消耗较多的系统资源,所以在实际开发时应该尽可能的减少导入类对象,以提高程序效率。可以参考以下依据进行判断: +1. 如导入的类特别复杂,继承自很多基类,方法和属性特别多则考虑不导入类; +2. 对导入类是否需要频繁操作,若导入类仅是为了实例化,并作为调用其它API的参数,则不应该导入类; +3. 在同一页面中是否导入了很多类?如果导入太多则需要考虑减少导入类的数目。 + +如果我们不导入类对象则无法通过new操作符实例化类对象,这时可通过plus.ios.newObject()、plus.android.newObject()方法来创建实例对象,如下: +``` javascript +// iOS平台创建NSDictionary的实例对象 +var ns = plus.ios.newObject( "NSDictionary" ); + +// Android平台创建Intent的实例对象 +var intent = plus.android.newObject( "android.content.Intent" ); +``` + +###API on Android +####plus.android.newObject +不导入类对象直接创建类的实例对象,方法原型如下: +``` javascript +InstanceObject plus.android.newObject( String classname, Object...args ); +``` +此方法对Native层中对类进行实例化操作,创建一个类的实体并返回NJS层的实例对象。相比导入类对象后使用new操作符创建对象效率要高。 +- classname:要创建实例对象的类名,类名必须是完整的命名空间,使用“.”分隔符(如“android.app.AlertDialog”),如果需要创建内部类对象需要使用“$”分割符(如“android.app.AlertDialog$Builder”)。如果指定的类名不存在,则创建对象失败,返回null。 +- args:调用类构造函数的参数,其类型和数目必须与Native层Java类构造函数区配,否则无法创建类对象,将返回null。 + +**注意**:由于没有导入类对象,所以通过此方法创建的实例对象无法通过“.”操作符直接调用对象的方法,而必须使用plus.android.invoke方法来调用。 + +示例: +1. 不导入类创建实例对象 +Java代码: +``` java +import io.dcloud.NjsHello; +//... +public class Test { +public static void main( String args[] ) { + // 创建对象的实例 + NjsHello hello = new NjsHello(); + //... +} +//... +} +``` +NJS代码: +``` javascript +// 不调用plus.android.importClass("io.dcloud.NjsHello")导入类NjsHello +// 创建对象的实例 +var hello = plus.android.newObject( "io.dcloud.NjsHello" ); +// ... +``` + +####plus.android.getAttribute +不导入类对象,则无法通过类对象并访问类的静态属性,需调用以下方法获取类的静态属性值,方法原型如下: +``` javascript +Object plus.android.getAttribute( String|Object obj, String name ); +``` +此方法也可以获取类对象或实例对象的属性值,如果是类对象获取的则是类的静态属性,如果是实例对象则获取的是对象的非静态属性。 +- obj:若是String类型,表示要获取静态属性值的类名,类名必须是完整的命名空间(使用"."分割);若是ClassObject类型,表示要获取静态属性的类对象;若是InstanceObject类型,表示要获取属性值的实例对象。 +- name:要获取的属性名称,如果指定的属性名称不存在,则获取属性失败,返回null。 + +**注意**:同样导入类对象后也可以调用此方法,obj参数类型为ClassObject时,其作用与ClassObject.plusSetAttribute方法一致。obj参数类型为InstanceObject时,其作用与InstanceObject.plusSetAttribute方法一致。 + +示例: +1. 不导入类对象获取类的静态常量属性 +Java代码: +``` java +import io.dcloud.NjsHello; +//... +public class Test { +public static void main( String args[] ) { + // 获取类的静态常量属性 + int type = NjsHello.CTYPE; + System.out.printf( "NjsHello Final's value: %d", type ); // 输出“NjsHello Final's value: 1” + // 获取类的静态属性 + int count = NjsHello.count; + System.out.printf( "NjsHello Static's value: %d", count ); // 输出“NjsHello Static's value: 0” + //... +} +//... +} +``` +NJS代码: +``` javascript +// 不调用plus.android.importClass("io.dcloud.NjsHello")导入类NjsHello +// 访问类的静态常量属性 +var type = plus.android.getAttribute( "io.dcloud.NjsHello", "CTYPE" ); +console.log( "NjsHello Final's value: "+type ); // 输出“NjsHello Final's value: 1” +// ... +``` +2. 不导入类对象,创建实例对象,并获取其name属性值 +Java代码: +``` java +import io.dcloud.NjsHello; +//... +public class Test { +public static void main( String args[] ) { + // 创建对象的实例 + NjsHello hello = new NjsHello(); + // 获取其name属性值 + String name = hello.name; + System.out.printf( "NjsHello Object's name: %s", name ); // 输出“NjsHello Object's name: Tester” + //... +} +//... +} +``` +NJS代码: +``` javascript +// 不调用plus.android.importClass("io.dcloud.NjsHello")导入类NjsHello +// 创建对象的实例 +var hello = plus.android.newObject( "io.dcloud.NjsHello" ); +// 获取其name属性值 +var name = plus.android.getAttribute( hello, "name" ); +console.log( "NjsHello Object's name: "+name ); // 输出“NjsHello Object's name: Tester” +// ... +``` + +####plus.android.setAttribute +若没有导入类对象,则无法通过类对象设置类的静态属性值,需调用以下方法设置类的静态属性值,方法原型如下: +``` javascript +void plus.android.setAttribute( String|Object obj, String name, Object value ); +``` +此方法也可以设置类对象或实例对象的属性值,如果是类对象设置的则是类的静态属性,如果是实例对象则设置的是对象的非静态属性。 +- obj:若是String类型,表示要设置静态属性值的类名,类名必须是完整的命名空间(使用"."分割);若是ClassObject类型,表示要设置静态属性的类对象;若是InstanceObject类型,表示要设置属性值的实例对象。 +- name:要设置的属性名称,如果指定的属性名称不存在,则设置属性失败,返回null。 +- value:要设置的属性值,其类型必须与Native层obj对象的属性区配,否则设置操作不生效,将保留以前的值。 + +**注意**:同样导入类对象后也可以调用此方法,obj参数类型为ClassObject时,其作用与ClassObject.plusSetAttribute方法一致。obj参数类型为InstanceObject时,其作用与InstanceObject.plusSetAttribute方法一致。 + +示例: +1. 不导入类对象设置类的静态属性值 +Java代码: +``` java +import io.dcloud.NjsHello; +//... +public class Test { +public static void main( String args[] ) { + // 设置类的静态属性值 + NjsHello.count = 2; + System.out.printf( "NjsHello Static's value: %d", NjsHello.count ); // 输出“NjsHello Static's value: 2” + //... +} +//... +} +``` +NJS代码: +``` javascript +// 不调用plus.android.importClass("io.dcloud.NjsHello")导入类NjsHello +// 设置类的静态属性值 +plus.android.setAttribute( "io.dcloud.NjsHello", "count", 2 ); +console.log( "NjsHello Static's value: "+plus.android.getAttribute("io.dcloud.NjsHello","count") ); // 输出“NjsHello Static's value: 2” +// ... +``` +2. 导入类对象,创建实例对象,并设置其name属性值 +Java代码: +``` java +import io.dcloud.NjsHello; +//... +public class Test { +public static void main( String args[] ) { + // 创建对象的实例 + NjsHello hello = new NjsHello(); + // 设置其name属性值 + hello.name = "Tester"; + System.out.printf( "NjsHello Object's name: %s", hello.name ); // 输出“NjsHello Object's name: Tester” + //... +} +//... +} +``` +NJS代码: +``` javascript +// 不调用plus.android.importClass("io.dcloud.NjsHello")导入类NjsHello +// 创建对象的实例 +var hello = plus.android.newObject( "io.dcloud.NjsHello" ); +// 设置其name属性值 +plus.android.setAttribute( hello, "name", "Tester" ); +console.log( "NjsHello Object's name: "+hello.plusGetAttribute("name") ); // 输出“NjsHello Object's name: Tester” +// ... +``` + +####plus.android.invoke +若没有导入类对象,则无法通过实例对象的“.”操作符调用其成员方法,需通过以下方法调用实例对象的成员方法,方法原型如下: +``` javascript +Object plus.android.invoke( String|Object obj, String name, Object... args ); +``` +此方法也可以调用类对象或实例对象的方法,如果是类对象则调用的是类的静态方法,如果是实例对象则调用的是对象的普通成员方法。函数返回值是调用Native层方法运行后的返回值,Native对象的方法无返回值则返回undefined。 +- obj:若是String类型,表示要调用静态方法的类名,类名必须包含完整的包名;若是ClassObject类型,表示要调用静态方法的类对象;若是InstanceObject类型,表示要调用成员方法的实例对象。 +- name:要调用的方法名称,如果指定的方法不存在,则调用方法失败,返回值为null。 +- args:调用方法的参数,其类型和数目必须与Native层对象方法的函数区配,否则无法调用对象的方法,将返回null。 + +**注意**:同样导入类对象后也可以调用此方法,其作用与通过类对象或实例对象的“.”操作符调用方法作用一致。 + +示例: +1.不导入类对象,调用类的静态方法 +Java代码: +``` java +import io.dcloud.NjsHello; +//... +public class Test { +public static void main( String args[] ) { + // 调用类的静态方法 + NjsHello.testCount(); + //... +} +//... +} +``` +NJS代码: +``` javascript +// 不调用plus.android.importClass("io.dcloud.NjsHello")导入类NjsHello +// 调用类的静态方法 +plus.android.invoke( "io.dcloud.NjsHello", "testCount" ); +// ... +``` + +2. 不导入类对象,创建实例对象,并调用其updateNmae方法 +Java代码: +``` java +import io.dcloud.NjsHello; +//... +public class Test { +public static void main( String args[] ) { + // 创建对象的实例 + NjsHello hello = new NjsHello(); + // 调用updateName方法 + hello.updateName( "Tester" ); + System.out.printf( "NjsHello Object's name: %s", name ); // 输出“NjsHello Object's name: Tester” + //... +} +//... +} +``` +NJS代码: +``` javascript +// 不调用plus.android.importClass("io.dcloud.NjsHello")导入类NjsHello +// 创建对象的实例 +var hello = plus.android.newObject( "io.dcloud.NjsHello" ); +// 调用updateName方法 +plus.android.invoke( hello, "updateName", "Tester" ); +console.log( "NjsHello Object's name: "+hello.getAttribute("name") ); // 输出“NjsHello Object's name: Tester” +// ... +``` +**完整API文档参考:[HTML5+ API - Native.js for Android](http://www.html5plus.org/doc/zh_cn/android.html)** + +###API on iOS +####plus.ios.newObject +不导入类对象直接创建类的实例对象,方法原型如下: +``` java +InstanceObject plus.ios.newObject( String classname, Object..args ); +``` +此方法会在Native层中对类进行实例化操作,创建一个类的实体并返回NJS层的类实例对象。相比导入类对象后使用new操作符创建对象效率要高。 +- classname:要创建实例对象的类名,如果指定的类名不存在,则创建对象失败,返回null。 +- args:调用类构造函数的参数,其类型和数目必须与Native层对象构造函数区配,否则无法创建类对象,将返回null。 + +**注意**:由于没有导入类对象,所以通过此方法创建的实例对象无法通过“.”操作符直接调用对象的方法,而必须使用plus.ios.invoke方法来调用。classname参数值为“@selector”表示需要创建一个函数指针对象,与Objective-C中的@selector指令功能相似,args参数为函数的名称,此时函数的名称需要包含“:”字符。 + +示例: +1. 不导入类创建实例对象 +Objective-C代码: +``` objective-c +#import "njshello.h" +int main( int argc, char *argv[] ) +{ + // 创建对象的实例 + NjsHello* hello = [[NjsHello alloc] init]; + // ... +} +``` +NJS代码: +``` javascript +// 未导入“NjsHello”类 +// 创建对象的实例 +var hello = plus.ios.newObject( "NjsHello" ); +// ... +``` + +####plus.ios.invoke +若没有导入类对象,则无法通过实例对象的“.”操作符调用其成员方法,需通过以下方法调用实例对象的成员方法,方法原型如下: +``` javascript +Object plus.ios.invoke( String|Object obj, String name, Object... args ); +``` +此方法也可以调用类对象或实例对象的方法,如果是类对象则调用的是类的静态方法,如果是实例对象则调用的是对象的普通成员方法。函数返回值是调用Native层方法运行后的返回值,Native对象的方法无返回值则返回undefined。 +- obj:若是String类型,表示要调用静态方法的类名,类名必须包含完整的包名;若是ClassObject类型,表示要调用静态方法的类对象;若是InstanceObject类型,表示要调用成员方法的实例对象。 +- name:要调用的方法名称,必须保留方法名称中的“:”字符,如果指定的方法不存在,则调用方法失败,返回值为null。 +- args:调用方法的参数,其类型和数目必须与Native层对象方法的函数区配,否则无法调用对象的方法,将返回null。 + +**注意**:同样导入类对象后也可以调用此方法,其作用与通过类对象或实例对象的“.”操作符调用方法作用一致。 + +示例: +1. 不导入类创建实例对象,并调用updateName方法 +Objective-C代码: +``` objective-c +#import "njshello.h" +int main( int argc, char *argv[] ) +{ + // 创建对象的实例 + NjsHello* hello = [[NjsHello alloc] init]; + // 调用updateName方法 + [hello updateName:@"Tester"]; + NSLog(@"NjsHello Object's name: %@",hello.name); // 输出“NjsHello Object's name: Tester” + // ... +} +``` +NJS代码: +``` javascript +// 未导入“NjsHello”类 +// 创建对象的实例 +var hello = plus.ios.newObject( "NjsHello" ); +// 调用updateName方法 +plus.ios.invoke( hello, "updateName", "Tester" ); +console.log( "NjsHello Object's name: "+hello.getAttribute("name") ); // 输出“NjsHello Object's name: Tester” +// ... +``` +**完整API文档参考:[HTML5+ API - Native.js for iOS](http://www.html5plus.org/doc/zh_cn/ios.html)** + + +##性能优化 +###调整代码结构优化 +前面章节中我们介绍如何通过NJS调用Native API来显示系统提示框,在真机运行时会发现第一次调用时会有0.5s左右的延时,再次调用则不会延时。这是因为NJS中导入类对象操作会花费较长的时间,再次调用时由于类对象已经导入过,会能很快执行完毕。因此可以调整代码结构进行优化,在页面打开后触发的“plusready”事件中进行类对象的导入操作,从而避免第一次调用的延时。 + +Android平台调整NJS代码结构如下: +``` javascript +// 保存Android导入对象和全局环境对象 +var AlertDialog=null,mainActivity=null; +// H5+事件处理 +document.addEventListener("plusready",function(){ + switch ( plus.os.name ) { + case "Android": + // 程序全局环境对象,内部自动导入Activity类 + mainActivity = plus.android.runtimeMainActivity(); + // 导入AlertDialog类 + AlertDialog = plus.android.importClass("android.app.AlertDialog"); + break; + default: + break; + } +},false); +//... +/** + * 在Android平台通过NJS显示系统提示框 + */ +function njsAlertForAndroid(){ + // 创建提示框构造对象,构造函数需要提供程序全局环境对象,通过plus.android.runtimeMainActivity()方法获取 + var dlg = new AlertDialog.Builder(mainActivity); + // 设置提示框标题 + dlg.setTitle("自定义标题"); + // 设置提示框内容 + dlg.setMessage("使用NJS的原生弹出框,可自定义弹出框的标题、按钮"); + // 设置提示框按钮 + dlg.setPositiveButton("确定(或者其他字符)",null); + // 显示提示框 + dlg.show(); +} +//... +``` +iOS平台调整NJS代码结构如下: +``` javascript +// 保存iOS平台导入的类对象 +var UIAlertView=null; +// H5+事件处理 +document.addEventListener("plusready",function(){ + switch ( plus.os.name ) { + case "iOS": + // 导入UIAlertView类 + UIAlertView = plus.ios.importClass("UIAlertView"); + break; + default: + break; + } +},false); +//... +/** + * 在iOS平台通过NJS显示系统提示框 + */ +function njsAlertForiOS(){ + // 创建UIAlertView类的实例对象 + var view = new UIAlertView(); + // 设置提示对话上的内容 + view.initWithTitlemessagedelegatecancelButtonTitleotherButtonTitles("自定义标题" // 提示框标题 + , "使用NJS的原生弹出框,可自定义弹出框的标题、按钮" // 提示框上显示的内容 + , null // 操作提示框后的通知代理对象,暂不设置 + , "确定(或者其他字符)" // 提示框上取消按钮的文字 + , null ); // 提示框上其它按钮的文字,设置为null表示不显示 + // 调用show方法显示提示对话框 + view.show(); +} +//... +``` + +###使用高级API优化 +前面章节中我们提到导入类对象会消耗较多的系统资源,导入过多的类对象会影响性能。在高级API中提供一组接口可以在不导入类对象的情况下调用Native API,从而提升代码运行性能。 + +Android平台使用高级API优化代码如下: +``` javascript +// 保存Android导入对象和全局环境对象 +var mainActivity=null; +// H5+事件处理 +document.addEventListener("plusready",function(){ + switch ( plus.os.name ) { + case "Android": + // 程序全局环境对象,内部自动导入Activity类 + mainActivity = plus.android.runtimeMainActivity(); + break; + default: + break; + } +},false); +//... +/** + * 在Android平台通过NJS显示系统提示框 + */ +function njsAlertForAndroid(){ + // 由于Builder类是android.app.AlertDialog类的内部类,这里需要使用$符号分割 + var dlg = plus.android.newObject("android.app.AlertDialog$Builder",mainActivity); + // 设置提示框标题 + plus.android.invoke(dlg,"setTitle","自定义标题"); + // 设置提示框内容 + plus.android.invoke(dlg,"setMessage","使用NJS的原生弹出框,可自定义弹出框的标题、按钮"); + // 设置提示框按钮 + plus.android.invoke(dlg,"setPositiveButton","确定(或者其他字符)",null); + // 显示提示框 + plus.android.invoke(dlg,"show"); +} +//... +``` + +iOS平台使用高级API优化代码如下: +``` javascript +/** + * 在iOS平台通过NJS显示系统提示框 + */ +function njsAlertForiOS(){ + // 创建UIAlertView类的实例对象 + var view = plus.ios.newObject("UIAlertView"); + // 设置提示对话上的内容,这里的方法名称中必须包含':'字符 + plus.ios.invoke(view,"initWithTitle:message:delegate:cancelButtonTitle:otherButtonTitles:" + ,"自定义标题" // 提示框标题 + , "使用NJS的原生弹出框,可自定义弹出框的标题、按钮" // 提示框上显示的内容 + , null // 操作提示框后的通知代理对象,暂不设置 + , "确定(或者其他字符)" // 提示框上取消按钮的文字 + , null ); // 提示框上其它按钮的文字,设置为null表示不显示 + // 调用show方法显示提示对话框,在JS中使用()语法调用对象的方法 + plus.ios.invoke(view,"show"); +} +//... +``` \ No newline at end of file -- GitLab