WPF開發一款軟件自動升級組件


  前幾天介紹了WPF進行自定義窗口的開發,關注的朋友還挺多,超出本人意料,呵呵,那么我就再接再勵,在上篇的基礎上,講述一下軟件自動升級組件的開發原理,大家時間寶貴,不想搞太長的篇幅,所以盡可能揀重要的說說,附件中有源碼,沒時間朋友直接下載吧,希望有需要的朋友能用的上,有時間的朋友還是跟着本文一起,體驗一下開發的過程吧,因為這個組件做的挺趕,問題估計不少,大家發現問題歡迎踴躍留言,本文只做拋磚引玉的作用...

  廢話不說,開始!

  軟件發布后,自動升級往往是一項必備的功能,本篇博客的目標就是用WPF打造一個自動升級組件。先看效果:

  

升級提醒界面

升級過程界面

升級完成界面

 

 

  升級界面不是本文的內容,具體見我的上一篇博客

  其實升級的過程很簡單,大致如下:

  檢測服務器上的版本號—>比較本地程序的版本號和服務器上的版本號—>如果不相同則下載升級的壓縮包—>下載完成后解壓升級包—>解壓后的文件覆蓋到應用程序文件目錄—>升級完成

  有兩點需要注意:

  1. 因為升級的過程就是用新文件覆蓋舊文件的過程,所以要防止老文件被占用后無法覆蓋的情況,因而升級之前應該關閉運用程序。
  2. 升級程序本身也可能需要升級,而升級程序啟動后如問題1所說,就不可能被覆蓋了,因而應該想辦法避免這種情況。

  有了上面的分析,下面我們就來具體實現之。

  首先新建一個WPF Application項目,命名為AutoUpdater,因為升級程序需要能夠單獨執行,必須編譯成exe文件,所以不能是類庫項目。

  接下來新建一個類Updater.cs來處理檢測的過程。

  服務器上的版本信息我們存儲到一個XML文件中,文件格式定義如下:

  

View Code
<?xml version="1.0" encoding="utf-8"?>

<UpdateInfo>

    <AppName>Test</AppName>

    <AppVersion>1.0.0.1</AppVersion>

    <RequiredMinVersion>1.0.0.0</RequiredMinVersion>

    <Desc>shengji</Desc>

</UpdateInfo>

 

  然后定義一個實體類對應XML定義的升級信息,如下:

    

View Code
public class UpdateInfo

    {

    public string AppName { get; set; }

 

         /// <summary>

         /// 應用程序版本

         /// </summary>

         public Version AppVersion { get; set; }

 

         /// <summary>

         /// 升級需要的最低版本

         /// </summary>

         public Version RequiredMinVersion { get; set; }

 

         public Guid MD5

         {

             get;

              set;

         }

 

        private string _desc;

        /// <summary>

        /// 更新描述

        /// </summary>

        public string Desc

        {

             get

             {

                 return _desc;

             }

             set

             {

                 _desc = string.Join(Environment.NewLine, value.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries));

             }

        }

    }

 

   檢測的詳細步驟應該如下:

  1. 異步下載update.xml到本地
  2. 分析xml文件信息,存儲到自定義的類UpdateInfo中
  3. 判斷升級需要的最低版本號,如果滿足,啟動升級程序。這里就碰到了上面提到的問題,文件被占用的問題。因為如果直接啟動AutoUpdater.exe,升級包中的AutoUpdater.exe是無法覆蓋這個文件的,所以采取的辦法是將AutoUpdater.exe拷貝到緩存文件夾中,然后啟動緩存文件夾中的AutoUpdater.exe文件來完成升級的過程。

  具體代碼如下,第一個方法CheckUpdateStatus()完成1、2兩個步驟,第二個方法StartUpdate(UpdateInfo updateInfo)完成步驟3:

View Code
        public static void CheckUpdateStatus()

        {

            System.Threading.ThreadPool.QueueUserWorkItem((s) =>

            {

                string url = Constants.RemoteUrl + Updater.Instance.CallExeName + "/update.xml";

                var client = new System.Net.WebClient();

                client.DownloadDataCompleted += (x, y) =>

                {

                    try

                    {

                        MemoryStream stream = new MemoryStream(y.Result);

 

                        XDocument xDoc = XDocument.Load(stream);

                        UpdateInfo updateInfo = new UpdateInfo();

                        XElement root = xDoc.Element("UpdateInfo");

                        updateInfo.AppName = root.Element("AppName").Value;

                        updateInfo.AppVersion = root.Element("AppVersion") == null || string.IsNullOrEmpty(root.Element("AppVersion").Value) ? null : new Version(root.Element("AppVersion").Value);

                        updateInfo.RequiredMinVersion = root.Element("RequiredMinVersion") == null || string.IsNullOrEmpty(root.Element("RequiredMinVersion").Value) ? null : new Version(root.Element("RequiredMinVersion").Value);

                        updateInfo.Desc = root.Element("Desc").Value;

                        updateInfo.MD5 = Guid.NewGuid();

 

                        stream.Close();

                        Updater.Instance.StartUpdate(updateInfo);

                    }

                    catch

                    { }

                };

                client.DownloadDataAsync(new Uri(url));

 

            });

 

        }

 

        public void StartUpdate(UpdateInfo updateInfo)

        {

            if (updateInfo.RequiredMinVersion != null && Updater.Instance.CurrentVersion < updateInfo.RequiredMinVersion)

            {

                //當前版本比需要的版本小,不更新

                return;

            }

 

            if (Updater.Instance.CurrentVersion >= updateInfo.AppVersion)

            {

                //當前版本是最新的,不更新

                return;

            }

 

            //更新程序復制到緩存文件夾

            string appDir = System.IO.Path.Combine(System.Reflection.Assembly.GetEntryAssembly().Location.Substring(0, System.Reflection.Assembly.GetEntryAssembly().Location.LastIndexOf(System.IO.Path.DirectorySeparatorChar)));

            string updateFileDir = System.IO.Path.Combine(System.IO.Path.Combine(appDir.Substring(0, appDir.LastIndexOf(System.IO.Path.DirectorySeparatorChar))), "Update");

            if (!Directory.Exists(updateFileDir))

            {

                Directory.CreateDirectory(updateFileDir);

            }

            updateFileDir = System.IO.Path.Combine(updateFileDir, updateInfo.MD5.ToString());

            if (!Directory.Exists(updateFileDir))

            {

                Directory.CreateDirectory(updateFileDir);

            }

 

            string exePath = System.IO.Path.Combine(updateFileDir, "AutoUpdater.exe");

            File.Copy(System.IO.Path.Combine(appDir, "AutoUpdater.exe"), exePath, true);

 

            var info = new System.Diagnostics.ProcessStartInfo(exePath);

            info.UseShellExecute = true;

            info.WorkingDirectory = exePath.Substring(0, exePath.LastIndexOf(System.IO.Path.DirectorySeparatorChar));

            updateInfo.Desc = updateInfo.Desc;

            info.Arguments = "update " + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(CallExeName)) + " " + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(updateFileDir)) + " " + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(appDir)) + " " + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(updateInfo.AppName)) + " " + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(updateInfo.AppVersion.ToString())) + " " + (string.IsNullOrEmpty(updateInfo.Desc) ? "" : Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(updateInfo.Desc)));

            System.Diagnostics.Process.Start(info);

        }

 

  在方法StartUpdate的最后,啟動Autoupdate.exe的代碼中,需要將升級信息當做參數傳遞過去,各參數間用空格分隔,考慮到信息本身(如AppName或Desc中)可能含有空格,所以傳遞前將信息進行Base64編碼。

 

      接下來打開Program.cs文件(沒有可以自己創建一個,然后在項目屬性中修改啟動對象,如下圖),

 

  在Main函數中接收傳遞過來的參數。代碼如下:

View Code
  static void Main(string[] args)

      {

            if (args.Length == 0)

            {

                return;

            }

            else if (args[0] == "update")

            {

                try

                {

                    string callExeName = args[1];

                    string updateFileDir = args[2];

                    string appDir = args[3];

                    string appName = args[4];

                    string appVersion = args[5];

                    string desc = args[6];

 

                    Ezhu.AutoUpdater.App app = new Ezhu.AutoUpdater.App();

                    UI.DownFileProcess downUI = new UI.DownFileProcess(callExeName, updateFileDir, appDir, appName, appVersion, desc) { WindowStartupLocation = WindowStartupLocation.CenterScreen };

                    app.Run(downUI);

                }

                catch (Exception ex)

                {

                    MessageBox.Show(ex.Message);

                }

      }

 

  參數接收成功后,打開下載界面,顯示升級的主要內容,如果用戶點擊升級按鈕,則開始下載升級包。

  步驟應該如下:

  1. 關閉應用程序進程
  2. 下載升級包到緩存文件夾
  3. 解壓升級包到緩存文件夾
  4. 從緩存文件夾復制解壓后的文件和文件夾到運用程序目錄
  5. 提醒用戶升級成功

  具體代碼如下:

  

View Code
public partial class DownFileProcess : WindowBase

    {

        private string updateFileDir;//更新文件存放的文件夾

        private string callExeName;

        private string appDir;

        private string appName;

        private string appVersion;

        private string desc;

        public DownFileProcess(string callExeName, string updateFileDir, string appDir, string appName, string appVersion, string desc)

        {

            InitializeComponent();

            this.Loaded += (sl, el) =>

            {

                YesButton.Content = "現在更新";

                NoButton.Content = "暫不更新";

 

                this.YesButton.Click += (sender, e) =>

                {

                    Process[] processes = Process.GetProcessesByName(this.callExeName);

 

                    if (processes.Length > 0)

                    {

                        foreach (var p in processes)

                        {

                            p.Kill();

                        }

                    }

 

                    DownloadUpdateFile();

                };

 

                this.NoButton.Click += (sender, e) =>

                {

                    this.Close();

                };

 

                this.txtProcess.Text = this.appName + "發現新的版本(" + this.appVersion + "),是否現在更新?";

                txtDes.Text = this.desc;

            };

            this.callExeName = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(callExeName));

            this.updateFileDir = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(updateFileDir));

            this.appDir = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(appDir));

            this.appName = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(appName));

            this.appVersion = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(appVersion));

 

            string sDesc = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(desc));

            if (sDesc.ToLower().Equals("null"))

            {

                this.desc = "";

            }

            else

            {

                this.desc = "更新內容如下:\r\n" + sDesc;

            }

        }

 

        public void DownloadUpdateFile()

        {

            string url = Constants.RemoteUrl + callExeName + "/update.zip";

            var client = new System.Net.WebClient();

            client.DownloadProgressChanged += (sender, e) =>

            {

                UpdateProcess(e.BytesReceived, e.TotalBytesToReceive);

            };

            client.DownloadDataCompleted += (sender, e) =>

            {

                string zipFilePath = System.IO.Path.Combine(updateFileDir, "update.zip");

                byte[] data = e.Result;

                BinaryWriter writer = new BinaryWriter(new FileStream(zipFilePath, FileMode.OpenOrCreate));

                writer.Write(data);

                writer.Flush();

                writer.Close();

 

                System.Threading.ThreadPool.QueueUserWorkItem((s) =>

                {

                    Action f = () =>

                    {

                       txtProcess.Text = "開始更新程序...";

                    };

                    this.Dispatcher.Invoke(f);

 

                    string tempDir = System.IO.Path.Combine(updateFileDir, "temp");

                    if (!Directory.Exists(tempDir))

                    {

                        Directory.CreateDirectory(tempDir);

                    }

                    UnZipFile(zipFilePath, tempDir);

 

                    //移動文件

                    //App

                    if(Directory.Exists(System.IO.Path.Combine(tempDir,"App")))

                    {

                        CopyDirectory(System.IO.Path.Combine(tempDir,"App"),appDir);

                    }

 

                    f = () =>

                    {

                        txtProcess.Text = "更新完成!";

 

                        try

                        {

                            //清空緩存文件夾

                            string rootUpdateDir = updateFileDir.Substring(0, updateFileDir.LastIndexOf(System.IO.Path.DirectorySeparatorChar));

                            foreach (string p in System.IO.Directory.EnumerateDirectories(rootUpdateDir))

                            {

                                if (!p.ToLower().Equals(updateFileDir.ToLower()))

                                {

                                    System.IO.Directory.Delete(p, true);

                                }

                            }

                        }

                        catch (Exception ex)

                        {

                            //MessageBox.Show(ex.Message);

                        }

 

                    };

                    this.Dispatcher.Invoke(f);

 

                    try

                    {

                        f = () =>

                        {

                            AlertWin alert = new AlertWin("更新完成,是否現在啟動軟件?") { WindowStartupLocation = WindowStartupLocation.CenterOwner, Owner = this };

                            alert.Title = "更新完成";

                            alert.Loaded += (ss, ee) =>

                            {

                                alert.YesButton.Width = 40;

                                alert.NoButton.Width = 40;

                            };

                            alert.Width=300;

                            alert.Height = 200;

                            alert.ShowDialog();

                            if (alert.YesBtnSelected)

                            {

                                //啟動軟件

                                string exePath = System.IO.Path.Combine(appDir, callExeName + ".exe");

                                var info = new System.Diagnostics.ProcessStartInfo(exePath);

                                info.UseShellExecute = true;

                                info.WorkingDirectory = appDir;// exePath.Substring(0, exePath.LastIndexOf(System.IO.Path.DirectorySeparatorChar));

                                System.Diagnostics.Process.Start(info);

                            }

                            else

                            {

 

                            }

                            this.Close();

                        };

                        this.Dispatcher.Invoke(f);

                    }

                    catch (Exception ex)

                    {

                        //MessageBox.Show(ex.Message);

                    }

                });

 

            };

            client.DownloadDataAsync(new Uri(url));

        }

 

        private static void UnZipFile(string zipFilePath, string targetDir)

        {

            ICCEmbedded.SharpZipLib.Zip.FastZipEvents evt = new ICCEmbedded.SharpZipLib.Zip.FastZipEvents();

            ICCEmbedded.SharpZipLib.Zip.FastZip fz = new ICCEmbedded.SharpZipLib.Zip.FastZip(evt);

            fz.ExtractZip(zipFilePath, targetDir, "");

        }

 

        public void UpdateProcess(long current, long total)

        {

            string status = (int)((float)current * 100 / (float)total) + "%";

            this.txtProcess.Text = status;

            rectProcess.Width = ((float)current / (float)total) * bProcess.ActualWidth;

        }

 

        public void CopyDirectory(string sourceDirName, string destDirName)

        {

            try

            {

                if (!Directory.Exists(destDirName))

                {

                    Directory.CreateDirectory(destDirName);

                    File.SetAttributes(destDirName, File.GetAttributes(sourceDirName));

                }

                if (destDirName[destDirName.Length - 1] != Path.DirectorySeparatorChar)

                    destDirName = destDirName + Path.DirectorySeparatorChar;

                string[] files = Directory.GetFiles(sourceDirName);

                foreach (string file in files)

                {

                    File.Copy(file, destDirName + Path.GetFileName(file), true);

                    File.SetAttributes(destDirName + Path.GetFileName(file), FileAttributes.Normal);

                }

                string[] dirs = Directory.GetDirectories(sourceDirName);

                foreach (string dir in dirs)

                {

                    CopyDirectory(dir, destDirName + Path.GetFileName(dir));

                }

            }

            catch (Exception ex)

            {

                throw new Exception("復制文件錯誤");

            }

        }

    }

 

  注:

  1. 壓縮解壓用到開源庫SharpZipLib,官網: http://www.icsharpcode.net/OpenSource/SharpZipLib/Download.aspx
  2. 服務器上升級包的目錄層次應該如下(假如要升級的運用程序為Test.exe):

  Test(與exe的名字相同)

  ----update.xml

  ----update.zip

  update.zip包用如下方式生成:

  新建一個目錄APP,將所用升級的文件拷貝到APP目錄下,然后壓縮APP文件夾為update.zip文件

  1. 升級服務器的路徑配置寫到Constants.cs類中。
  2. 使用方法如下,在要升級的運用程序項目的Main函數中,加上一行語句:
  3. Ezhu.AutoUpdater.Updater.CheckUpdateStatus();

  到此,一款簡單的自動升級組件就完成了!

 

前幾天代碼鏈接加到上一篇博客的附件上了,請需要的朋友重新下載,感謝yeyong這位朋友的指正!

 

完整代碼下載


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM