引言
本來博主想偷懶使用AutoUpdater.NET組件,但由于博主項(xiàng)目有些特殊性和它的功能過于多,于是博主自己實(shí)現(xiàn)一個(gè)輕量級(jí)獨(dú)立自動(dòng)更新組件,可稍作修改集成到大家自己項(xiàng)目中,比如:WPF/Winform/Windows服務(wù)。大致思路:發(fā)現(xiàn)更新后,從網(wǎng)絡(luò)上下載更新包并進(jìn)行解壓,同時(shí)在 WinForms 應(yīng)用程序中顯示下載和解壓進(jìn)度條,并重啟程序。以提供更好的用戶體驗(yàn)。
1. 系統(tǒng)架構(gòu)概覽
自動(dòng)化軟件更新系統(tǒng)主要包括以下幾個(gè)核心部分:
- 版本檢查:定期或在啟動(dòng)時(shí)檢查服務(wù)器上的最新版本。
- 下載更新:如果發(fā)現(xiàn)新版本,則從服務(wù)器下載更新包。
- 解壓縮與安裝:解壓下載的更新包,替換舊文件。
- 重啟應(yīng)用:更新完畢后,重啟應(yīng)用以加載新版本。
組件實(shí)現(xiàn)細(xì)節(jié)
獨(dú)立更新程序邏輯:
1. 創(chuàng)建 WinForms 應(yīng)用程序
首先,創(chuàng)建一個(gè)新的 WinForms 應(yīng)用程序,用來承載獨(dú)立的自動(dòng)更新程序,界面就簡單兩個(gè)組件:添加一個(gè) ProgressBar
和一個(gè) TextBox
控件,用于顯示進(jìn)度和信息提示。
2. 主窗體加載事件
我們?cè)谥鞔绑w的 Load
事件中完成以下步驟:
- 解析命令行參數(shù)。
- 關(guān)閉當(dāng)前運(yùn)行的程序。
- 下載更新包并顯示下載進(jìn)度。
- 解壓更新包并顯示解壓進(jìn)度。
- 啟動(dòng)解壓后的新版本程序。
下面是主窗體 Form1_Load
事件處理程序的代碼:
private async void Form1_Load(object sender, EventArgs e)
{
var args = Environment.GetCommandLineArgs();
if (!ParseArguments(args, out string downloadUrl, out string programToLaunch, out string currentProgram))
{
_ = MessageBox.Show("請(qǐng)?zhí)峁┯行У南螺d地址和啟動(dòng)程序名稱的參數(shù)。");
Application.Exit();
return;
}
Process[] processes = Process.GetProcessesByName(currentProgram);
foreach (Process process in processes)
{
process.Kill();
process.WaitForExit();
}
string downloadPath = Path.Combine(Path.GetTempPath(), Path.GetFileName(downloadUrl));
progressBar.Value = 0;
textBoxInformation.Text = "下載中...";
await DownloadFileAsync(downloadUrl, downloadPath);
progressBar.Value = 0;
textBoxInformation.Text = "解壓中...";
await Task.Run(() => ExtractZipFile(downloadPath, AppDomain.CurrentDomain.BaseDirectory));
textBoxInformation.Text = "完成";
string programPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, programToLaunch);
if (File.Exists(programPath))
{
_ = Process.Start(programPath);
Application.Exit();
}
else
{
_ = MessageBox.Show($"無法找到程序:{programPath}");
}
}
3. 解析命令行參數(shù)
我們需要從命令行接收下載地址、啟動(dòng)程序名稱和當(dāng)前運(yùn)行程序的名稱。以下是解析命令行參數(shù)的代碼:
查看代碼
4. 下載更新包并顯示進(jìn)度
使用 HttpClient
下載文件,并在下載過程中更新進(jìn)度條:
private async Task DownloadFileAsync(string url, string destinationPath)
{
using (HttpClient client = new HttpClient())
{
using (HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))
{
_ = response.EnsureSuccessStatusCode();
long? totalBytes = response.Content.Headers.ContentLength;
using (var stream = await response.Content.ReadAsStreamAsync())
using (var fileStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true))
{
var buffer = new byte[8192];
long totalRead = 0;
int bytesRead;
while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) != 0)
{
await fileStream.WriteAsync(buffer, 0, bytesRead);
totalRead += bytesRead;
if (totalBytes.HasValue)
{
int progress = (int)((double)totalRead / totalBytes.Value * 100);
_ = Invoke(new Action(() => progressBar.Value = progress));
}
}
}
}
}
}
5. 解壓更新包并顯示進(jìn)度
在解壓過程中跳過 Updater.exe
文件(因?yàn)楫?dāng)前更新程序正在運(yùn)行,大家可根據(jù)需求修改邏輯),并捕獲異常以確保進(jìn)度條和界面更新:
private void ExtractZipFile(string zipFilePath, string extractPath)
{
using (ZipArchive archive = ZipFile.OpenRead(zipFilePath))
{
int totalEntries = archive.Entries.Count;
int extractedEntries = 0;
foreach (ZipArchiveEntry entry in archive.Entries)
{
try
{
if (entry.FullName.Equals(CustConst.AppNmae, StringComparison.OrdinalIgnoreCase))
{
continue;
}
string destinationPath = Path.Combine(extractPath, entry.FullName);
_ = Invoke(new Action(() => textBoxInformation.Text = $"解壓中... {entry.FullName}"));
if (string.IsNullOrEmpty(entry.Name))
{
_ = Directory.CreateDirectory(destinationPath);
}
else
{
_ = Directory.CreateDirectory(Path.GetDirectoryName(destinationPath));
entry.ExtractToFile(destinationPath, overwrite: true);
}
extractedEntries++;
int progress = (int)((double)extractedEntries / totalEntries * 100);
_ = Invoke(new Action(() => progressBar.Value = progress));
}
catch (Exception ex)
{
_ = Invoke(new Action(() => textBoxInformation.Text = $"解壓失?。?span id="ft5rnzh" class="hljs-subst" style="margin: 0px; padding: 0px;">{entry.FullName}, 錯(cuò)誤: {ex.Message}"));
continue;
}
}
}
}
6. 啟動(dòng)解壓后的新程序
在解壓完成后,啟動(dòng)新版本的程序,并且關(guān)閉更新程序:
查看代碼
檢查更新邏輯
1. 創(chuàng)建 UpdateChecker
類
創(chuàng)建一個(gè) UpdateChecker
類,對(duì)外提供引用,用于檢查更新并啟動(dòng)更新程序:
public static class UpdateChecker
{
public static string UpdateUrl { get; set; }
public static string CurrentVersion { get; set; }
public static string MainProgramRelativePath { get; set; }
public static void CheckForUpdates()
{
try
{
using (HttpClient client = new HttpClient())
{
string xmlContent = client.GetStringAsync(UpdateUrl).Result;
XDocument xmlDoc = XDocument.Parse(xmlContent);
var latestVersion = xmlDoc.Root.Element("version")?.Value;
var downloadUrl = xmlDoc.Root.Element("url")?.Value;
if (!string.IsNullOrEmpty(latestVersion) && !string.IsNullOrEmpty(downloadUrl) && latestVersion != CurrentVersion)
{
string currentProcessName = Process.GetCurrentProcess().ProcessName;
string arguments = $"--url \"{downloadUrl}\" --launch \"{MainProgramRelativePath}\" --current \"{currentProcessName}\"";
_ = Process.Start(CustConst.AppNmae, arguments);
Application.Exit();
}
}
}
catch (Exception ex)
{
_ = MessageBox.Show($"檢查更新失敗:{ex.Message}");
}
}
}
2. 服務(wù)器配置XML
服務(wù)器上存放一個(gè)XML文件配置當(dāng)前最新版本、安裝包下載地址等,假設(shè)服務(wù)器上的 XML 文件內(nèi)容如下:
<?xml version="1.0" encoding="utf-8"?>
<update>
<version>1.0.2</version>
<url>https://example.com/yourfile.zip</url>
</update>
主程序調(diào)用更新檢查
主程序可以通過定時(shí)器或者手動(dòng)調(diào)用檢查更新的邏輯,博主使用定時(shí)檢查更新:
查看代碼
思考:性能與安全考量
在實(shí)現(xiàn)自動(dòng)化更新時(shí),還應(yīng)考慮性能和安全因素。例如,為了提高效率,可以添加斷點(diǎn)續(xù)傳功能;為了保證安全,應(yīng)驗(yàn)證下載文件的完整性,例如使用SHA256校驗(yàn)和,這些博主就不做實(shí)現(xiàn)與講解了,目前的功能已經(jīng)完成了基本的自動(dòng)更新邏輯
?轉(zhuǎn)自https://www.cnblogs.com/Bob-luo/p/18231510
該文章在 2024/11/6 9:19:26 編輯過