使用 Webclient 实现应用自动更新
本文提供了一个通过URL下载文件的简易实现。
背景
很多应用都会提供一个自动更新的功能,这样终端用户能获得最好的用户体验。简单地说,自动更新可以通过两步完成:
-
Step 1: 访问程序的服务端来检查是否有可用的新版本。
-
Step 2: 询问用户是否要更新并遵从用户的选择。
本文主要会讲解围绕上面两步所展开的工作流程。这是我的实际工作经验。如果您有什么疑问可以给我留言。
代码
1.从服务器取得最新版本信息.
通常应用会有一个像1.20.11这样的版本号,这是一个通过'.'分隔的一系列数字。当检查服务器上的最新版本是否比用户的当前版本新时,使用版本号是一个很不错的方法。通常我们会认为1.20.22的版本要比1.20.11的版本新。
[Update] CurrentVersion = 1.10.12 IniPath = ftp://192.168.29.19//Version.ini
此处有两个数据行。"CurrentVersion"代表了当前(用户)的应用版本,"IniPath"代表了服务器的Version.ini文件路径。我们可以下载这个Version.ini文件并读取它的版本号来做对比,这样就可以检查是否需要通知用户(是否要更新应用)。
服务端的Version.ini内容:
[Update] CurrentVersion = 1.11.00 ExePath = ............
第一个数据行表示了服务器上的应用的最新版本号,第二行代表了新版本应用的下载路径。
稍后我们会介绍如何下载这个文件,现在我先介绍如何比较两个版本号:
/** * * @param version1 * @param version2 * @return if version1 > version2, return 1, if equal, return 0, else return -1 */ public static int compare(String version1, String version2) { if (version1 == null || version1.Length == 0 || version2 == null || version2.Length == 0 ) { return -1; } int index1 = 0; int index2 = 0; while(index1 < version1.Length && index2 < version2.Length) { int[] number1 = getValue(version1, index1); int[] number2 = getValue(version2, index2); if (number1[0] < number2[0]) return -1; else if (number1[0] > number2[0]) return 1; else { index1 = number1[1] + 1; index2 = number2[1] + 1; } } if(index1 == version1.Length && index2 == version2.Length) return 0; if(index1 < version1.Length) return 1; else return -1; } /** * * @param version * @param index the starting point * @return the number between two dots, and the index of the dot */ public static int[] getValue(string version, int index) { int[] value_index = new int[2]; StringBuilder sb = new StringBuilder(); while(index < version.Length && version.ElementAt(index) != '.') { sb.Append(version.ElementAt(index)); index++; } value_index[0] = Convert.ToInt32(sb.ToString(),10); value_index[1] = index; return value_index; }
这段代码通过'.'字符来分隔version字符串,然后对取得的数字依次进行比较.
(译者注:
本人觉得这个方法太过繁琐,如果使用android应用的版本控制会好点:定义一个versionName和versionCode,versionName是一个字符串,它作为版本的数字序列或名称,而versionCode作为int型的版本号,查检更新的时候可以通过versionCode这个int型的数字直接比较)
2. 从服务端下载文件.
正如本文标题所说,此方法会用到webclient接口来进行下载。它提供了很多用于下载的方法。
webclient提供了同步和异常方法。同步方法会阻塞线程。它通常用于下载耗时较短的小文件。在这我们使用同步方法来下载INI文件。代码如下:
// New a webclient object WebClient web = new WebClient(); //Download synchronized try { string UserName = AutoUpdate.Form1.GetStringFromFile("UpdateInfo", "ID_USER_NAME"); string PassWord = AutoUpdate.Form1.GetStringFromFile("UpdateInfo", "ID_USER_PASSWORD"); web.Credentials = new NetworkCredential(UserName, PassWord); web.DownloadFile(new Uri(ServerIniPath.ToString()), ItemSavePath); } catch (System.Exception ex) { throw ex; }
我们可以从INI文件中取得最新应用的下载路径,如果需要下载它,我们应该使用异步方法.
try { web.DownloadFileAsync(new Uri(DownLoadAddress), ItemSavePath); web.DownloadProgressChanged += client_DownloadProgressChanged; web.DownloadFileCompleted += client_DownloadFileCompleted; } catch (System.Exception ex) { throw ex; }
从代码我们可以看到当调用下载方法后,我们也为它添加了两个事件handler(处理器)。这两个handler可以告知我们下载的详细信息,例如已下载百分比,已下载字节数等等。handler代码如下:
void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) { Action<AsyncCompletedEventArgs> onCompleted = progressCompleted; onCompleted.Invoke(e); } void client_DownloadFileProgresschanged(object sender, AsyncCompletedEventArgs e) { Action<DownloadProgressChangedEventArgs> onCompleted = progressChanging; onCompleted.Invoke(e); }
方法progressCompleted和progressChanging可以按需实现。
3. 下载异常.
这个异常并不是通过try和catch捕获的异常。这是一个真正的异常:网络不可用,用户取消更新,服务器不可用。因为我们使用异步方法进行下载,这会导致很多并发问题。接下来我会介绍我是如何处理它们的。
网络不可用.
这个问题随时都会发生,所以我需要启用另一个线程,它会根据时间来检查网络状态。一旦发现网络断开了,我会调用下载线程来通知用户。
如何检查网络状态其实很简单,我们可以使用 "wininet.dll"提供的方法。
[DllImport("wininet.dll")] public static extern bool InternetGetConnectedState(out int lpdwFlags, int dwReserved);
用户取消下载:
weblient已经考滤到了这种情况,它提供了cancel方法来停止下载和dispose方法释放下载进程占用的资源。
结束.