You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Expo Notifications推送票据/回执状态正常但未收到通知

Expo Managed Workflow下expo-notifications推送通知无法接收排查

我在Expo Managed Workflow中使用expo-notifications模块发送推送通知,已依据官方文档搭建最小复现项目,但遇到无法接收推送通知的问题:

  • 本地定时通知可正常显示
  • 提取FCM推送令牌直接通过FCM发送也能正常接收
  • 推送票据(Push Ticket)与回执(Receipt)状态均标记为ok,但设备仍收不到通知
  • 当前使用安卓真机上的EAS开发构建版本

相关代码

import { useState, useEffect, useRef } from 'react';
import { Text, View, Button, Platform } from 'react-native';
import * as Device from 'expo-device';
import * as Notifications from 'expo-notifications';
import Constants from 'expo-constants';

Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: true,
    shouldPlaySound: false,
    shouldSetBadge: false,
  }),
});

export default function App() {
  const [expoPushToken, setExpoPushToken] = useState('');
  const [notification, setNotification] = useState(false);
  const notificationListener = useRef();
  const responseListener = useRef();

  useEffect(() => {
    registerForPushNotificationsAsync().then(token => setExpoPushToken(token.data));

    notificationListener.current = Notifications.addNotificationReceivedListener(notification => {
      setNotification(notification);
    });

    responseListener.current = Notifications.addNotificationResponseReceivedListener(response => {
      console.log(response);
    });

    return () => {
      Notifications.removeNotificationSubscription(notificationListener.current);
      Notifications.removeNotificationSubscription(responseListener.current);
    };
  }, []);

  return (
    <View
      style={{
        flex: 1,
        alignItems: 'center',
        justifyContent: 'space-around',
      }}>
      <Text>Your expo push token: {expoPushToken}</Text>
      <View style={{ alignItems: 'center', justifyContent: 'center' }}>
        <Text>Title: {notification && notification.request.content.title} </Text>
        <Text>Body: {notification && notification.request.content.body}</Text>
        <Text>Data: {notification && JSON.stringify(notification.request.content.data)}</Text>
      </View>
      <Button
        title="Press to schedule a notification"
        onPress={async () => {
          await scheduleNotification(); 
        }}
      />
      <Button
        title="Press to send a push notification"
        onPress={async () => {
          await sendPushNotification(expoPushToken);
        }}
      />
    </View>
  );
}

async function scheduleNotification() {
  console.log("pressed")
  await Notifications.scheduleNotificationAsync({
    content: {
      title: "test",  
      body: 'test3',
      data: {},
    },
    trigger: null
  });
}

async function sendPushNotification(expoPushToken) {
  const message = {
    to: expoPushToken,
    sound: 'default',
    title: 'Original Title',
    body: 'And here is the body!',
    data: { someData: 'goes here' },
  };

  const response = await fetch('https://exp.host/--/api/v2/push/send', {
    method: 'POST',
    headers: {
      Accept: 'application/json',
      'Accept-encoding': 'gzip, deflate',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(message),
  })

  const responseData = await response.json()
  console.log("Push Ticket : ", responseData.data)
  fetchReceipt(responseData.data.id)
}

async function fetchReceipt(id) {
  ids = new Array(id)
  const message = {
    ids: ids
  }

  const response = await fetch('https://exp.host/--/api/v2/push/getReceipts', {
    method: 'POST',
    headers: {
      Accept: 'application/json',
      'Accept-encoding': 'gzip, deflate',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(message),
  });

  const responseData = await response.json();
  console.log("Reciept : ", responseData.data)
}

async function registerForPushNotificationsAsync() {
  let token;

  if (Platform.OS === 'android') {
    await Notifications.setNotificationChannelAsync('default', {
      name: 'default',
      importance: Notifications.AndroidImportance.MAX,
      vibrationPattern: [0, 250, 250, 250],
      lightColor: '#FF231F7C',
    });
  }

  if (Device.isDevice) {
    const { status: existingStatus } = await Notifications.getPermissionsAsync();
    let finalStatus = existingStatus;
    if (existingStatus !== 'granted') {
      const { status } = await Notifications.requestPermissionsAsync();
      finalStatus = status;
    }
    if (finalStatus !== 'granted') {
      alert('Failed to get push token for push notification!');
      return;
    }
    token = await Notifications.getExpoPushTokenAsync({
      projectId: Constants.expoConfig.extra.eas.projectId,
    });
    console.log(token);
    const firebaseToken = (await Notifications.getDevicePushTokenAsync()).data;
    console.log("FCM Token : ", firebaseToken)
  } else {
    alert('Must use physical device for Push Notifications');
  }

  return token;
}

日志示例

Push Ticket :  Object {
  "id": "2fbfdd91-53a0-4199-ac32-b53131d72933",
  "status": "ok",
}
Reciept :  Object {
  "2fbfdd91-53a0-4199-ac32-b53131d72933": Object {
    "status": "ok",
  },
}

关键信息

Even if a receipt's status says ok, this doesn't guarantee that the device has received the message; "ok" in a push receipt means that the Android or iOS push notification service successfully received the notification.

但通过FCM控制台直接推送成功,说明设备与FCM的通信无问题,问题应出在Expo推送服务到FCM的转发环节或配置上。

排查方向

  • 检查Expo项目的FCM配置:确认EAS构建时已正确配置google-services.json,项目的app.json/app.config.js中对应的Firebase配置无错误
  • 验证Expo Push Token有效性:确保获取的Expo Push Token与FCM Token绑定到同一设备,可对比设备标识(如Device.deviceId)是否一致
  • 完善推送消息参数:Android设备需要指定channelId,当前代码创建了default频道,但推送消息中未添加该参数,可在message中添加android: { channelId: 'default' }
  • 测试后台接收情况:当前代码的监听仅针对前台,将应用退到后台后再次测试推送
  • 查看Expo控制台推送记录:在Expo Dashboard中查看推送请求的详细日志,是否存在隐藏错误
  • 检查EAS构建配置:确认开发构建已开启推送权限,构建过程中无配置遗漏

内容的提问来源于stack exchange,提问作者Freddie Moore

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 05:45:55