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

Ionic iOS上传TestFlight后Xcode调试Push Notification失效问题

问题现象

我已成功实现Ionic iOS端的Push Notification功能,但将应用上传发布至TestFlight后出现异常:TestFlight环境安装的应用可正常接收推送,此前运行正常的Xcode调试构建版本却无法接收推送。

基础配置与环境信息

我使用Cordova 9集成Firebase FCM实现推送能力,配置的Apple相关证书如下:

  • Apple Sandbox Push Services
  • Distribution Managed
  • iOS Distribution
  • Development

当前项目运行环境信息:

Ionic:

   ionic (Ionic CLI)             : 4.12.0
   Ionic Framework               : @ionic/angular 5.2.3
   @angular-devkit/build-angular : 0.901.11
   @angular-devkit/schematics    : 9.1.15
   @angular/cli                  : 9.1.15
   @ionic/angular-toolkit        : 2.2.0

Cordova:

   cordova (Cordova CLI) : 8.1.2 (cordova-lib@8.1.1)
   Cordova Platforms     : android 7.1.4, browser 5.0.4, ios 6.1.0
   Cordova Plugins       : cordova-plugin-ionic-keyboard 2.2.0, cordova-plugin-ionic-webview 5.0.0, (and 28 other plugins)

System:

   ios-sim : 8.0.2
   NodeJS  : v10.14.1 (/usr/local/bin/node)
   npm     : 7.21.1
   OS      : macOS Monterey
   Xcode   : Xcode 13.4 Build version 13F17a
相关实现代码

服务端推送逻辑(PHP)

function send_notification ($token, $message, $title)
{
    $path_to_firebase_cm = 'https://fcm.googleapis.com/fcm/send';
        
    $fields = array(
        'to' => $token,
        'notification' => array('title' => $title, 'body' => $message, 'click_action' => 'FCM_PLUGIN_ACTIVITY'),
        'data' => array('message' => $message, 'title' => $title),
        'sound' => 'default'
    );

    $headers = array(
        'Authorization:key=AAAAf.............'
    );      
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $path_to_firebase_cm); 
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 ); 
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($fields));
    $result = curl_exec($ch);      
    curl_close($ch);
    return $result;
}

前端推送逻辑(app.component.ts)

{
    this.backgroundMode.enable();
    this.badge.set(this.notiunread);

    this.initializeApp();
}

initializeApp() {
    this.platform.ready().then(() => {
      this.statusBar.styleBlackTranslucent();
      this.splashScreen.hide();

        const SigninData = JSON.parse(localStorage.getItem("signindata"));
        if(SigninData!=null){
            console.log("have data");
            this.router.navigate(["/tabs"], { skipLocationChange: true });
        } else {
            console.log("no data");
            this.router.navigate(["/landing"], { skipLocationChange: true });
        }



    //  this.fcm.subscribeToTopic('marketing');
      this.fcm.getToken().then(token => {
        console.log(token);
      }, (err) => {
            console.log(err);
        });

      // ionic push notification example
      this.fcm.onNotification().subscribe(data => {
        console.log(data);
        if (data.wasTapped) {
          console.log('Received in background');
          this.router.navigate(['/tabs/tab1/notification']);
        } else {
          this.backgroundMode.enable();
          this.badge.set(this.notiunread);
          console.log('Received in foreground');
          this.call(data);
        }
      });

      // refresh the FCM token
      this.fcm.onTokenRefresh().subscribe(token => {
        console.log(token);
      }, (err) => {
            console.log(err);
        });



    });

    this.providersservices.getObservable().subscribe((data) => {
        console.log('Data received', data);
        this.pic = data.pic;
        this.name = data.name;
        this.phone = data.number;
    });

    const SigninData = JSON.parse(localStorage.getItem("signindata"));
    if(SigninData!=null){
        const SigninData = JSON.parse(localStorage.getItem("signindata"));
        this.userData.id=SigninData.mem_id;
        this.userData.token=SigninData.token;
        this.userData.plid = "7";
        this.providersservices.postData(this.userData, "memberencrypt").subscribe((result) =>{
            this.data = result;
            this.items = this.data.data;
            this.code = this.items.mem_id;
            console.log(result);
        }, (err) => {
            console.log(JSON.stringify(err));
        });
    }


 this.providersservices.postData(this.userData, "badgenotification").subscribe((result) =>{
    this.data3 = result;
    this.items3 = this.data3.data;
    this.notiunread = this.items3.length;
    this.backgroundMode.enable();
      console.log(this.notiunread);
    this.badge.set(this.notiunread);

    if(Array.isArray(this.items3))
      this.exist3 = this.items3[0].exist;
    else
      this.exist3 = this.items3.exist;
    console.log(result);
  }, (err) => {
    console.log(JSON.stringify(err));
  });


    this.providersservices.postData(this.userData, "pointshare").subscribe((result) =>{
        this.data1 = result;
        this.items1 = this.data1.data;
        this.point = this.items1.pl_value;
        console.log(result);
    }, (err) => {
        console.log(JSON.stringify(err));
    });

    if(this.pic==undefined&&this.name==undefined&&this.name==undefined){
        const SigninData = JSON.parse(localStorage.getItem("signindata"));
        if(SigninData!=null){
            this.pic = SigninData.mem_photo;
            this.name = SigninData.mem_name;
            this.phone = SigninData.mem_phonetcode + SigninData.mem_phone;
        }
    }
    this.menuCtrl.enable(false, 'custom');



{
    //this.translate.addLangs(['en', 'ml']);
    //this.translate.setDefaultLang('en');
 }

}
问题根因与修复方案

核心原因

该问题由iOS APNs推送环境的强隔离机制导致:

  • Xcode调试构建的应用走APNs沙盒(Sandbox)环境,使用Development签名证书,设备生成的是沙盒环境专属的FCM推送Token
  • TestFlight、App Store安装的正式包走APNs生产(Production)环境,使用Distribution签名证书,设备生成的是生产环境专属的FCM推送Token
    两类Token完全不互通。安装TestFlight版本后,FCM会将对应设备的旧沙盒Token标记为失效、替换为新的生产Token,此时再用旧的沙盒Token发推送,自然无法送达调试版应用。

分步修复

  1. 分环境管理推送Token
    不要用同一条用户记录混存不同环境的推送Token。给上报的Token增加环境标识:

    • 调试阶段测试时,仅向标记为沙盒环境的Token发推送
    • 发布TestFlight/正式包时,仅向标记为生产环境的Token发推送
      同一个设备先后安装调试版、正式版时,要根据上报的环境类型分别存储对应Token,不要互相覆盖。
  2. 修正服务端推送参数错误
    当前代码存在两处参数问题,直接影响推送送达率:

    • sound字段层级错误:必须放在notification对象内部才能在iOS端触发提示音,写在最外层无效
    • 缺少高优先级标识:iOS端只有高优先级推送才能保证在后台/锁屏状态下稳定送达
      修正后的$fields配置参考:
    $fields = array(
        'to' => $token,
        'priority' => 'high',
        'notification' => array(
            'title' => $title, 
            'body' => $message, 
            'click_action' => 'FCM_PLUGIN_ACTIVITY',
            'sound' => 'default'
        ),
        'data' => array('message' => $message, 'title' => $title),
    );
    
  3. 校验Xcode调试包签名与能力配置
    打开Xcode项目逐项检查Debug配置:

    • 选中项目Target → Signing & Capabilities,确认Push Notifications能力已正常添加
    • 确认Debug构建的APS Environment值为development,Release构建的APS Environment值为production
    • Debug包必须使用Development证书签名,禁止误用Distribution证书签名调试版本
  4. 重新获取调试版有效Token
    卸载测试设备上原有调试版、TestFlight版应用,重新通过Xcode安装Debug包,从控制台获取最新的沙盒环境FCM Token,单独使用该Token测试调试版推送,不要和正式环境Token混用。

  5. Firebase证书校验
    登录Firebase控制台,确认iOS应用下同时上传了有效的APNs沙盒证书和生产证书,若仅上传生产证书,所有沙盒环境的推送请求会被FCM直接拒绝。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 13:54:25