MASA MAUI Plugin (十)iOS訊息推送(原生APNS方式)

語言: CN / TW / HK

背景

MAUI的出現,賦予了廣大.Net開發者開發多平臺應用的能力,MAUI 是Xamarin.Forms演變而來,但是相比Xamarin效能更好,可擴充套件性更強,結構更簡單。但是MAUI對於平臺相關的實現並不完整。所以MASA團隊開展了一個實驗性專案,意在對微軟MAUI的補充和擴充套件

專案地址https://github.com/BlazorComponent/MASA.Blazor/tree/feature/Maui/src/Masa.Blazor.Maui.Plugin

每個功能都有單獨的demo演示專案,考慮到app安裝檔案體積(雖然MAUI已經整合裁剪功能,但是該功能對於程式碼本身有影響),屆時每一個功能都會以單獨的nuget包的形式提供,方便測試,現在專案才剛剛開始,但是相信很快就會有可以交付的內容啦。

前言

本系列文章面向移動開發小白,從零開始進行平臺相關功能開發,演示如何參考平臺的官方文件使用MAUI技術來開發相應功能。

介紹

Apple 推送通知服務(Apple Push Notification service),簡稱 APNs。與之前Android使用個推不同,由於APNs國內可用,所以我們可以直接使用APNs來實現遠端訊息推送,不依賴其他第三方元件和服務。我們這裡推送使用的是p8證書,p8證書相對p12證書來講,更靈活,而且沒有p12證書有效期1年的限制。

一、實現方式

一、申請p8證書

https://developer.apple.com/

1、登入開發者中心,點選右上角Account,找到Keys管理。

在這裡插入圖片描述 2、在頂部點選+號。 在這裡插入圖片描述 3、勾選APNs服務,並輸入Key名稱,下一步Continue。 在這裡插入圖片描述 4、點選Register。 在這裡插入圖片描述 5、記錄Key ID,並下載證書,得到AuthKey_xxxxxxxxxx.p8證書檔案。 在這裡插入圖片描述 6、獲取Team ID,Account介面點選Membership details 在這裡插入圖片描述 在這裡插入圖片描述

二、編寫MAUI實現程式碼

參考官方文件:https://developer.apple.com/documentation/usernotifications/registering_your_app_with_apns

1、首先需要先開啟App訊息推送的能力

我們新建一個iOSPush 資料夾,並在資料夾下面新建MauiBlazor專案iOSPushSample (由於受打包長度影響,專案名稱和資料夾名稱,我這裡儘量簡短。路徑長度超過255會導致編譯時提示部分檔案找不到。) 我們找到Platforms->iOS->Info.plist檔案,雙擊用預設的iOS清單編輯器開啟,勾選“啟用後臺模式”和“遠端通知”。這項操作會在Info.plist檔案中新增如下資訊:

	<key>UIBackgroundModes</key>
	<array>
		<string>remote-notification</string>
	</array>

在這裡插入圖片描述 在開發環境,你需要以下的額外配置。

如果你的專案已經使用了Entitlements.plist檔案,雙擊開啟改檔案,並勾選“推送通知”。 在這裡插入圖片描述 如果沒有這個檔案,那麼新建這個文字檔案,內容如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>aps-environment</key>
	<string>development</string>
</dict>
</plist>

注意:這項配置的目的是在development環境支援推送,如果在專案釋出的時候,MAUI不會自動清除,需要手動關閉或註釋掉這項配置,否則會報錯。

2、編寫實現程式碼

參考文件 https://developer.apple.com/documentation/usernotifications/registering_your_app_with_apns

我們首先需要將App註冊到APNs並獲取唯一的device token。在iOS中需要呼叫UIApplication 中通過registerForRemoteNotifications() 方法,實現註冊,如果註冊成功,就可以在delegateapplication(_:didRegisterForRemoteNotificationsWithDeviceToken:) 方法中獲取device token。如果註冊失敗,會觸發application(_:didFailToRegisterForRemoteNotificationsWithError:) 方法。

Swift程式碼
func application(_ application: UIApplication,
           didFinishLaunchingWithOptions launchOptions:
           [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
   // Override point for customization after application launch.you’re         
   UIApplication.shared.registerForRemoteNotifications()
   return true
}

func application(_ application: UIApplication,
            didRegisterForRemoteNotificationsWithDeviceToken 
                deviceToken: Data) {
   self.sendDeviceTokenToServer(data: deviceToken)
}

func application(_ application: UIApplication,
            didFailToRegisterForRemoteNotificationsWithError 
                error: Error) {
   // Try again later.
}

我們開始編寫程式碼

向APNs註冊裝置

首先在iOS->AppDelegate.cs 類中,重寫FinishedLaunching 方法,在應用啟動之後進行註冊。

    [Register("AppDelegate")]
    public class AppDelegate : MauiUIApplicationDelegate
    {
        protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
        public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
        {
            UNUserNotificationCenter center = UNUserNotificationCenter.Current;

            var options = UNAuthorizationOptions.Alert | UNAuthorizationOptions.Sound | UNAuthorizationOptions.CriticalAlert;
             // Request notification permissions from the user
            center.RequestAuthorization(options,
                (bool success, NSError error) =>
                {
                // Handle approval
                }
            );
            UIApplication.SharedApplication.RegisterForRemoteNotifications();
            return base.FinishedLaunching(application, launchOptions);
        }
    }

應用啟動時應立即請求通知許可權,方法是將以下程式碼新增到FinishedLaunchingAppDelegate 所需通知型別 (UNAuthorizationOptions) 的方法:

UNUserNotificationCenter 僅適用於 iOS 10+,但是考慮到基本沒人用低於10的版本了,這裡我們就不做版本檢查了

使用者可以批准應用的三個不同級別的通知請求: 橫幅顯示-Alert 。 聲音警報-Sound 。 對應用圖示進行錯誤設定-CriticalAlert。

請求許可權結束後我們通過 UIApplication.SharedApplication.RegisterForRemoteNotifications(); 向APNs註冊。 註冊成功後我們通過application:didRegisterForRemoteNotificationsWithDeviceToken: 方法獲取device token,但是由於這個方法是在UIApplication下,但是我們的AppDelegate是繼承自 MauiUIApplicationDelegate ,預設沒有這個方法,我們可以通過Export特性,匯出我們需要的方法,繼續在AppDelegate中新增

        [Export("application:didFailToRegisterForRemoteNotificationsWithError:")]
        public void FailedToRegisterForRemoteNotifications(UIKit.UIApplication application, NSError error)
        {
            Console.WriteLine("FailedToRegisterForRemoteNotifications");
        }
        
        [Export("application:didRegisterForRemoteNotificationsWithDeviceToken:")]
        public void RegisteredForRemoteNotifications(UIKit.UIApplication application,
            Foundation.NSData deviceToken)
        {
            var token = ExtractToken(deviceToken);
            Preferences.Default.Set("PushToken", token);
            Console.WriteLine(token);
        }

        private string ExtractToken(NSData deviceToken)
        {
            if (deviceToken.Length == 0)
                return null;
            var result = new byte[deviceToken.Length];
            System.Runtime.InteropServices.Marshal.Copy(deviceToken.Bytes, result, 0, (int)deviceToken.Length);
            return BitConverter.ToString(result).Replace("-", "");
        }

ExtractToken是將返回的deviceToken解析為推送可用的字串。 我們通過Preferences.Default.Set將token儲存起來,方便在登入等業務中使用。

接收遠端推送

接收推送的訊息是通過**application:didReceiveRemoteNotification:fetchCompletionHandler:**實現的

        [Export("application:didReceiveRemoteNotification:fetchCompletionHandler:")]
        public void DidReceiveRemoteNotification(UIKit.UIApplication application, NSDictionary userInfo, Action<UIKit.UIBackgroundFetchResult> completionHandler)
        {
            foreach (var item in userInfo)
            {
                var alertMsg = ((NSDictionary)item.Value)["alert"];
                Console.WriteLine($"alertMsg:{alertMsg}");
            }
            Console.WriteLine("DidReceiveRemoteNotification");
        }

其實我們在方法內部不需要寫任何程式碼,就可以實現基本的推送功能。但如果想處理推送過來的訊息,可以通過NSDictionary型別userInfo中拿到。這裡示例從alert中拿到具體的訊息內容,並列印。

三、編寫演示程式碼

我們修改Index.razor,通過點選按鈕獲取裝置Token

@page "/"

<button @onclick="GetDeviceToken">Get Device Token</button>
<text>@deviceToken</text>
@code
{
    private string deviceToken { get; set; }
    private void GetDeviceToken()
    {
        deviceToken= Preferences.Default.Get("PushToken",string.Empty);
    }
}

四、服務端測試

我們可以通過個推的線上測試工具,配置好p8證書和其他引數。 在這裡插入圖片描述 我這裡寫了一個命令列的測試程式,這裡使用了第三方的PushNotifications.Server包

using PushNotifications.Server.Apple;

Console.WriteLine("Hello, World!");
IApnsClient apnsClient = new ApnsClient(new ApnsJwtOptions
{
    CertContent = "-----BEGIN PRIVATE KEY-----\r\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\r\n-----END PRIVATE KEY-----",
    KeyId = "LUxxxxxxxx",
    TeamId = "V4xxxxxxxx",
    BundleId = "com.iOSPush.Sample",
    UseSandbox = true
});

var apnsRequest = new ApnsRequest(ApplePushType.Alert)
    .AddToken("47006118F8xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
    .AddSound()
    .AddAlert("MASA", $"Message @ {DateTime.Now}", $"Message @ {DateTime.Now}");
var apnsResponse = await apnsClient.SendAsync(apnsRequest);
Console.ReadKey();

五、演示效果

請新增圖片描述

可以看出,應用開啟的狀態不會收到推送,後臺執行或者劃掉關閉都可以收到通知。

如果你對我們的 MASA Framework 感興趣,無論是程式碼貢獻、使用、提 Issue,歡迎聯絡我們