使用 Webclient 实现应用自动更新

jopen 9年前

本文提供了一个通过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) != &apos;.&apos;)           {                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方法释放下载进程占用的资源。

结束.