動態磁貼(Live Tile)是WP系統的大亮點之一,一直以來受到廣大用戶的喜愛。這一講主要研究如何在UWP應用里通過后台任務添加和使用動態磁貼功能。
從WP7到Win8,再到Win10 UWP,磁貼模板不斷進行調整和優化,目前磁貼模板已經發展到第三代,一般稱之為“Adaptive Tile Templates”。
在運用UWP動態磁貼之前,請先了解一下自適應磁貼的語法規則。關於自適應磁貼模板的語法規則,請詳讀這篇文章:http://blogs.msdn.com/b/tiles_and_toasts/archive/2015/06/30/adaptive-tile-templates-schema-and-documentation.aspx
一. 磁貼更新的原理
磁貼的“動態”,在於它能不斷地進行更新,展示新的內容。磁貼又可分為主磁貼(即由應用列表Pin到桌面的磁貼)和二級磁貼(即由應用內部通過程序控制Pin到桌面的磁貼)。這兩種磁貼都支持小、中、寬和大磁貼4種尺寸。
Windows.UI.Notifications.TileUpdater可以用來管理和修改當前磁貼的內容,從而達到更新磁貼的目的。TileUpdater對象必須通過Windows.UI.Notifications.TileUpdateManager的CreateTileUpdaterForApplication或CreateTileUpdaterForSecondaryTile 方法來獲取。如:
1 var updater = TileUpdateManager.CreateTileUpdaterForApplication();
1 var updater = TileUpdateManager.CreateTileUpdaterForSecondaryTile("appdota2");
然后調用updater的Update方法即可實現對內容的更新。Update方法需要傳入一個TileNotification對象:
1 // 2 // 摘要: 3 // 將內容或外觀的更改應用於圖塊。 4 // 5 // 參數: 6 // notification: 7 // 為平鋪的內容提供新的 XML 定義的對象。 8 public void Update(TileNotification notification);
顧名思義,TileNotifiction承載着要進行通知的磁貼模板,磁貼模板實際上就是一個定義好的XML文件。在UWP里,這個磁貼模板就需要按照“Adaptive Tile Templates”的規則來定義。
二.磁貼的更新方式
磁貼更新的方式可以通過程序內部控制,如在后台請求新數據並進行更新,這種方式也就是通過后台任務(Background Task)來實現更新;也可以通過推送通知使磁貼產生變化。我們今天重點講后台任務的更新磁貼方法。
后台任務更新邏輯:
1.應用首先注冊一個后台任務
2.后台任務定期向服務器請求新數據
3.服務器傳回新的數據
4.后台任務通過TileUpdater更新磁貼內容

三.實現后台任務更新磁貼
1.后台任務邏輯
創建一個WinRT組件,再創建一個類叫LiveTileTask,並且實現IBackgroundTask接口,必須實現接口的Run方法:
1 public sealed class LiveTileTask : IBackgroundTask 2 { 3 public async void Run(IBackgroundTaskInstance taskInstance) 4 { 5 var deferral = taskInstance.GetDeferral(); 6 // TODO: 獲取數據,更新磁貼邏輯 8 deferral.Complete(); 10 } 11 }
Run方法中必須獲取deferral對象,並且執行完成后需要調用deferral對象關閉,因為我們在后台任務中是需要執行異步代碼的,所以獲取完deferral對象之后,大約有5秒鍾的時間可以進行異步操作,超過時間系統就會強制釋放deferral對象。這樣能保證較好的用戶體驗,如果異步請求的時間過長,自然會認為執行失敗而不會去更新磁貼了。
接下來再Run方法中間位置開始執行請求數據的任務:
1 public async void Run(IBackgroundTaskInstance taskInstance) 2 { 3 var deferral = taskInstance.GetDeferral(); 4 5 await GetLatestNews(); 6 7 deferral.Complete(); 8 } 9 10 private IAsyncOperation<string> GetLatestNews() 11 { 12 try 13 { 14 return AsyncInfo.Run(token => GetNews()); 15 } 16 catch (Exception) 17 { 18 // ignored 19 } 20 return null; 21 }
其中GetNews方法即向服務端請求數據,完成后可以開始更新磁貼:
1 private async Task<string> GetNews() 2 { 3 try 4 { 5 var response = await ApiService.GetHotNewsListAsync(); 6 if (response?.Data != null) 7 { 8 var news = response.Data.Take(5).ToList(); 9 UpdatePrimaryTile(news); 10 UpdateSecondaryTile(news); 11 } 12 13 } 14 catch (Exception) 15 { 16 // ignored 17 } 18 return null; 19 }
注意磁貼最多只能更新5個,所以只處理返回數據的前5個。
更新磁貼的方法非常簡單:
1 private void UpdatePrimaryTile(List<News> news) 2 { 3 if (news == null || !news.Any()) 4 { 5 return; 6 } 7 8 try 9 { 10 var updater = TileUpdateManager.CreateTileUpdaterForApplication(); 11 updater.EnableNotificationQueueForWide310x150(true); 12 updater.EnableNotificationQueueForSquare150x150(true); 13 updater.EnableNotificationQueueForSquare310x310(true); 14 updater.EnableNotificationQueue(true); 15 updater.Clear(); 16 17 foreach (var n in news) 18 { 19 var doc = new XmlDocument(); 20 var xml = string.Format(TileTemplateXml, n.Pic, n.Title, n.Desc); 21 doc.LoadXml(WebUtility.HtmlDecode(xml), new XmlLoadSettings 22 { 23 ProhibitDtd = false, 24 ValidateOnParse = false, 25 ElementContentWhiteSpace = false, 26 ResolveExternals = false 27 }); 28 29 updater.Update(new TileNotification(doc)); 30 } 31 } 32 catch (Exception) 33 { 34 // ignored 35 } 36 }
我們采用隊列的形式允許磁貼逐個更新,還有一個需要注意的地方,服務端返回的數據可能帶有轉義字符,在加載模板XML的時候必須做一下編碼處理,否則可能導致異常而無法更新磁貼。當然其中的TileTemplateXml自己根據自適應磁貼的規則定義即可,舉例:
1 private const string TileTemplateXml = @" 2 <tile branding='name'> 3 <visual version='3'> 4 <binding template='TileMedium'> 5 <image src='{0}' placement='peek'/> 6 <text>{1}</text> 7 <text hint-style='captionsubtle' hint-wrap='true'>{2}</text> 8 </binding> 9 <binding template='TileWide'> 10 <image src='{0}' placement='peek'/> 11 <text>{1}</text> 12 <text hint-style='captionsubtle' hint-wrap='true'>{2}</text> 13 </binding> 14 <binding template='TileLarge'> 15 <image src='{0}' placement='peek'/> 16 <text>{1}</text> 17 <text hint-style='captionsubtle' hint-wrap='true'>{2}</text> 18 </binding> 19 </visual> 20 </tile>";
這個模板實現的效果是如同商店應用的Peek動態更新效果:
| Animated | Peek shown | Peek sliding up | Content shown | Peek sliding down |
|---|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
2.注冊后台任務
注意后台任務必須在前台程序進行觸發(Trigger)設置,即所謂的注冊后台任務。后台任務將根據觸發器是否被觸發而執行。
Win8.1的Trigger有SystemTrigger, TimeTrigger, MaintenaceTrigger, DeviceUseTrigger, DeviceServingTrigger, PushNotificationTrigger;
WP8.1的Trigger有CachedFileUpdaterTrigger, DeviceConnectionChangedTrigger, GattCharacteristicNotificationTrigger, RfcommonConnectionTrigger, LocationTrigger;
Win10新增了如下Trigger:AppointmentStoreNotificationTrigger, ContactStoreNotificationTrigger, BluetoothLEAdvertisementWarcherTrigger, BluetoothLEAdvertisementPublisherTrigger, DeviceWatcherTrigger, ActivitySensorTrigger, SensorDataThresholdTrigger, ToastNotificationHistoryChangedTrigger, ToastNotificationActionTrigger, ApplicationTrigger, SocketActivityTrigger。
如果我們要進行周期性的磁貼更新,那么我們可以將用Timer觸發器去進行觸發,需要在Package.appxmanifest中聲明一個后台任務,支持的任務類型勾選計時器,且應用設置中Entry Point設置為LiveTileTask的完整類名。

在前台程序的App.cs或其他地方進行設置:
1 private const string LIVETILETASK = "LIVETILETAKS"; 2 private async void RegisterLiveTileTask() 3 { 4 var status = await BackgroundExecutionManager.RequestAccessAsync(); 5 if (status == BackgroundAccessStatus.Unspecified || status == BackgroundAccessStatus.Denied) 6 { 7 return; 8 } 9 BackgroundTaskRegistration.AllTasks.ForEach(t => 10 { 11 if (t.Value.Name == LIVETILETASK) 12 { 13 t.Value.Unregister(true); 14 } 15 }); 16 17 var taskBuilder = new BackgroundTaskBuilder 18 { 19 Name = LIVETILETASK, 20 TaskEntryPoint = typeof(LiveTileTask).FullName 21 }; 22 taskBuilder.AddCondition(new SystemCondition(SystemConditionType.InternetAvailable)); 23 24 var updater = TileUpdateManager.CreateTileUpdaterForApplication(); 25 updater.Clear(); 26 var updater2 = TileUpdateManager.CreateTileUpdaterForSecondaryTile("appdota2"); 27 updater2.Clear();33 34 taskBuilder.SetTrigger(new TimeTrigger(60, false)); 35 taskBuilder.Register(); 36 }
對於后台任務,還可以設定一定的條件使其觸發,如當沒有網絡的情況下,即使到了時間周期,也不會去觸發后台任務。
這樣就實行了每個60分鍾觸發一次后台任務,讓后台任務去請求新的數據,並將磁貼已隊列的形式循環進行更新。





