消息通知是最常用的應用功能之一了,但是由於平台的差異,IOS Android 以及 Windows 都有其特殊性,Android開發者在國內常常都是使用三方的一些推送服務,或者是使用自建的服務器為應用提供推送服務,方式多種多樣這里就不多闡述了,對比在國內可以使用的兩個推送系統 IOS 和 Windows 對比來說,經歷了 WindowsPhone 7 / 7.5 /8 / 8.1 /10 五代系統的迭代以后相比 IOS 系統的推送消息的種類來說更加豐富且更加人性化,今天給大家介紹一下如何在 Windows 10 推送消息中顯示圖片並且實現消息快速回復。
由於 Windows 10 Universal 應用架構這個功能可以實現一次開發可以實現在手機和平板上統統兼容。
由上圖看到左側是 Windows10 UWP 應用在Windows Phone上收到消息通知的樣子,右側的圖片是應用在 Windows 10 收到推送消息時的樣子。
都是由4個部分組成的:
消息主題:消息發送者照片 + 消息主題與消息文字內容。
消息圖片:如果消息發送者消息中包含圖片可以在消息預覽中直接將圖片顯示出來。
消息快速回復:如果是即時消息,消息的接收者可以在不啟動應用的情況下直接在 吐司消息 或 通知中心中直接回復消息。
消息快速反饋/行動(這個不知道怎么翻譯更確切):這里是可以讓用戶通過按鈕的形式對消息進行一個標記或者是一個簡短的回復,例如圖片中的點贊或打開應用查看詳情。
接下來我分享以下這個Toast消息的的XML結構來簡單分析以下:
<?xml version="1.0"?> <toast launch="action=viewConversation conversationId=384928"> <visual> <binding template="ToastGeneric"> <text>Andrew sent you a picture</text> <text>Check this out, Happy Canyon in Utah!</text> <image src="http://blogs.msdn.com/cfs-filesystemfile.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-01-71-81-permanent/2727.happycanyon1_5B00_1_5D00_.jpg"/> <image src="ms-appdata:///local/Andrew.jpg" placement="appLogoOverride" hint-crop="circle"/> </binding> </visual> <actions> <input id="tbReply" type="text" placeHolderContent="Type a response"/> <action content="Reply" arguments="action=reply&conversationId=384928" activationType="background" imageUri="Assets/Reply.png" hint-inputId="tbReply"/> <action content="Like" arguments="action=like&conversationId=384928" activationType="background"/> <action content="View" arguments="action=viewImage&imageUrl=http%3A%2F%2Fblogs.msdn.com%2Fcfs-filesystemfile.ashx%2F__key%2Fcommunityserver-blogs-components-weblogfiles%2F00-00-01-71-81-permanent%2F2727.happycanyon1_5B00_1_5D00_.jpg"/> </actions> </toast>
通過代碼我們可以清晰的看到這條消息主要分為兩部分
visual binding 部分中主要用來描述消息標題和消息文字內容,其中還包括了兩張圖片分別是用戶頭像和消息圖片,通過兩個 image 標簽可以理解到實際上消息內容是一個網絡圖片,而發送者的頭像則是一張應用的本地圖片(通過ms-appdata:///local可以看出來)。
其次Action結點下的內容就是一個輸入框和三個按鈕了,很明顯在 content屬性中標明了他們的作用,並且請大家注意一下argument屬性標明的內容會傳入應用用來標記通知或者會話的ID,activationType是用來標記這個按鈕觸發事件以后,的代碼執行位置,Background是一個后台任務(這樣就意味着應用可以不啟動就可以執行一些操作)。
接着我們來看一下這個應用的聲明文件(package.appxmanifest)中聲明了這個應用支持 background tasks 並且task類型是一個 System event
<Extensions> <Extension Category="windows.backgroundTasks" EntryPoint="BackgroundTaskComponent.ToastNotificationBackgroundTask"> <BackgroundTasks> <Task Type="systemEvent" /> </BackgroundTasks> </Extension> </Extensions>
接着我們看看如何在后台任務中處理這個消息通知。首先通過 details.Argument來獲取到之前在XML中設置的消息參數,通過swith case按鈕的conten屬性中的內容來進行邏輯判斷從而執行不同的應用程序邏輯。
public async void Run(IBackgroundTaskInstance taskInstance) { // Get a deferral since we're executing async code var deferral = taskInstance.GetDeferral(); try { // If it's a toast notification action if (taskInstance.TriggerDetails is ToastNotificationActionTriggerDetail) { // Get the toast activation details var details = taskInstance.TriggerDetails as ToastNotificationActionTriggerDetail; // Deserialize the arguments received from the toast activation QueryString args = QueryString.Parse(details.Argument); // Depending on what action was taken... switch (args["action"]) { // User clicked the reply button (doing a quick reply) case "reply": await HandleReply(details, args); break; // User clicked the like button case "like": await HandleLike(details, args); break; default: throw new NotImplementedException(); } } // Otherwise handle other background activations else throw new NotImplementedException(); } finally { // And finally release the deferral since we're done deferral.Complete(); } }
在實現代碼中可以再次獲取更詳細的參數獲得消息會話ID以及用戶在快速回復對話框中輸入的內容從而與服務器進行交互。
private async Task HandleReply(ToastNotificationActionTriggerDetail details, QueryString args) { // Get the conversation the toast is about int conversationId = int.Parse(args["conversationId"]); // Get the message that the user typed in the toast string message = (string)details.UserInput["tbReply"]; // In a real app, this would be making a web request, sending the new message await Task.Delay(TimeSpan.FromSeconds(2.3)); // In a real app, you most likely should NOT notify your user that the request completed (only notify them if there's an error) SendToast("Your message has been sent! Your message: " + message); } private async Task HandleLike(ToastNotificationActionTriggerDetail details, QueryString args) { // Get the conversation the toast is about int conversationId = int.Parse(args["conversationId"]); // In a real app, this would be making a web request, sending the like command await Task.Delay(TimeSpan.FromSeconds(1.1)); // In a real app, you most likely should NOT notify your user that the request completed (only notify them if there's an error) SendToast("Your like has been sent!"); }
當然也不要忘記在應用啟動的時候注冊這個BackgroundTask,這部分代碼可以放在應用 Onlaunched 和 OnActivated事件中。
private void RegisterBackgroundTask() { const string taskName = "ToastBackgroundTask"; // If background task is already registered, do nothing if (BackgroundTaskRegistration.AllTasks.Any(i => i.Value.Name.Equals(taskName))) return; // Otherwise create the background task var builder = new BackgroundTaskBuilder() { Name = taskName, TaskEntryPoint = typeof(ToastNotificationBackgroundTask).FullName }; // And set the toast action trigger builder.SetTrigger(new ToastNotificationActionTrigger()); // And register the task builder.Register(); }
另外剛才除了后台執行代碼的兩個按鈕還有查看消息和查看圖片按鈕的執行邏輯,這里應該是放在Launched 和 Activated方法中的首先要判斷應用的 IActivatedEventArgs類型是否ToastNotificationActivatedEventArgs如果是嘗試模仿后台任務中查找按鈕參數的方法找到會話ID和照片連接並導航到應用的正確頁面去。
private async Task OnLaunchedOrActivated(IActivatedEventArgs e) { // Initialize things like registering background task before the app is loaded await InitializeApp(); #if DEBUG if (System.Diagnostics.Debugger.IsAttached) { this.DebugSettings.EnableFrameRateCounter = true; } #endif Frame rootFrame = Window.Current.Content as Frame; // Do not repeat app initialization when the Window already has content, // just ensure that the window is active if (rootFrame == null) { // Create a Frame to act as the navigation context and navigate to the first page rootFrame = new Frame(); rootFrame.NavigationFailed += OnNavigationFailed; if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) { // TODO: Load state from previously suspended application } // Place the frame in the current Window Window.Current.Content = rootFrame; } // Handle toast activation if (e is ToastNotificationActivatedEventArgs) { var toastActivationArgs = e as ToastNotificationActivatedEventArgs; // If empty args, no specific action (just launch the app) if (toastActivationArgs.Argument.Length == 0) { if (rootFrame.Content == null) rootFrame.Navigate(typeof(MainPage)); } // Otherwise an action is provided else { // Parse the query string QueryString args = QueryString.Parse(toastActivationArgs.Argument); // See what action is being requested switch (args["action"]) { // Open the image case "viewImage": // The URL retrieved from the toast args string imageUrl = args["imageUrl"]; // If we're already viewing that image, do nothing if (rootFrame.Content is ImagePage && (rootFrame.Content as ImagePage).ImageUrl.Equals(imageUrl)) break; // Otherwise navigate to view it rootFrame.Navigate(typeof(ImagePage), imageUrl); break; // Open the conversation case "viewConversation": // The conversation ID retrieved from the toast args int conversationId = int.Parse(args["conversationId"]); // If we're already viewing that conversation, do nothing if (rootFrame.Content is ConversationPage && (rootFrame.Content as ConversationPage).ConversationId == conversationId) break; // Otherwise navigate to view it rootFrame.Navigate(typeof(ConversationPage), conversationId); break; default: throw new NotImplementedException(); } // If we're loading the app for the first time, place the main page on the back stack // so that user can go back after they've been navigated to the specific page if (rootFrame.BackStack.Count == 0) rootFrame.BackStack.Add(new PageStackEntry(typeof(MainPage), null, null)); } } // Handle launch activation else if (e is LaunchActivatedEventArgs) { var launchActivationArgs = e as LaunchActivatedEventArgs; // If launched with arguments (not a normal primary tile/applist launch) if (launchActivationArgs.Arguments.Length > 0) { // TODO: Handle arguments for cases like launching from secondary Tile, so we navigate to the correct page throw new NotImplementedException(); } // Otherwise if launched normally else { // If we're currently not on a page, navigate to the main page if (rootFrame.Content == null) rootFrame.Navigate(typeof(MainPage)); } } else { // TODO: Handle other types of activation throw new NotImplementedException(); } // Ensure the current window is active Window.Current.Activate(); }
好了這樣你的應用就可以處理 Windows10 可交互性的自定義消息通知了。
並且為了測試方便你可以在應用中使用 NotificationsExtensions.Win10 這個擴展類庫進行測試,在nuget上可以直接下載到。
https://www.nuget.org/packages/NotificationsExtensions.Win10/
測試代碼如下:
private void ButtonSendToast_Click(object sender, RoutedEventArgs e) { // In a real app, these would be initialized with actual data string title = "Andrew sent you a picture"; string content = "Check this out, Happy Canyon in Utah!"; string image = "http://blogs.msdn.com/cfs-filesystemfile.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-01-71-81-permanent/2727.happycanyon1_5B00_1_5D00_.jpg"; string logo = "ms-appdata:///local/Andrew.jpg"; int conversationId = 384928; // Construct the visuals of the toast ToastVisual visual = new ToastVisual() { TitleText = new ToastText() { Text = title }, BodyTextLine1 = new ToastText() { Text = content }, InlineImages = { new ToastImage() { Source = new ToastImageSource(image) } }, AppLogoOverride = new ToastAppLogo() { Source = new ToastImageSource(logo), Crop = ToastImageCrop.Circle } }; // Construct the actions for the toast (inputs and buttons) ToastActionsCustom actions = new ToastActionsCustom() { Inputs = { new ToastTextBox("tbReply") { PlaceholderContent = "Type a response" } }, Buttons = { new ToastButton("Reply", new QueryString() { { "action", "reply" }, { "conversationId", conversationId.ToString() } }.ToString()) { ActivationType = ToastActivationType.Background, ImageUri = "Assets/Reply.png", // Reference the text box's ID in order to // place this button next to the text box TextBoxId = "tbReply" }, new ToastButton("Like", new QueryString() { { "action", "like" }, { "conversationId", conversationId.ToString() } }.ToString()) { ActivationType = ToastActivationType.Background }, new ToastButton("View", new QueryString() { { "action", "viewImage" }, { "imageUrl", image } }.ToString()) } }; // Now we can construct the final toast content ToastContent toastContent = new ToastContent() { Visual = visual, Actions = actions, // Arguments when the user taps body of toast Launch = new QueryString() { { "action", "viewConversation" }, { "conversationId", conversationId.ToString() } }.ToString() }; // And create the toast notification ToastNotification notification = new ToastNotification(toastContent.GetXml()); // And then send the toast ToastNotificationManager.CreateToastNotifier().Show(notification); }
有即時消息需求的應用是非常適合這個功能的,行動起來快試試把這個功能加到應用中吧。
參考資料:
Adaptive Tile Templates - Schema and Documentation
What's new with live tiles in Windows 10
What's new/different with toast notifications and action center in Windows 10
Quickstart: Sending a local toast notification and handling activations from it (Windows 10)
Adaptive and interactive toast notifications for Windows 10
希望上的總結可以幫助到大家, 同時歡迎大家在這里和我溝通交流或者在新浪微博上 @王博_Nick