小程序刚出那会我还很兴奋,觉得微信好厉害,应用做平台扩展应用之上的小程序这样的产品,不过兴起一小段时间后再次沉寂,毕竟用户的习惯并没有那么容易改过来。
这没多久,小程序再次发力了,原本不做游戏,也出了各种各样的小游戏,与微信整个平台更加紧密连接,但这些夺走了生产厂家的许多东西。硬件厂家们预装软件因为小程序的出现减少了,这一部分利润消失让几大硬件厂意识到了,也做出了这个对策。
快应用的诞生就是对标小程序,同样极小的,不需要安装可以称为应用的东西,并且相对小程序有更好的使用体验个人感觉并不能搞过小程序,微信难以撼动

直接入手吧:

安装

几个必要的软件:

  1. NodeJs:
    需安装6.0以上8.0以下版本的NodeJS,推荐v6.11.3

  2. hap-toolkit:
    使用npm安装:
    npm install -g hap-toolkit
    之后使用npm时可能会出现类似
    Error: Cannot find module:......
    这样的错误,可以使用:
    hap update --force
    然后不再需要npm install,直接执行之后如npm run build等的命令。

  3. 快应用调试器:
    下载链接
    用于快速调试安装更新开发快应用的工具,不过也可以直接使用hbuilder内部自带的运行,方便快捷除了偶尔会出现点奇怪的错误
    命令行中进入项目根目录后:
    npm run server
    开启临时服务器,使用调试器上的扫码安装扫描该二维码即可安装,不过经常容易出现更新安装失败的问题,所以可以在开启server之后使用:
    npm run build
    打包出rpk文件的安装包,在根目录下的dist文件夹里,第一次可能需要手动拖入手机,在调试器内点击本地安装进行安装,之后下一次更新只要server开着即可直接使用build命令直接打包更新。

  4. 平台预览版:
    调试器安装完如果未安装预览版会提示下载并安装,
    调试器1
    之后三个按钮也可以点击了
    调试器2

    开发使用的ide比较宽泛,从记事本到webstorm vscode都可以,android studio也可以,只是一些文件格式需要加入识别,和小程序weex相似都有各自的文件格式,不过都是使用的js进行开发。
    不过在这里推荐一款轻便的编辑器HBuilder,运行调试编译都可以在这里进行,所以也就不需要调试器了。
    hbuilder

动手

  1. 创建项目
    输入:hap init ProjectName创建一个项目,修改为当前项目的结构,目录结构如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    ├── sign                      rpk包签名模块
    │ └── debug 调试环境
    │ ├── certificate.pem 证书文件
    │ └── private.pem 私钥文件
    ├── dist 使用npm run build命令打包的rpk文件目录
    ├── src
    │ ├── Common 公用的资源和组件文件
    │ │ └── logo.png 应用图标
    │ ├── WebView 文章展示目录
    │ │ └── index.ux 文章展示使用的webview页面
    │ ├── Main 主页目录
    │ | └── index.ux 主页页面
    │ ├── app.ux APP文件,可引入公共脚本,暴露公共数据和方法等
    │ └── manifest.json 项目配置文件,配置应用图标、页面路由等
    │ └── util.js 创建项目自动创建的文件
    └── package.json 定义项目需要的各种模块及配置信息

    本次使用的是玩安卓的api,感谢鸿洋大神!只写个主页以及文章,毕竟只是练手。

  2. 首先是manifest.json的修改
    代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    {
    /*包名*/
    "package": "com.xblydxj.fastAppDemo",
    /*应用名*/
    "name": "快应用小入门",
    /*版本名称和版本号*/
    "versionName": "1.0.0",
    "versionCode": "1",
    /*支持的最小平台版本号*/
    "minPlatformVersion": "100",
    /*应用图标*/
    "icon": "/Common/logo.png",
    /*接口*/
    "features": [
    { "name": "system.prompt" },
    { "name": "system.router" },
    { "name": "system.shortcut" },
    { "name": "system.fetch" },
    { "name": "system.webview" },
    { "name": "system.storage" },
    { "name": "system.share" },
    { "name": "system.clipboard" }
    ],
    "permissions": [
    { "origin": "*" }
    ],
    /*控制台可查看的日志输出级别*/
    "config": {
    "logLevel": "debug"
    },
    /*路由*/
    "router": {
    "entry": "Main",
    "pages": {
    "Main": {
    "component": "index"
    },
    "Webview":{
    "component": "index"
    }
    }
    },
    /*UI配置*/
    "display": {
    "titleBarBackgroundColor": "#26a69a",
    "titleBarTextColor": "#ffffff",
    "pages": {
    "Main": {
    "titleBarText": "主页"
    }
    }
    }
    }
  3. 主页

    • 主页布局:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      <template>
      <refresh @refresh="refresh" refreshing="{{isRefreshing}}">
      <list class="home-page" onscrollbottom="loadMoreData">
      <list-item type="banner">
      <swiper class="banner" autoplay="true" interval="4000">
      <stack class="banner" for="{{bannerlist}}">
      <image class="banner-image" src="{{$item.imagePath}}" onclick="openArticle($item.url, '', $item.title)"></image>
      </stack>
      </swiper>
      </list-item>

      <block for="articleList">
      <list-item type="article" class="article-item" onclick="openArticle($item.link, $item.projectLink, $item.title)">
      <div style="display: flex">
      <text class="text-title" style="flex: 1">{{$item.title}}</text>
      </div>
      <div class="article-tip">
      <text class="tip">分类: {{$item.superChapterName}}/{{$item.chapterName}}</text>
      </div>
      <div class="article-tip">
      <text class="tip">作者: {{$item.author}}</text>
      <text class="time">{{$item.niceDate}}</text>
      </div>
      </list-item>
      </block>

      <list-item type="loadMore" class="load-more" if="articleList.length > 0">
      <progress type="circular" show="{{hasMoreData}}"></progress>
      <text show="{{hasMoreData}}">加载中</text>
      <text show="{{!hasMoreData}}">没有更多了</text>
      </list-item>
      </list>
      </refresh>
      </template>

      外部由refresh组件包裹,支持下拉刷新,内部由一个整体的list组件进行排列。
      第一个item为banner,由swiper作为banner的组件,内部包裹一个stack容器,容器内即为banner内部的图片。
      第二个item为block包裹的list-item文章列表,内部为列表中单个item的布局
      第三个item为上拉刷新,显示内容如上,具体内部刷新逻辑为js处理。

    • 主页js:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      <script>
      import fetch from '@system.fetch'
      import webview from '@system.webview'
      import router from '@system.router'
      export default {
      data: {
      bannerlist: [],
      articleList: [],
      page: 0,
      hasMoreData: true,
      isRefreshing: false,
      },
      onInit() {
      this.refresh({refreshing: true})
      },
      async getBanner() {
      fetch.fetch({
      url:"http://www.wanandroid.com/banner/json",
      success:function(data){
      this.bannerlist = JSON.parse(data.data).data;
      }.bind(this),
      fail: function(data, code) {
      console.log("failed: code=" + code);
      }
      })
      },
      async getArticle() {
      fetch.fetch({
      url:'http://www.wanandroid.com/article/list/' + this.page + '/json',
      success: function(data){
      data = JSON.parse(data.data).data
      this.hasMoreData = !data.over
      if(this.page > 0) {
      this.articleList = this.articleList.concat(data.datas)
      } else {
      this.isRefreshing = false
      this.articleList = data.datas
      };
      }.bind(this),
      fail: function(data, code) {
      console.log("failed code=" + code);
      }
      })
      },
      loadMoreData() {
      if(this.hasMoreData) {
      this.page++
      this.getArticle()
      }
      },
      openArticle(link, projectLink, title) {
      var url = projectLink === '' ? link : projectLink
      if(url !== '') {
      this.$app.$def.router.push({
      uri: 'Webview',
      params: {
      title: title,
      url: url
      }
      })
      }
      },
      refresh(evt) {
      this.isRefreshing = evt.refreshing
      this.page = 0
      this.getBanner()
      this.getArticle()
      }
      }
      </script>

      对应布局中使用到的一些方法,以及快应用本身的生命周期方法。
      主页中使用到的banner与article数据的初始化,以及一些标志位作为刷新判断。
      getBanner使用async异步处理,进行url的请求,得到数据后json解析并赋值于bannerlist用于布局中使用显示。
      getArticle由于有刷新的部分,内部添加了一些页面的判断修改之后同样提供给articleList。
      下方一些为上拉加载与下拉刷新的方法以及点击banner或者文章时跳转webview的方法。

    • 主页style:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      <style>
      .banner, .banner-image {
      width: 100%;
      padding: 5px;
      height: 360px;
      }
      .banner-title {
      text-overflow: ellipsis;
      lines: 1;
      text-align: center;
      height: 90px;
      width: 100%;
      color: #ffffff;
      background-color: #222222;
      opacity: 0.5;
      }
      .article-item {
      height: 200px;
      padding: 30px;
      display: flex;
      flex-direction: column;
      border-bottom-width: 1px;
      border-bottom-color: #eeeeee;
      }
      .text-tip {
      width: 100%;
      padding: 10px;
      padding-top: 30px;
      padding-bottom: 30px;
      border-bottom-width: 1px;
      border-bottom-color: #eeeeee;
      }
      .article-item .text-title {
      font-size: 30px;
      color: #222222;
      text-overflow: ellipsis;
      lines: 1;
      margin-bottom: 15px;
      }
      .article-item .article-tip {
      display: flex;
      flex-direction: row;
      }
      .article-tip .tip, .article-tip .time {
      font-size: 24px;
      color: #aaaaaa;
      }
      .time {
      flex: 1;
      text-align: right;
      margin-top: 5px;
      }
      .load-more {
      padding: 20px;
      display: flex;
      flex-direction: row;
      justify-content: center;
      }
      </style>

      由于快应用中的组件会被渲染为原生的组件,所以暂时还有很多不支持。只是进行一些简单的配置。

  1. Webview页面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <template>
    <web src="{{ url }}" id="web"></web>
    </template>

    <script>
    export default {
    data: {
    title: '',
    url: ''
    },
    onInit () {
    this.$page.setTitleBar({ text: this.title })
    }
    }
    </script>

    主页中获取的链接与标题将会使用路由发送至此页面进行显示。

  2. app.ux中的配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
     <script>
    /**
    * 应用级别的配置,供所有页面公用
    */
    import util from './util'
    import clipboard from '@system.clipboard'
    import prompt from '@system.prompt'
    import router from '@system.router'
    import storage from '@system.storage'
    import share from '@system.share'
    import nativeFetch from '@system.fetch'

    export default {
    clipboard: clipboard,
    prompt: prompt,
    router: router,
    storage: storage,
    share: share,
    createShortcut: util.createShortcut
    }

    const globalRef = global.__proto__ || global
    // global注入regeneratorRuntime
    globalRef.regeneratorRuntime = require('babel-runtime/regenerator')

    const natives = {
    async fetch (options) {
    const p1 = new Promise((resolve, reject) => {
    options.success = function (data, code) {
    resolve({ data, code })
    }
    options.fail = function (data, code) {
    resolve({ data, code })
    }
    nativeFetch.fetch(options)
    })
    return p1
    }
    }
    globalRef.natives = natives
    </script>

在这里进行一些全局的配置,主要由接口已经全局引用和网络请求上的初始化配置。

成品

成品1

成品2