安卓开发者的小程序入门整理 网络访问&异步解决

202 阅读6分钟

写在前面

  • 上一篇讲了小程序一个页面文件构成
  • 本篇主要是模块化以及网络访问(http&websocket)
  • 还有一点点自己的异步解决

网络访问

小程序API提供了一套封装好了的网络框架
对!就是不用封装了
每次android换一次网络框架真的是 一言难尽

HTTP

这里挑一个使用频率较高的 wx.request(OBJECT) 来说
参数OBJECT实际上是一个JSON格式的数据里面除了url以外其他都是非必填参数

method -> GET
dataType -> json
responseType -> text

值得注意的一点是也是我在网上查的时候发现很多人问的问题
啰嗦一句 小程序的 header 其实也是有默认值的

{
'content-type': 'application/json' // 默认值
}

所以当传输为Form表单时记得修改header

PS.因为wx这个API还是留了些参数让开发者自定义,建议进行简单的二次封装并模块化方便复用

WebSocket

小程序这次是第一次使用websocket所以对于我还是挺新鲜的会详细总结一下 WS其实在okhttp上也有封装 只是安卓碰到的大多是标准的 客户端 -> 服务器 场景 遇到 客户端 <=> 服务器 轮询似乎成了唯一方法 但是小程序内部封装了WS协议的访问方法 而且使用起来 ** 十分简洁 **

简单介绍

WS的从服务器到小程序机制类似于安卓广播
只不过这个广播没有特定的目标
只要接入了服务器WS API 的所有用户都可以在同时收到来自服务器的广播
所以这个对于服务器是有一定压力的 (但是相比轮询来说已经好很多了)
然而客户端一般只需要对广播进行判断就可以进行相应操作
比如 访问之前需要轮询的HTTP接口

TIPS

WS看上去解决了很多问题 但是WS的保活就显得十分重要
加上小程序看起来 乱七八糟 的声明周期有时候WS被莫名奇妙关闭就会显得很麻烦
下面是一些注意事项
第一点
onHide 和 onUnload 并不会自动关闭WS
但是WS对于服务器还是有一定的压力的建议在onUnload时关掉WS
第二点
右上角的关闭键会销毁 WS 但不会直接马上销毁
dei dei dei 会销毁但不会马上销毁
最重要的是 重新进来小程序时不会从onLoad执行 !
而且在小程序的API中并没有提供方法进行WS状态的判定方法
建议在APP.js中的globalData里面自己写一个WS的状态值
并且在wx.onSocketClosewx.onSocketOpen 里面处理一下

异步

异步对于小程序网络访问是绝对让人头大的一个问题 而且微信的官方代码里给了一个并不太漂亮的解决方法 让我这个从Android开发者一开始上手遇到了不少很麻烦的问题

WHY 异步
JS其实确实是单线程应用所以它无法像JAVA一样去直接操控线程来达到async同步
这里再啰嗦一句 JS单线程为什么存在异步

JS运行下的运行环境如
浏览器或者node
会在一些耗时任务给予其开辟另外的线程去执行耗时任务
然后通过类似于Android中MessageQueue的机制把额外线程执行完后的事件
循环按照顺序取出并执行 以此来实现异步

而在小程序开发时一定会遇到的问题就是
用户数据读取自己服务器更新这些数据的先后关系
在小程序默认index.jsapp.js 其实官方使用接口回调举了一个栗子
首先我们先来分析一下接口回调情况下怎么解决这个问题
这个栗子所解决当的问题在于 APP 和 Page 的加载时出现的异步
下面的Main.js指正常情况下第一个被加载出来的页面
也就是说App.js 中网络访问和 Main.jsonLoad 是不确定谁先完成
那么就需要一个方法来解决异步
即当 wx.getSetting 执行完之后通知Main.js去刷新代码
先看一下官方的代码
App.js

// 获取用户信息
    wx.getSetting({
      success: res => {
        if (res.authSetting['scope.userInfo']) {
          // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
          wx.getUserInfo({
            success: res => {
              // 可以将 res 发送给后台解码出 unionId
              this.globalData.userInfo = res.userInfo

              // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
              // 所以此处加入 callback 以防止这种情况
              if (this.userInfoReadyCallback) {
                this.userInfoReadyCallback(res)
              }
            }
          })
        }
      }
    })

Main.js 即官方demo里的index.js

if (app.globalData.userInfo) {
      this.setData({
        userInfo: app.globalData.userInfo,
        hasUserInfo: true
      })
    } else if (this.data.canIUse){
      // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
      // 所以此处加入 callback 以防止这种情况
      app.userInfoReadyCallback = res => {
        this.setData({
          userInfo: res.userInfo,
          hasUserInfo: true
        })
      }
    }

首先看App.js 获取授权之后执行真正获取信息的 wx.getUserInfo
成功以后把值赋给 .globalData.userInfo
然后会去判断一个Callback是否存在存在即执行
把这个Callback先放一边看下一段
Main.js中如果.globalData.userInfo存在那么会直接 setData
这种即 Main.onLoad() 之前就已经获得了data那么不需要处理异步
直接加载即可
继续往下看 先忽略这个if条件不是我们要讨论的对象
然后这里对 app.userInfoReadyCallback进行了一个定义 然后内容是 setData
那么就明白了 App.js 本来就有一个空值
如果onLoadwx.getUserInfo之前完成
.globalData.userInfo 不存在为空那么会去定义回调函数
wx.getUserInfo里的success执行过程中判定到app.userInfoReadyCallback 不为空
也就是网络访问慢于页面加载是会对回调函数进行执行 解决异步问题

OK 我们搞清楚了这种回调 但是当有很多异步需要管理的时候
嵌套就会变得很麻烦 但是JS有一个类似于RxJAVA的函数 Promise()
而且是小程序本来就支持的
其实JS还有ES6语法async/waite
但是IDE并不能直接转码还需要加入第三方工具进行转码 并不划算 *
话不多说 举个栗子
Main.js

app.promisLogin()//token
    .then(that.promiseUpdateInfo)//刷新页面
	.catch(function (res) {
      wx.hideLoading()
      wx.showToast({
        title: '网络错误',
        icon: 'none'
      })
      console.log('初始化错误 '+res);
    });

这几个函数体本身

promisLogin : function(){
    return new Promise(function(resolve,reject){
      wx.login({
        success: res => {
          // 发送 res.code 到后台换取 openId, sessionKey, unionId
          if (res.code) {
            //发起网络请求
            wxCode=res.code
            console.log(res.code)
            // this.apiLogin(res)
            resolve()
          } else {
            console.log('获取用户登录态失败!' + res.errMsg)
          }
        }
      })
    })
  },

重点在于resolve()reject()
当方法执行到这两个函数时开始执行下一个步骤
这样的链式调用明显使得异步逻辑更清晰了

模块化

模块化几乎是所有开发过程中都需要用到
特别是碰到网络访问之后一个api的复用所以这里放在一起总结


JS的模块化和JAVA类似 通过 * require * 来进行声明

var api = require('../../utils/api.js')

但是由于JS没有 * private protected public * 这些字段来约束可见性
所以在模块的最后需要通过 * module.exports * 字段来声明那些方法可见

多个方法

module.exports = {
  getUserInfo: getUserInfo,
  getGroupInfo: getGroupInfo,
}

单个方法

module.exports.getUserInfo = getUserInfo