返回

Airship Flutter 推送点击失效(iOS)?看这里解决!

IOS

Airship Flutter 推送通知点击在应用关闭时失效问题分析与解决(iOS)

使用 Urban Airship SDK 的 Flutter 应用中,推送通知点击跳转功能可能在应用完全关闭(从后台滑动退出)后失效。虽然应用在前台或后台时能正确响应通知点击,但在应用关闭后,相关的 onNotificationResponse 事件监听却无法触发,导致通知点击后未能实现预期跳转。这可能让用户体验大打折扣。本篇文章将探讨此问题,并给出解决方案。

问题根源

问题主要在于 iOS 应用的生命周期管理和通知处理机制。当应用被彻底关闭时,系统不会在后台直接运行应用内的代码来响应通知点击。通常,系统会先启动应用,然后应用需要初始化 Airship SDK 并设置相关的监听器,才能接收到通知事件。因此,如果应用在收到通知点击事件时未正确配置这些监听器,则相应的事件回调就不会被执行。

解决方案

以下提供几种解决此问题的方案,确保即使应用完全关闭,也能正常处理 Airship 推送通知点击:

1. 利用 Push.onLaunch

Urban Airship SDK 提供了 onLaunch 方法,该方法会在应用启动时,特别是通过点击通知启动时被调用。可以利用此方法检查是否存在待处理的通知,并进行相应的跳转操作。

  • 实现步骤

    1. main() 函数的 Airship.takeOff 之后,以及在任何推送通知事件监听器设置之前,添加 Airship.push.onLaunch 监听器。
    2. onLaunch 事件处理中,检查 event 对象是否为空。如果不为空,表示是由通知点击启动,则根据通知内容进行页面跳转或其他操作。
    3. 保留原有的onNotificationResponse 监听,这样在前后台收到通知都可以正常处理
  • 代码示例

import 'package:airship_flutter/airship_flutter.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  // Airship 初始化配置
  var config = AirshipConfig(...);
  Airship.takeOff(config);

  // 在 onNotificationResponse 前监听onLaunch
  Airship.push.onLaunch.listen((event) {
     if (event != null) {
       debugPrint("Launched from notification: $event");
       // 这里可以根据通知内容处理跳转逻辑
      // controller.animateTo(1);
     }
    });

  // 原来的 NotificationResponse 监听
  Airship.push.onNotificationResponse.listen((event) {
     debugPrint('Notification Response $event');
       // 移动 controller.animateTo(1)到launch里,在launch 里也做逻辑处理
       //controller.animateTo(1); 
   });

  runApp(MyApp());
}
  • 操作步骤
    1. 修改main.dart文件,按上述示例修改。
    2. 重新编译安装到真机测试。
  • 解释
    • 此方案确保在应用被关闭后由通知启动时,能够正确捕获并处理通知点击事件,并在此时执行跳转等操作。 同时保留onNotificationResponse 处理,确保前台和后台时也可以正常处理跳转。

2. 处理通知的Payload(负载)

另一种策略是在发送通知时,将自定义数据附加在通知的 payload 中,以便应用在启动时读取并解析这些数据,根据自定义数据实现不同的页面跳转。
这可以让处理流程变得更为灵活,特别是需要根据通知中的某些信息来确定跳转目标的时候。

  • 实现步骤

    1. 修改服务端发送通知的 payload。添加自定义键值对。
    2. Airship.push.onLaunchAirship.push.onNotificationResponse 的监听器中,读取 payload 里面的数据,进行相应的路由跳转处理。
  • 代码示例
    发送推送示例 payload (服务端示例, 各推送服务有细微差别):

    {
         "audience": "all",
          "device_types": [
              "ios", "android"
           ],
          "notification": {
              "alert": "你的通知消息内容",
                 "ios":{
                      "extras": {
                         "screen_route": "/details"  
                      }
               }
            },
        "push": {
            "aps":{
            		 "content-available":1 //确保后台处理
               }
        }
    
    }
    

    flutter 代码处理

    import 'package:airship_flutter/airship_flutter.dart';
    import 'package:flutter/material.dart';
    
    
    final GlobalKey<NavigatorState> key = GlobalKey();
    
    // 注意 context 的问题
    void handleRoute (Map? data) {
    
       String? routeName;
    
       if(data != null && data["extras"] != null){
    
         routeName = (data["extras"] as Map)['screen_route'];
       }
    
       if(routeName != null){
    
           // key 在最外层,并且保证有 context
    
          key.currentState?.pushNamed(routeName);
       }
    
    
    }
    void main() {
    WidgetsFlutterBinding.ensureInitialized();
      var config = AirshipConfig(...);
        Airship.takeOff(config);
    
    
      Airship.push.onLaunch.listen((event) {
    
        // 注意 null 判断
           if(event != null && event.notificationResponse != null && event.notificationResponse!.notification.extras != null ){
               debugPrint('Launch from Notification with extras ${event.notificationResponse?.notification.extras}');
               handleRoute(event.notificationResponse?.notification.extras);
         }
    
    
     });
    
    
    Airship.push.onNotificationResponse.listen((event) {
    
      debugPrint('Notification Response $event');
    
      if (event.notification.extras != null){
       handleRoute(event.notification.extras);
      }
    });
    
     runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
        const MyApp({super.key});
    
        @override
      Widget build(BuildContext context) {
             return MaterialApp(
                debugShowCheckedModeBanner: false,
                  navigatorKey: key, // 这里保证 GlobalKey 的有效性
             routes: {
                     '/': (context) => const MyHomePage(),
                        '/details': (context) => const DetailScreen(),
    
                   }
    
                );
           }
      }
    
    class MyHomePage extends StatelessWidget {
     const MyHomePage({super.key});
    @override
      Widget build(BuildContext context) {
        return  Scaffold(appBar: AppBar(title: const Text("home page")));
    
        }
    
      }
    

class DetailScreen extends StatelessWidget {
const DetailScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: const Text("details page")));
}
}

*   **操作步骤** :
    1.  按服务端示例调整通知Payload数据
    2.  修改`main.dart` 文件,添加路由跳转相关逻辑。
    3.  真机测试,检查是否跳转到对应页面
  *   **解释**   *   这种方法提供了一种更为灵活的处理方式。它通过自定义通知数据,可以在任何时候修改页面跳转逻辑而无需改动代码。 注意 context 的有效性,和null判断。


## 额外建议

1.  **代码健壮性** :确保所有可能为空的值在使用前进行检查,避免因为空指针或空值导致的崩溃。
2.  **异常处理** : 在 `Airship` 方法调用以及页面跳转时添加 `try-catch` 代码块,以便捕获异常并进行处理。这对于稳定性和用户体验非常关键。
3.  **测试** :在真实设备上测试确保各项逻辑的正常运行,包括在应用前后台,以及关闭重启后的表现。 使用 postman 或第三方推送服务发送消息。

## 总结

在 iOS 上,处理 Airship 推送通知点击涉及应用生命周期管理和 Airship SDK 事件监听。通过仔细配置  `Airship.push.onLaunch``Airship.push.onNotificationResponse` 事件,同时在通知负载中包含自定义信息,能解决应用完全关闭时无法跳转页面的问题。确保应用在各种情况下都能提供稳定可靠的推送通知响应体验。