在ScheduledTaskAgent中使用HttpWebRequest

ScheduledTaskAgent是WP7的后台代理,可以利用它在后台执行某些操作,比如更新Live Tile。可以用推送通知来更新Live Tile,但某些实时性要求不高的任务可以用后台代理来做。

但后台代理有诸多限制,比如某些API就不能使用。http://msdn.microsoft.com/zh-cn/library/hh202962(v=vs.92).aspx 这里有后台代理不支持的API列表,比如摄像头这些设备就无法在后台代理中使用。需要注意的API:


GeoCoordinateWatcher

此 API 用于获取设备的地理坐标,支持在后台代理中使用,但它使用缓存的位置值而不是实时数据。设备每 15 分钟更新缓存的位置值。

Mutex 类

应该使用 Mutex 类同步对在前台应用程序和后台代理之间共享的资源(如独立存储中的文件)的访问。

ShellToast 类

该类可以用于从正在运行的后台代理弹出 Toast 通知。

ShellTile 类的 Update(ShellTileData) 方法

ShellTile 类的 Delete()()()() 方法

ShellTile 类的 ActiveTiles 属性。

这些方法可以用于修改正在运行的后台代理中的 shell 磁贴。请注意,不能在后台代理中创建 shell 磁贴。

HttpWebRequest 类

该类允许您从正在运行的后台代理进行 Web 请求。

 
有HttpWebRequest类就可以实现目的了,从网络请求数据,用ShellTile的Update()方法更新Live Tile即可。在使用中也发现了一些问题,记录下来。
 
首先要实现Live Tile。http://msdn.microsoft.com/zh-cn/library/hh202979(v=vs.92).aspx 此页面演示了如何创建、修改Live Tile。我的目的是创建显示货币汇率的Live Tile,代码如下:
 1 /// <summary>
2 /// Handles the Click event of the menuPinToStart control.固定到开始屏幕
3 /// </summary>
4 /// <param name="sender">The source of the event.</param>
5 /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
6 private void menuPinToStart_Click(object sender, RoutedEventArgs e)
7 {
8 MenuItem menuItem = sender as MenuItem;
9 CurrEntity currEntity = menuItem.DataContext as CurrEntity;
10
11 if (currEntity.CurrBase.Code != currEntity.CurrTarget.Code)
12 {
13 StringBuilder sb = new StringBuilder();
14 sb.AppendFormat(CultureInfo.InvariantCulture, paratemplate, currEntity.CurrBase.Code, currEntity.CurrTarget.Code);
15
16 // Look to see whether the Tile already exists and if so, don't try to create it again.
17 ShellTile TileToFind = ShellTile.ActiveTiles.FirstOrDefault(x => x.NavigationUri.ToString().Contains("MainPage.xaml?" + sb.ToString()));
18
19 // Create the Tile if we didn't find that it already exists.
20 if (TileToFind == null)
21 {
22 // Create the Tile object and set some initial properties for the Tile.
23 // The Count value of 12 shows the number 12 on the front of the Tile. Valid values are 1-99.
24 // A Count value of 0 indicates that the Count should not be displayed.
25 StandardTileData NewTileData = new StandardTileData
26 {
27 BackgroundImage = new Uri("TileBackground.png", UriKind.Relative),
28 Title = "1" + currEntity.CurrBase.Code + "=" + currEntity.Rate.ToString() + currEntity.CurrTarget.Code,
29 Count = 0,
30 BackTitle = currEntity.TradeDate.ToString(),
31 BackContent = "1" + currEntity.CurrBase.Code + " = " + currEntity.Rate.ToString() + currEntity.CurrTarget.Code,
32 //BackBackgroundImage = new Uri("Blue.jpg", UriKind.Relative)
33 };
34 // Create the Tile and pin it to Start. This will cause a navigation to Start and a deactivation of our application.
35 ShellTile.Create(new Uri("/MainPage.xaml?" + sb.ToString(), UriKind.Relative), NewTileData);
36 }
37 }
38 }
为了区别每种货币,将货币代码附在ShellTile的NavigationUri属性里,这样可以在后台代理中取出相应的货币。
 
然后创建后台代理。新建一个后台代理项目。http://msdn.microsoft.com/zh-cn/library/hh202941(v=vs.92).aspx 这个页面演示了如何使用后台代理弹出Toast通知,为了更新Live Tile,需要改动一下。关键代码如下:
 1 protected override void OnInvoke(ScheduledTask task)
2 {
3 //TODO: Add code to perform your task in background
4 string toastMessage = "";
5
6 // If your application uses both PeriodicTask and ResourceIntensiveTask
7 // you can branch your application code here. Otherwise, you don't need to.
8 if (task is PeriodicTask)
9 {
10 // Execute periodic task actions here.
11 toastMessage = "Periodic task running.";
12 }
13 else
14 {
15 // Execute resource-intensive task actions here.
16 toastMessage = "Resource-intensive task running.";
17 }
18
19 // Launch a toast to show that the agent is running.
20 // The toast will not be shown if the foreground application is running.
21 ShellToast toast = new ShellToast();
22 toast.Title = "Background Agent Sample";
23 toast.Content = toastMessage;
24 toast.Show();
25
26 // If debugging is enabled, launch the agent again in one minute.
27 #if DEBUG_AGENT
28 ScheduledActionService.LaunchForTest(task.Name, TimeSpan.FromSeconds(60));
29 #endif
30
31 // Call NotifyComplete to let the system know the agent is done working.
32 NotifyComplete();
33 }

把其中的代码换成更新Live Tile的代码即可。首先要异步请求数据,然后更新Live Tile。异步请求的代码如下:

  1 /// <summary>
2 /// State information for our BeginGetResponse async call
3 /// </summary>
4 public class RequestState
5 {
6 public HttpWebRequest AsyncRequest { get; set; }
7 public HttpWebResponse AsyncResponse { get; set; }
8 }
9
10
11 private void ConvertCurrencyByHttpWebRequest()
12 {
13 //先判断网络是否可用
14 bool networkIsAvailable = Microsoft.Phone.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable();//当前网络是否可用
15 if (networkIsAvailable)
16 {
17 //把当前要转换的货币转换成uri
18 StringBuilder sb = new StringBuilder();
19
20 foreach (CurrEntity data in this.currEntityList)
21 {
22 try
23 {
24 //do something
37 }
38 catch
39 {
40 }
41 }
42
43 string url = String.Format(CultureInfo.InvariantCulture, currUrl, sb.ToString());
44 Uri uri = new Uri(url);
45
46 //开始异步调用
47 HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(uri);
48 //设置异步请求状态
49 RequestState requestState = new RequestState();
50 requestState.AsyncRequest = myRequest;
51 //开始对 Internet 资源的异步请求。
52 //IAsyncResult result = (IAsyncResult)req.BeginGetResponse(new AsyncCallback(ResponseCallback), req);//返回异步操作的状态
53 myRequest.BeginGetResponse(new AsyncCallback(ResponseCallback), requestState);
54
55
56 }
57 }
58
59
60
61 private void ResponseCallback(IAsyncResult asyncResult)
62 {
63 RequestState requestState = (RequestState)asyncResult.AsyncState;
64 //获取异步操作返回的的信息
65 HttpWebRequest myRequest = (HttpWebRequest)requestState.AsyncRequest;
66 //结束对 Internet 资源的异步请求
67 requestState.AsyncResponse = (HttpWebResponse)myRequest.EndGetResponse(asyncResult);
68 //WebResponse response = request.EndGetResponse(result);
69
70 using (Stream stream = requestState.AsyncResponse.GetResponseStream())
71 using (StreamReader respReader = new StreamReader(stream))
72 {
73 try
74 {
75 //do something
128 //更新Live Tile
129 UpdateTile();
130 }
131 finally
132 {
133 respReader.Close();
134 }
135
136 }
137
138 }

更新Live Tile:

 1 /// <summary>
2 /// Updates the tile.更新LiveTile
3 /// </summary>
4 private void UpdateTile()
5 {
6 //throw new NotImplementedException();
7 foreach (CurrEntity currEntity in currEntityList)
8 {
9 StringBuilder sb = new StringBuilder();
10 sb.AppendFormat(CultureInfo.InvariantCulture, paratemplate, currEntity.CurrBase.Code, currEntity.CurrTarget.Code);
11 //找出对应的Tile
12 ShellTile TileToFind = ShellTile.ActiveTiles.FirstOrDefault(x => x.NavigationUri.ToString().Contains("MainPage.xaml?" + sb.ToString()));
13 if (TileToFind != null)
14 {
15 StandardTileData NewTileData = new StandardTileData
16 {
17 BackgroundImage = new Uri("TileBackground.png", UriKind.Relative),
18 Title = "1" + currEntity.CurrBase.Code + "=" + currEntity.Rate.ToString() + currEntity.CurrTarget.Code,
19 Count = 0,
20 BackTitle = currEntity.TradeDate.ToString(),
21 BackContent = "1" + currEntity.CurrBase.Code + " = " + currEntity.Rate.ToString() + currEntity.CurrTarget.Code,
22 //BackBackgroundImage = new Uri("Blue.jpg", UriKind.Relative)
23 };
24 TileToFind.Update(NewTileData);
25
26 }
27 }
28 //ShellToast toast = new ShellToast();
29 //toast.Title = "test";
30 //toast.Content = "update success!";
31 //toast.Show();
32 }

将后台代理的代码改为:

 1 protected override void OnInvoke(ScheduledTask task)
2 {
3 //TODO: Add code to perform your task in background
4 //string toastTitle = "Periodic";
5 this.AdjustToLocalTime = true;
6 this.currEntityList = new List<CurrEntity>();
7 // If your application uses both PeriodicTask and ResourceIntensiveTask
8 // you can branch your application code here. Otherwise, you don't need to.
9 //if (task is PeriodicTask)
10 //{
11 //// Execute periodic task actions here.
12 // toastTitle = "Periodic ";
13 //}
14 //else
15 //{
16 //// Execute resource-intensive task actions here.
17 // toastTitle = "Resource-intensive ";
18 //}
19
20
21 var query = from x in ShellTile.ActiveTiles where x.NavigationUri.ToString().Contains("?s=") select x;
22 List<ShellTile> shellTileList = query.ToList();
23 if (shellTileList.Count > 0)
24 {
25 //foreach (ShellTile tile in query.ToList())
26 for (int i = 0; i < shellTileList.Count; i++)
27 {
28 ShellTile tile = shellTileList[i] as ShellTile;
29 string strCode = tile.NavigationUri.ToString().Substring(tile.NavigationUri.ToString().IndexOf("=") + 1, 6);
30 CurrEntity currEntity = new CurrEntity();
31 currEntity.CurrBase = new CurrDescEntity(strCode.Substring(0, 3), "");
32 currEntity.CurrTarget = new CurrDescEntity(strCode.Substring(3, 3), "");
33 currEntity.CurrencyAmount = 0;
34 currEntity.IsInversion = false;
35 currEntity.IsStandard = false;
36 currEntity.Rate = 0;
37 currEntity.TradeDate = System.DateTime.Now;
38 currEntityList.Add(currEntity);
39 }
40 //调用HttpWebRequest更新数据
41 this.ConvertCurrencyByHttpWebRequest();
42 }
43
44
45
46 // If debugging is enabled, launch the agent again in one minute.
47 #if DEBUG_AGENT
48 ScheduledActionService.LaunchForTest(task.Name, TimeSpan.FromSeconds(60));
49 #endif
50
51 NotifyComplete();
52 }

从Live Tile的NavigationUri中读取参数,并调用HttpWebRequest进行异步请求。

然后运行一下,奇怪的是,总是无法更新。断点调试,发现异步请求 myRequest.BeginGetResponse(new AsyncCallback(ResponseCallback), requestState); 执行了,但总是无法获取返回数据。好像ResponseCallback根本就没有执行。

 

查看后台代理的OnInvoke方法,发现在调用异步请求后,执行了一句 NotifyComplete();

这个方法意思是:通知操作系统,代理已完成其针对当前代理调用的预定任务。

也就是说,发起异步请求后,还没有等到异步返回结果,就通知系统代理已经完成任务了,导致ResponseCallback没有执行。

因此把NotifyComplete()转移到异步返回数据之后即可。

2012.1.20补充:http://forums.create.msdn.com/forums/p/90294/587823.aspx#587823

可以写成下面这样:

Move NotifyComplete() into your anonymous delegate like this:
1 protected override void OnInvoke(ScheduledTask task)
2 {
3 // tried also Create instead of CreateHttp
4 var request = (HttpWebRequest)HttpWebRequest.CreateHttp(new Uri("http://www.1112.net/lastpage.html"));
5
6
7 // breakpoint here #1
8 request.BeginGetResponse((ar) =>
9 {
10 // breakpoint here #2
11 try
12 {
13 using (StreamReader reader = new StreamReader(request.EndGetRequestStream(ar)))
14 {
15 // breakpoint here #3
16 string result = reader.ReadToEnd();
17 }
18 }
19 catch (Exception)
20 {
21 // breakpoint here #4
22 }
23 // call it here.
24 NotifyComplete();
25 }, null);
26
27 // DON'T call this here.
28 // NotifyComple



posted @ 2012-01-18 11:18  yan_xiaodi  阅读(1620)  评论(3编辑  收藏  举报