基于 Expo 的 React Native 消息推送

ExpoReact Native 开发的一个神器,正如 Expo 官网上所说,Expo 之于 React Native 就像 Rails 之于 Ruby,它提供了很多超越原生 React Native API 的功能,包括二维码扫描、存储、内部浏览器等,甚至还可以使用 Expo 进行 APP 的打包,完全不需要使用 XCode 和 Android Studio。

而消息推送则是 APP 应用非常常见的一个功能,今天就来介绍一下基于 Expo 的 React Native 消息推送功能是如何开发的吧。

APP 的消息推送一般有两种形式,一种是本地消息,比如 APP 中有个定时执行的任务,完成后给用户发送一个”任务执行完成”的通知;另外一种是远程消息,比如说某个电商做活动,会向用户推送一些活动相关的消息。

本地和远程消息两者本质的区别就是:本地消息是由 APP 本身发送的,而远程消息是由 APP 的后端(服务端)发送的。

本地消息推送

本地消息不需要和服务端交互,相对比较简单,让我们先来看看 Expo 的本地消息是如何发送的。

消息对象

一般我们手机上收到的推送消息大概是下面这样子,有 APP 的 Icon、标题、摘要信息以及推送的时间。

所以我们需要先构造一个本地消息对象,它主要包含以下属性:

  • title:字符串类型,必填属性,消息的标题,会显示在手机的通知栏上面
  • body:字符串类型,必填属性,消息的摘要信息,会显示在手机的通知栏上面
  • data:对象类型,可选属性,附加在消息上的一个数据对象,不会显示在通知栏上,但可在 APP 内部使用该对象。
1
2
3
4
5
6
7
const localNotification = {
  title: 'Test',
  body: 'This is a Test',
  data: {
    foo: 'foo'
  },
};

消息发送

创建好了消息对象后,我们就可以来尝试将它发送,Expo 提供了两种发送方式:立即发送和定时发送。

立即发送

1
2
3
import { Notifications } from 'expo';

Notifications.presentLocalNotificationAsync(localNotification);

调用这个方法后 APP 会马上发送一条消息,然后在手机通知栏上就可以看到。

定时发送

定时发送除了要求消息对象外,还需要时间调度的对象。

1
2
3
4
5
6
import { Notifications } from 'expo';

const schedulingOptions = {
  time: Date.now() + 1000 // 表示一秒后发送消息
};
Notifications.scheduleLocalNotificationAsync(localNotification, schedulingOptions);

上面的示例中会在 1 秒后发送消息,schedulingOptions 的有如下属性:

  • time:可以是 Date 对象,也可以是时间毫秒数,表示什么时候开始发送消息
  • repeat:字符串,可以填的值有'minute'、'hour'、'day'、'week'、'month'、'year',表示是否重复发送消息

远程消息推送

远程消息的发送稍微复杂一些,需要搭建自己的后台服务,流程图如下:

  • 手机通过后台服务提供的 API 进行设备登记
  • 后台服务调用 Expo 后台服务的 API
  • Expo 后台服务往所有登记过的设备上推送消息

下面我们来详细看下每一步的具体操作。

后台服务

我们需要搭建自己的后台服务来提供 API,API 要做的事情就是接收请求中的设备号并保存起来,下面我们用Node.js来写个简单的 API 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 使用 Set 来保存手机设备号,这样保证每个设备号只有一个
// 这里只是简单示例,更好的方案是用数据库保存设备号
const PUSH_TOKENS = new Set();
// 这里使用的是 [Hapi](https://hapijs.com/) 框架
exports.register = (server, options, next) => {
  server.route([
    {
      method: 'POST',
      path: '/users/push-token',
      config: {
        handler: (request, reply) => {
          // token 为请求参数中的设备号
          const { token } = request.payload;
          // 添加到 Set 中
          pushTokens.add(token);
          return reply({ message: `The tokens is ${token}` });
        },
      },
    },
  ]);
  next();
};

例子非常简单,这里提供了一个方法为POST,url 为/users/push-token的 API,请求中必须带有 token 参数。

登记设备

准备好了 API 之后,我们就可以在设备上调用 API 进行设备登记了。

Expo 在Notifications模块中提供了相应的方法来让获取设备号,我们来看一下示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { Permissions, Notifications } from 'expo';

const getToken = async () => {
  // 首先要获取手机允许接收消息的权限
  const { status: existingStatus } = await Permissions.getAsync(
    Permissions.NOTIFICATIONS
  );
  let finalStatus = existingStatus;

  // 如果没有授权则发请求获取
  if (existingStatus !== 'granted') {
    const { status } = await Permissions.askAsync(Permissions.NOTIFICATIONS);
    finalStatus = status;
  }

  // 用户不允许则返回空
  if (finalStatus !== 'granted') return null;

  // 调用 Expo API 来获取设备号
  const token = await Notifications.getExpoPushTokenAsync();
  return token;
}

拿到设备号后,我们就可以将设备号发送到我们的后台服务了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const PUSH_ENDPOINT = 'https://your-server.com/users/push-token';
const pushToken = async () => {
  try {
    // 通过 POST 请求将设备号发送到后台服务
    await fetch(PUSH_ENDPOINT, {
      method: 'POST',
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ token }),
    });
  } catch (e) {
    console.error(`发送设备号异常:${e.message}`);
  }
}

发送消息

最后一步是让 Expo 的后台服务将消息推送给登记过的所有设备,那我们要怎么发送请求给 Expo 的后台服务呢?

其实 Expo 提供了 sdk 包来让我们的后台服务可以与 Expo 的服务器进行通信,sdk 包支持多种语言,方便与各种 Web 服务集成,支持的语言包括Node.jsPythonRubyPHPGolang等,我们以Node.js为例介绍一下我们的后台服务是如何与 Expo 后台服务进行通信的:

  • 首先是要添加 Expo 的 sdk 包
1
yarn add expo-server-sdk
  • 接着调用 Expo 的 API 进行消息推送,下面是示例代码,可以看注释理解相应的代码:
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
import Expo from 'expo-server-sdk';

// 创建 Expo 客户端实例
let expo = new Expo();

// 创建要发送的消息
let messages = [];
// 往所有设备推送消息
for (let pushToken of somePushTokens) {
  // 设备号的格式为:ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]
  // 检查每个设备号格式是否正确
  if (!Expo.isExpoPushToken(pushToken)) {
    console.error(`Push token ${pushToken} is not a valid Expo push token`);
    continue;
  }

  // 构造消息对象
  messages.push({
    to: pushToken,
    sound: 'default',
    body: 'This is a test notification',
    data: { withSome: 'data' },
  })
}

// 将消息转换成块,这样同样内容的消息会进行压缩,Expo 支持且推荐批量推送消息
let chunks = expo.chunkPushNotifications(messages);

(async () => {
  // 分批发送消息
  for (let chunk of chunks) {
    try {
      let receipts = await expo.sendPushNotificationsAsync(chunk);
      console.log(receipts);
    } catch (error) {
      console.error(error);
    }
  }
})();

这样就完成了一次远程消息的推送。

消息接收

当消息发送到手机上的时候,APP 要如何进行消息接收呢?Expo 提供了一个消息监听器,不管是本地的还是远程的消息都可以监听到,我们来看一段简单的代码示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React from 'react';
import {
  Notifications,
} from 'expo';

export default class AppContainer extends React.Component {
  state = {
    notification: {},
  };

  componentDidMount() {
    // 推荐在`componentDidMount`生命周期中添加消息监听器
    // 消息的处理方法作为参数传入`Notifications.addListener`方法中
    this.notificationSubscription = Notifications.addListener(this.handleNotification);
  }

  handleNotification = (notification) => {
    // 这里我们简单地将接收到的消息保存到组件的 state 中
    this.setState({notification: notification});
  };
  render() {...}
}

notification 对象

handleNotification方法中我们会对接收消息进行处理,notification就是接收的消息对象,它有如下属性:

  • remote:true表示这个消息是远程消息,false则是本地消息
  • origin:这个属性有 2 个值——selectedreceivedselected表示是用户从通知栏上点击消息而接收到的,而received则表示是 APP 在打开状态时接收到的。
  • data:附加在消息上的 data 对象

EventSubscription 对象

调用Notifications.addListener后返回的notificationSubscription 是一个EventSubscription对象,它有以下属性和方法:

  • remove():该方法可以用来取消已有的监听器
  • origin:同notification对象的origin属性
  • remote:同notification对象的remote属性
  • data:同notification对象的data属性

总结

消息推送是 APP 的常见需求,市面上也有不少解决方案,但 Expo 相对其他解决方案来说最大的优势就是无需与原生的模块打交道,比如其他消息推送方案可能需要对 android 的源文件进行修改,然后再对 ios 的源文件进行修改,而 Expo 则不需要关心这些,只要在 React Native 中写好代码就可以了。唯一的不足就是需要搭建自己的后台服务,但如果你的 APP 本身就带有后台服务的话,则 Expo 的消息推送方案是你的首要选择。

赞赏

如果文章对您有所帮助,可以转点 ETH 给我喝咖啡😌,钱包二维码:

0x0C9b27c89A61aadb0aEC24CA8949910Cbf77Aa73 qrcode

Comments