血泪总结:如何从微信小程序的坑跳进支付宝小程序的大坑

众所周知,iOS没人要了,小程序现在火了...

哈哈,开玩笑,不过小程序真是非常火

所以,今天我就来讲讲最近折腾出的小程序总结:如何从微信小程序的坑跳进支付宝小程序的大坑!

小程序非常适合不经常使用的线下服务场合,比如偶尔在某饭店点个菜,偶尔在某景区租个车,等等

如果你已经有了微信小程序,那么再开发一个支付宝小程序也并没有多难.毕竟80%以上的代码是通用的.最大的困难可能还是对小程序语法/api的不熟悉,还有文档不够全面.

于是我最近就经历了这样一系列填坑操作.

小程序API差异:

xml

对于xml来说,最大的不同除了文件后缀名不同外,其实就是wx:改为了a:,还有事件绑定由bind开头改为用on开头:

微信支付宝备注
文件名.wxml文件名.axml后缀名不同
wx:ifa:if
wx:elifa:elif
wx:elsea:else
wx:for-itema:for-item
wx:keya:key
bindtaponTap
bindsubmitonSubmitform组件
bindresetonResetform组件
bindinputonIpputinput组件
bindchangeonChangeswitch组件
bindmarkertaponMarkerTapmap组件
bindcontroltaponControlTapmap组件

css

css中两者几乎一模一样,基本不用改动,主要问题在map组件

微信支付宝备注
文件名.wxss文件名.acss后缀名不同
90%90vh!importantmap组件
// 地图高度设置不能用标签选择器:#map
map {
  height: 90vh!important;
}
复制代码

用其它方法来设置map组件的高度是不起作用的,必须加上!important;

  • 如果还不起作用,请务必使用90vh!important这样;

  • 如果还不起作用,请注意不要使用标签选择器:#map

  • 如果还有问题,请在xml中调整一下,在所有组件最外层包上一个<view>组件,并将其设置为100%宽高;

  • 别问我怎么知道的,我搞了好几天....

还有一点需要注意,相信大家也很容易看出来:
支付宝中view组件布局默认是包裹内部组件;

也就是说如果view组件内部一个button,button宽高为40rpx,那么view不会全屏显示,需要手动设置view组件width:100%;height:100%

js

js的文件后缀名是相同的,喜大普奔!
最大改变是将wx.改为了my.

微信支付宝备注
wx.showModalmy.confirm
wx.getStorageSync('user')my.getStorageSync({ key: 'user' }).data非常蛋疼!
wx.setStorageSync('area', res.data)my.setStorageSync({key: 'area', data:res.data})非常蛋疼!
wx.removeStorageSync('area')my.removeStorageSync({key:'area'})非常蛋疼!
wx.requestmy.httpRequest注意参数header—>headers,结果中的res.statusCode-->res.status
wx.requestPaymentmy.tradePay支付接口参数不同:支付宝为拼接的参数,微信为单独参数;回调结果支付宝有三种状态:成功,失败,未知或支付中;微信只有成功,失败
wx.getSetting权限在对应api中获取
wx.loginmy.getAuthCode多了参数scopes: 'auth_user', // 主动授权(弹框):auth_user,静默授权(不弹框):auth_base
wx.setNavigationBarTitlemy.setNavigationBar不仅能设置title,还能设置背景色等
wx.createBLEConnectionmy.connectBLEDevice蓝牙
wx.closeBLEConnectionmy.disconnectBLEDevice蓝牙
wx.getSystemInfoSync().SDKVersionmy.SDKVersion
wx.getSystemInfoSync().systemmy.getSystemInfoSync().platform注意大小写:系统平台android/iOS—>系统平台Android/iOS;另外个别安卓机型上可能为空!!!!!
wx.createMapContext('map').includePoints({points: that.data.map.polyline[0].points})that.data.map.includePoints = that.getPolyLine(data.border1).points改为先在axml中绑定属性,再设置
wx.openLocation()my.openLocation()参数数量不同,类型不同

地图组件中又出现了小麻烦,微信中是拿到mapContext直接设置包含的点,而支付宝中需要先在axml中绑定

<map include-points='{{map.includePoints}}'></map>
复制代码

然后改变对应的值并that.setData赋值刷新界面.

还有打开内置地图导航的API,也有差异:微信中只需要三个参数:经度,纬度,名称.经纬度为Number类型.而支付宝中需要四个参数:经度,纬度,名称,地址.经纬度为String类型

json

json的文件后缀名也是相同的,喜大普奔!

微信支付宝备注
navigationBarTitleTextdefaultTitle标题
navigationBarBackgroundColortitleBarColor

更大的坑

蓝牙适配器

更大的坑还是出在了蓝牙组件上,微信中多次调用打开蓝牙适配器没有问题,但支付宝小程序中,蓝牙已经连接的情况下再重复调用就导致连接失败或断开重连:

wx.openBluetoothAdapter()—>my.openBluetoothAdapter()
复制代码

所以建议先判断适配器的状态,在error回调中,根据err.code判断发现没有打开时再打开蓝牙适配器.

如果没有提前打开Adapter,那么后面的my.onBluetoothAdapterStateChange监听是无法工作的.

官方文档:初始化小程序蓝牙模块,生效周期为调用 my.openBluetoothAdapter 至调用 my.closeBluetoothAdapter 或小程序被销毁为止。 在小程序蓝牙适配器模块生效期间,开发者可以正常调用下面的小程序API,并会收到蓝牙模块相关的 on 事件回调。

  1. my.getBluetoothAdapterState判断是否已打开,已经打开可正常操作;
  2. 未打开的话,再调用my.openBluetoothAdapter来打开;
  3. 在complete回调中,再调用my.getBluetoothAdapterState判断是否已打开,并设置监听;

蓝牙搜索新设备

接下来,是在搜索蓝牙的回调里,蓝牙广播中的advertisData格式不同,支付宝中是16进制的字符串,而微信中是ArrayBuffer,也就是二进制数组.

微信官方文档也做了说明:advertisData为当前蓝牙设备的广播数据段中的ManufacturerData数据段 (注意:vConsole 无法打印出 ArrayBuffer 类型数据)

my.onBluetoothDeviceFound(function (res) {
    let devices = res.devices // 设备列表
    
    for (let i in devices) {
        //支付宝中,将16进制字符串转换为真正的字符串
        let mac = hexToString(devices[i].advertisData).toLowerCase()
        //微信中,将二进制数组转换为字符串
        //let mac = ab2str(devices[i].advertisData).toLowerCase()
        let deviceId = devices[i].deviceId
    }
}
复制代码

获取已连接的蓝牙设备

还有一个不同,获取处于已连接状态的蓝牙设备列表.

微信中只要传入service数组就可以准确获取到对应的蓝牙设备,并在success中给出设备的信息;

而支付宝中必须传入广播中公开的serviceId才能找到对应的设备,success回调中给出的也是设备的广播数据...

举个例子:一个蓝牙设备:小米手环2s,共有4个service:['AAAA','BBBB','CCCC','DDDD'],其中AAAA和BBBB是公开的,也就是放在广播中的,无须连接就可以获取到;而CCCC和DDDD是必须连接上之后才能获取到的.

在微信中,传入CCCC和DDDD,就可以得到当前已经连接中的'小米手环2s'(可能有多个,所以是数组);

而在支付宝中,传入CCCC和DDDD得到的是空数组.必须传入AAAA或BBBB这种公开在广播中的serviceId才能找到已连接的所有'小米手环2s':

my.getConnectedBluetoothDevices({   // 获取处于已连接状态的设备
    //services: [that.data.u1], that.data.u2],//支付宝中有限制
    success: function (res) {
        res.devices.forEach(function (ud, index) {
            if (ud.deviceId === that.data.deviceId) {//目标设备
                console.log('目标设备已连接',ud.deviceId,that.data.deviceId)
            }
        })
    } 
}

复制代码

蓝牙写入操作

微信中写入的value是ArrayBuffer即二进制,而支付宝中是Hex String即16进制字符串.

比较坑的是官方说明:

value: Hex String 必填 蓝牙设备特征值对应的值,16进制字符串,限制在20字节内

这个20字节,实际指的是二进制数据的长度,而16进制的2位才代表1字节,所以每次传入的字符串长度应该是:小于40!!!!

my.writeBLECharacteristicValue({
      deviceId: deviceId,
      serviceId: serviceId,
      characteristicId: characteristicId,
      value: value,
      success: function (res) {
        console.log('向蓝牙写入数据 success', res)
      },
      fail: function (res) {
        console.log('向蓝牙写入数据失败', res)
        
      }
    })
复制代码

蓝牙状态监听

还有一个比较搞笑的是,支付宝小程序官方文档中对蓝牙各种监听的说明:

// 开始监听蓝牙变化,先移除一下

my.offBluetoothAdapterStateChange();//tip: 为防止多次注册事件监听导致一次事件多次回调,建议每次调用on方法监听事件之前,先调用off方法,关闭之前的事件监听。

my.onBluetoothAdapterStateChange(function (res) {/*监听到适配器状态改变*/})
复制代码

所有的监听事件api都加了说明,让先调用off方法移除一下,以防重复监听,这种操作让我这个做过iOS原生蓝牙开发的非常不适应,简直哭笑不得呀.

再看微信的小程序,根本就没有off方法,估计是内部做了防止重复监听的处理.

在安卓上打印为空

4月份已经有人在官方论坛上反应onBluetoothDeviceFound返回值打印为空了,官方回应Demo是正常的.我也遇到了这个问题.

还有人说是打开调试开关导致的异常,但我关掉调试界面,也没有用.....

经过与官方Demo的仔细对比,发现最大问题是里面有数据,但在安卓上打印不出来...

my.onBluetoothDeviceFound(function (res) {
 let devices = res.devices // 设备列表
 console.log('onBluetoothDeviceFound', res)//打印出的res在安卓上为空
 console.log(res)//打印出的res在安卓上为[object Object]
 console.log(JSON.stringify(res))//在安卓上终于成功输出了...
})
复制代码

后来仔细一测试,安卓上,所有的东西都不能直接打印,官方示例中全都是用JSON.stringify(res)来打印输出的.而正常取值是不受影响的.

蛋碎一地呀~!!!

switch-case的坑

在支付宝小程序中,一个简单的switch-case语法竟然出错了!!!

switch (code) {
    case 0:
        console.log('0')
        break;
    case 1:
        console.log('1')
        break;
    case 2:
        console.log('2')
        break;
    default:
        console.log('default')
复制代码

出错原因是,当code=0时,case 0分支无法进入,直接进入了default分支,真是无比蛋疼!!!

其他分支没有问题,避免方法是,不使用case 0,改成case 10~

安卓上删除小程序不会清理数据

在微信上,如果用户删除了小程序,所有产生的/储存的数据都会被清理掉。

在支付宝上,iOS 表现和微信一致,都会被删除;而安卓上setStorageSync储存的数据不会被删除!!!!!

my.setStorageSync({ key: 'user', data: userData })
复制代码

所以必须手动判断删除:

my.removeStorageSync({ key: 'user' })
复制代码

真是让人无比蛋疼!!坑爹啊!!!

地图组件 control 控件的图标

由于现在支付宝地图组件层级最高,还没有推出cover-image功能,所以要在地图上覆盖按钮,就只能用control组件。这样需要给control组件设置iconPath,如下:

controls: [{
    id: 10,
    iconPath: imgPath + "icon_new_user.png",
    position: {
      left: 280,
      top: 400,
      width: 35,
      height: 35
    },
    clickable: true
}]
复制代码

这里有个坑,就是position在安卓上正常的,在 iOS 上widthheight无效!!!!即永远显示图片自身的真实大小!

更开放的 webview

微信中的 webview 组件有很多限制,最常见的是 webview 和小程序的通信是受限的,虽然有专门的函数,但是官方说发送的数据只有在页面销毁或用户手动点击分享时,小程序才会真正收到消息。

还有就是微信小程序中,webview 组件强制全屏且不可被覆盖。

支付宝中就没有这么多限制。

总结

总之,支付宝小程序和微信小程序相似度非常高,熟悉js和vue的同学可以在2周内就熟练将一个小项目从微信小程序转换成支付宝小程序;

两者的IDE也都是基于VS Code做的二次开发,界面类似,容易上手,目前支付宝小程序IDE的功能更少一些.

目前,两者坑不少,而支付宝小程序是比微信小程序更大的坑...