MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Flutter平台特定通知功能:实现iOS与Android的通知系统

2023-07-145.0k 阅读

Flutter平台特定通知功能:实现iOS与Android的通知系统

一、Flutter通知概述

Flutter作为一款跨平台的移动应用开发框架,在通知功能的实现上为开发者提供了强大且灵活的解决方案。通知对于移动应用而言至关重要,它能在应用处于后台甚至关闭状态时,有效地向用户传达重要信息,提升用户的参与度和留存率。

Flutter本身提供了一些通用的通知插件,如flutter_local_notifications,它允许开发者创建本地通知,而无需过多关注底层平台的差异。然而,在实际开发中,针对iOS和Android平台的特性,开发者可能需要实现一些特定的通知功能,以提供更原生、更符合用户习惯的体验。

二、iOS通知系统的集成与定制

(一)iOS通知权限

在iOS上,应用需要请求用户授权才能发送通知。Flutter中,可以使用flutter_local_notifications插件来实现权限请求。首先,在pubspec.yaml文件中添加依赖:

dependencies:
  flutter_local_notifications: ^8.0.0+4

然后,在代码中初始化并请求权限:

import 'package:flutter_local_notifications/flutter_local_notifications.dart';

final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();

Future<void> requestIOSPermissions() async {
  final IOSFlutterLocalNotificationsSettings initializationSettingsIOS =
      IOSFlutterLocalNotificationsSettings(
    requestAlertPermission: true,
    requestBadgePermission: true,
    requestSoundPermission: true,
  );

  final InitializationSettings initializationSettings = InitializationSettings(
    iOS: initializationSettingsIOS,
  );
  await flutterLocalNotificationsPlugin.initialize(initializationSettings);

  final bool? granted = await flutterLocalNotificationsPlugin
     .resolvePlatformSpecificImplementation<
          IOSFlutterLocalNotificationsPlugin>()
     ?.requestPermissions(
        alert: true,
        badge: true,
        sound: true,
      );
  print('Notification permissions granted: $granted');
}

这段代码首先配置了iOS通知的初始化设置,包括请求提醒、徽章和声音权限。然后初始化flutter_local_notifications插件,并请求通知权限。

(二)iOS通知样式定制

  1. 基本通知样式 iOS上的基本通知样式相对简洁,开发者可以设置标题、正文、附件等。使用flutter_local_notifications插件发送基本通知:
Future<void> showIOSBasicNotification() async {
  const AndroidNotificationDetails androidPlatformChannelSpecifics =
      AndroidNotificationDetails(
    'your channel id',
    'your channel name',
    channelDescription: 'your channel description',
    importance: Importance.max,
    priority: Priority.high,
  );
  const IOSNotificationDetails iOSPlatformChannelSpecifics =
      IOSNotificationDetails();
  const NotificationDetails platformChannelSpecifics = NotificationDetails(
    android: androidPlatformChannelSpecifics,
    iOS: iOSPlatformChannelSpecifics,
  );
  await flutterLocalNotificationsPlugin.show(
    0,
    'iOS Basic Notification',
    'This is a basic iOS notification',
    platformChannelSpecifics,
  );
}

这里创建了AndroidNotificationDetailsIOSNotificationDetails,并将它们组合成NotificationDetails,然后使用show方法发送通知。

  1. 富文本通知样式 iOS支持富文本通知,允许在通知中展示图片、视频等附件。要实现富文本通知,需要先将附件下载到本地,然后在通知中引用。
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';

Future<void> showIOSRichNotification() async {
  final String imageUrl = 'https://example.com/image.jpg';
  final Directory appDocDir = await getApplicationDocumentsDirectory();
  final String filePath = '${appDocDir.path}/image.jpg';
  final http.Response response = await http.get(Uri.parse(imageUrl));
  final File file = File(filePath);
  await file.writeAsBytes(response.bodyBytes);

  final IOSNotificationAttachment attachment =
      await IOSNotificationAttachment.fromPath(filePath);

  const AndroidNotificationDetails androidPlatformChannelSpecifics =
      AndroidNotificationDetails(
    'your channel id',
    'your channel name',
    channelDescription: 'your channel description',
    importance: Importance.max,
    priority: Priority.high,
  );
  const IOSNotificationDetails iOSPlatformChannelSpecifics =
      IOSNotificationDetails(attachments: [attachment]);
  const NotificationDetails platformChannelSpecifics = NotificationDetails(
    android: androidPlatformChannelSpecifics,
    iOS: iOSPlatformChannelSpecifics,
  );
  await flutterLocalNotificationsPlugin.show(
    1,
    'iOS Rich Notification',
    'This is a rich iOS notification with an image',
    platformChannelSpecifics,
  );
}

这段代码从网络下载一张图片,保存到本地,然后创建IOSNotificationAttachment并将其添加到通知中,从而实现富文本通知。

(三)处理iOS通知交互

iOS通知支持用户与通知进行交互,如点击按钮、回复等。以点击通知跳转到应用内特定页面为例:

  1. 注册通知处理回调main.dart中初始化时注册回调:
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
      FlutterLocalNotificationsPlugin();

  final IOSFlutterLocalNotificationsSettings initializationSettingsIOS =
      IOSFlutterLocalNotificationsSettings(
    requestAlertPermission: true,
    requestBadgePermission: true,
    requestSoundPermission: true,
  );

  final InitializationSettings initializationSettings = InitializationSettings(
    iOS: initializationSettingsIOS,
  );
  await flutterLocalNotificationsPlugin.initialize(initializationSettings,
      onSelectNotification: (String? payload) async {
    if (payload != null) {
      print('Notification payload: $payload');
      // 跳转到特定页面
      Navigator.push(
        context,
        MaterialPageRoute(builder: (context) => SpecificPage(payload: payload)),
      );
    }
  });

  runApp(MyApp());
}
  1. 发送带有payload的通知
Future<void> sendIOSNotificationWithPayload() async {
  const AndroidNotificationDetails androidPlatformChannelSpecifics =
      AndroidNotificationDetails(
    'your channel id',
    'your channel name',
    channelDescription: 'your channel description',
    importance: Importance.max,
    priority: Priority.high,
  );
  const IOSNotificationDetails iOSPlatformChannelSpecifics =
      IOSNotificationDetails();
  const NotificationDetails platformChannelSpecifics = NotificationDetails(
    android: androidPlatformChannelSpecifics,
    iOS: iOSPlatformChannelSpecifics,
  );
  await flutterLocalNotificationsPlugin.show(
    2,
    'iOS Notification with Payload',
    'This is a notification with a payload',
    platformChannelSpecifics,
    payload: 'page_1',
  );
}

这里在发送通知时添加了payload,当用户点击通知时,会触发onSelectNotification回调,根据payload跳转到相应的页面。

三、Android通知系统的集成与定制

(一)Android通知渠道

Android从Oreo(API 26)开始引入通知渠道的概念。每个通知都必须分配到一个渠道,不同渠道可以设置不同的通知行为,如声音、震动等。

  1. 创建通知渠道
import 'package:flutter_local_notifications/flutter_local_notifications.dart';

Future<void> createAndroidNotificationChannel() async {
  final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
      FlutterLocalNotificationsPlugin();

  const AndroidNotificationChannel channel = AndroidNotificationChannel(
    'high_importance_channel',
    'High Importance Notifications',
    description: 'This channel is used for important notifications.',
    importance: Importance.high,
  );

  await flutterLocalNotificationsPlugin
     .resolvePlatformSpecificImplementation<
          AndroidFlutterLocalNotificationsPlugin>()
     ?.createNotificationChannel(channel);
}
  1. 使用通知渠道发送通知
Future<void> showAndroidNotification() async {
  await createAndroidNotificationChannel();

  const AndroidNotificationDetails androidPlatformChannelSpecifics =
      AndroidNotificationDetails(
    'high_importance_channel',
    'High Importance Notifications',
    channelDescription: 'This channel is used for important notifications.',
    importance: Importance.high,
    priority: Priority.high,
  );
  const NotificationDetails platformChannelSpecifics = NotificationDetails(
    android: androidPlatformChannelSpecifics,
  );
  await flutterLocalNotificationsPlugin.show(
    3,
    'Android Notification',
    'This is an Android notification',
    platformChannelSpecifics,
  );
}

这段代码首先创建了一个高重要性的通知渠道,然后使用该渠道发送通知。

(二)Android通知样式定制

  1. 基本通知样式 Android的基本通知样式与iOS有所不同,开发者可以设置标题、正文、小图标等。
Future<void> showAndroidBasicNotification() async {
  const AndroidNotificationDetails androidPlatformChannelSpecifics =
      AndroidNotificationDetails(
    'your channel id',
    'your channel name',
    channelDescription: 'your channel description',
    importance: Importance.max,
    priority: Priority.high,
    icon: '@mipmap/ic_launcher',
    largeIcon: DrawableResourceAndroidBitmap('@mipmap/ic_launcher'),
  );
  const NotificationDetails platformChannelSpecifics = NotificationDetails(
    android: androidPlatformChannelSpecifics,
  );
  await flutterLocalNotificationsPlugin.show(
    4,
    'Android Basic Notification',
    'This is a basic Android notification',
    platformChannelSpecifics,
  );
}

这里设置了通知的小图标icon和大图标largeIcon,使通知更具辨识度。

  1. 扩展通知样式 Android支持扩展通知样式,如大文本样式、大图片样式等。以大图片样式为例:
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'dart:io';

Future<void> showAndroidBigPictureNotification() async {
  final String imageUrl = 'https://example.com/big_image.jpg';
  final Directory appDocDir = await getApplicationDocumentsDirectory();
  final String filePath = '${appDocDir.path}/big_image.jpg';
  final http.Response response = await http.get(Uri.parse(imageUrl));
  final File file = File(filePath);
  await file.writeAsBytes(response.bodyBytes);

  final AndroidNotificationDetails androidPlatformChannelSpecifics =
      AndroidNotificationDetails(
    'your channel id',
    'your channel name',
    channelDescription: 'your channel description',
    importance: Importance.max,
    priority: Priority.high,
    styleInformation: BigPictureStyleInformation(
      FilePathAndroidBitmap(filePath),
      hideExpandedLargeIcon: true,
      contentTitle: 'Big Picture Notification',
      summaryText: 'This is a big picture notification on Android',
    ),
  );
  const NotificationDetails platformChannelSpecifics = NotificationDetails(
    android: androidPlatformChannelSpecifics,
  );
  await flutterLocalNotificationsPlugin.show(
    5,
    'Android Big Picture Notification',
    'This is a big picture Android notification',
    platformChannelSpecifics,
  );
}

这段代码下载一张图片,然后使用BigPictureStyleInformation将其设置为通知的大图片样式。

(三)处理Android通知交互

Android通知同样支持用户交互,如点击通知跳转到应用内特定页面,或者添加自定义按钮。以添加自定义按钮为例:

  1. 创建通知并添加按钮
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter/material.dart';

Future<void> showAndroidNotificationWithAction() async {
  const AndroidNotificationDetails androidPlatformChannelSpecifics =
      AndroidNotificationDetails(
    'your channel id',
    'your channel name',
    channelDescription: 'your channel description',
    importance: Importance.max,
    priority: Priority.high,
    actions: [
      AndroidNotificationAction(
        'action_id_1',
        'Button 1',
        showsUserInterface: true,
      ),
      AndroidNotificationAction(
        'action_id_2',
        'Button 2',
        showsUserInterface: true,
      ),
    ],
  );
  const NotificationDetails platformChannelSpecifics = NotificationDetails(
    android: androidPlatformChannelSpecifics,
  );
  await flutterLocalNotificationsPlugin.show(
    6,
    'Android Notification with Action',
    'This is a notification with action buttons',
    platformChannelSpecifics,
  );
}
  1. 处理按钮点击事件main.dart中注册回调处理按钮点击:
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
      FlutterLocalNotificationsPlugin();

  const AndroidNotificationChannel channel = AndroidNotificationChannel(
    'high_importance_channel',
    'High Importance Notifications',
    description: 'This channel is used for important notifications.',
    importance: Importance.high,
  );

  await flutterLocalNotificationsPlugin
     .resolvePlatformSpecificImplementation<
          AndroidFlutterLocalNotificationsPlugin>()
     ?.createNotificationChannel(channel);

  const InitializationSettings initializationSettings = InitializationSettings(
    android: AndroidInitializationSettings('@mipmap/ic_launcher'),
  );
  await flutterLocalNotificationsPlugin.initialize(initializationSettings,
      onSelectNotification: (String? payload) async {
    if (payload != null) {
      print('Notification payload: $payload');
    }
  }, onDidReceiveNotificationResponse: (NotificationResponse details) {
    final String? payload = details.payload;
    if (details.notificationResponseType ==
        NotificationResponseType.action) {
      if (details.actionId == 'action_id_1') {
        print('Button 1 clicked');
      } else if (details.actionId == 'action_id_2') {
        print('Button 2 clicked');
      }
    }
  });

  runApp(MyApp());
}

这里在发送通知时添加了两个按钮,然后通过onDidReceiveNotificationResponse回调来处理按钮的点击事件。

四、跨平台通知的统一管理

虽然iOS和Android有各自独特的通知系统,但在Flutter开发中,可以通过封装来实现跨平台的统一管理。

  1. 创建通知服务类
import 'package:flutter_local_notifications/flutter_local_notifications.dart';

class NotificationService {
  static final NotificationService _instance = NotificationService._internal();
  factory NotificationService() => _instance;
  final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
      FlutterLocalNotificationsPlugin();

  NotificationService._internal();

  Future<void> initialize() async {
    const AndroidNotificationChannel channel = AndroidNotificationChannel(
      'high_importance_channel',
      'High Importance Notifications',
      description: 'This channel is used for important notifications.',
      importance: Importance.high,
    );

    await flutterLocalNotificationsPlugin
       .resolvePlatformSpecificImplementation<
            AndroidFlutterLocalNotificationsPlugin>()
       ?.createNotificationChannel(channel);

    final IOSFlutterLocalNotificationsSettings initializationSettingsIOS =
        IOSFlutterLocalNotificationsSettings(
      requestAlertPermission: true,
      requestBadgePermission: true,
      requestSoundPermission: true,
    );

    final InitializationSettings initializationSettings = InitializationSettings(
      android: AndroidInitializationSettings('@mipmap/ic_launcher'),
      iOS: initializationSettingsIOS,
    );
    await flutterLocalNotificationsPlugin.initialize(initializationSettings,
        onSelectNotification: (String? payload) async {
      if (payload != null) {
        print('Notification payload: $payload');
      }
    }, onDidReceiveNotificationResponse: (NotificationResponse details) {
      final String? payload = details.payload;
      if (details.notificationResponseType ==
          NotificationResponseType.action) {
        if (details.actionId == 'action_id_1') {
          print('Button 1 clicked');
        } else if (details.actionId == 'action_id_2') {
          print('Button 2 clicked');
        }
      }
    });
  }

  Future<void> showNotification(
      {required int id,
      required String title,
      required String body,
      String? payload}) async {
    const AndroidNotificationDetails androidPlatformChannelSpecifics =
        AndroidNotificationDetails(
      'high_importance_channel',
      'High Importance Notifications',
      channelDescription: 'This channel is used for important notifications.',
      importance: Importance.high,
      priority: Priority.high,
    );
    const IOSNotificationDetails iOSPlatformChannelSpecifics =
        IOSNotificationDetails();
    const NotificationDetails platformChannelSpecifics = NotificationDetails(
      android: androidPlatformChannelSpecifics,
      iOS: iOSPlatformChannelSpecifics,
    );
    await flutterLocalNotificationsPlugin.show(
      id,
      title,
      body,
      platformChannelSpecifics,
      payload: payload,
    );
  }
}
  1. 使用通知服务类 在需要发送通知的地方,使用NotificationService类:
import 'package:flutter/material.dart';
import 'package:your_project/notification_service.dart';

class HomePage extends StatelessWidget {
  final NotificationService notificationService = NotificationService();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Notification Example'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () async {
            await notificationService.initialize();
            await notificationService.showNotification(
              id: 7,
              title: 'Cross - Platform Notification',
              body: 'This is a cross - platform notification',
              payload: 'home_page',
            );
          },
          child: Text('Send Notification'),
        ),
      ),
    );
  }
}

通过这种方式,开发者可以在不同平台上统一管理通知的初始化、发送以及交互处理,提高代码的可维护性和复用性。

五、常见问题与解决方法

(一)通知不显示

  1. 原因
    • 权限问题:在iOS上未请求到通知权限,或者在Android上未创建正确的通知渠道。
    • 配置错误:通知的参数设置不正确,如在Android上未设置正确的图标,或者在iOS上未配置合适的通知样式。
  2. 解决方法
    • iOS权限:仔细检查权限请求代码,确保用户授予了通知权限。可以在设备的设置中手动检查应用的通知权限。
    • Android渠道:确认通知渠道的创建代码正确无误,并且在发送通知时使用了正确的渠道ID。
    • 参数配置:检查通知的图标、标题、正文等参数是否设置正确。在Android上,图标资源路径要确保正确,如@mipmap/ic_launcher等。

(二)通知点击无响应

  1. 原因
    • 回调未注册:在Flutter中,没有正确注册通知点击的回调函数,导致无法处理用户的点击操作。
    • 上下文问题:在回调函数中使用了不正确的上下文,导致无法进行页面跳转等操作。
  2. 解决方法
    • 注册回调:在flutter_local_notifications插件初始化时,正确注册onSelectNotificationonDidReceiveNotificationResponse回调函数。
    • 上下文处理:在需要进行页面跳转等操作时,确保使用了正确的上下文。可以将全局的NavigatorKey传递到回调函数中,以便进行页面导航。

(三)iOS和Android通知样式不一致

  1. 原因
    • 平台特性:iOS和Android本身的通知样式存在差异,即使使用相同的插件,也可能因为平台默认样式的不同而看起来不一致。
    • 定制不足:开发者没有针对不同平台进行足够的样式定制,导致通知在不同平台上未能达到预期的效果。
  2. 解决方法
    • 了解平台特性:深入了解iOS和Android通知样式的差异,根据不同平台的特点进行定制。例如,iOS的富文本通知和Android的大图片样式等,要分别按照平台的规范进行实现。
    • 精细定制:对iOS和Android的通知样式进行精细定制,通过设置不同的参数,如iOS的attachments和Android的styleInformation等,使通知在不同平台上都能提供良好的用户体验。

通过对以上内容的深入学习和实践,开发者能够在Flutter应用中有效地实现iOS和Android平台特定的通知功能,为用户提供更加丰富、个性化的通知体验。同时,通过跨平台的统一管理和对常见问题的解决,能够提升应用的稳定性和用户满意度。