虽说是在WINCE手持平台上,但是系统架构还是C/S模式没有变,C/S模式比较难搞的一个部分就是系统更新,这个在WINCE平台下处理方式跟PC平台上也无特别大的区别,思路差不多都是如下模式:
1.程序启动之前验证版本
2.如果版本不一致则下载更新
WINCE当然也是这个模式,有点不同的就是WINCE更新时候需要安装CAB包,代码下只能用WINCE自带的CAB包安装命令
ProcessStartInfo info = new ProcessStartInfo();
info.FileName = @"wceload.exe";
info.Arguments = @"/noui " + path;
//info.Arguments = path;
System.Diagnostics.Process process = new System.Diagnostics.Process();
process.StartInfo = info;
process.Start();
wceload.exe是WINCE自带的安装命令,程序调用可以加参数控制,详情可见MSDN,这个命令有个不好的地方就是如果有DLL,或者EXE占用(线程非安全退出),它在安装的时候是不会呈现出来的,这样就有可能有客户端不完整更新,
这是个很糟糕的情况,我们要想办法避免这种情况出现。因此我们必须要新建一个project完全负责更新程序,他应该是脱离应用程序的独立程序。
项目结构如图
ClientLoader窗体就是更新的主程序,主要逻辑都在这种实现。
AppConfiger是自己创建的CONFIG读取类,实现可在我之前的博文http://www.cnblogs.com/vinnie520/archive/2012/03/13/2393337.html 中看到。
.ico是程序图标,因为是系统入口程序,所以把这个程序加上图标而不是加在实际应用程序上。
updateHelper是创建的静态类,用于下载补丁包,获取客户端\服务器版本号,检验是否需要更新之类的功能,代码如下
public static void DownloadFile(string URL, string filename, System.Windows.Forms.ProgressBar prog, System.Windows.Forms.Label label1)
{
Application.DoEvents();
float percent = 0;
try
{
System.Net.HttpWebRequest Myrq = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(URL);
System.Net.HttpWebResponse myrp = (System.Net.HttpWebResponse)Myrq.GetResponse();
long totalBytes = myrp.ContentLength;
if (prog != null)
{
prog.Maximum = (int)totalBytes;
}
System.IO.Stream st = myrp.GetResponseStream();
System.IO.Stream so = new System.IO.FileStream(filename, System.IO.FileMode.Create);
long totalDownloadedByte = 0;
byte[] by = new byte[1024];
int osize = st.Read(by, 0, (int)by.Length);
while (osize > 0)
{
totalDownloadedByte = osize + totalDownloadedByte;
System.Windows.Forms.Application.DoEvents();
so.Write(by, 0, osize);
if (prog != null)
{
prog.Value = (int)totalDownloadedByte;
}
osize = st.Read(by, 0, (int)by.Length);
percent = (float)totalDownloadedByte / (float)totalBytes * 100;
label1.Text = "当前补丁下载进度" + percent.ToString() + "%";
System.Windows.Forms.Application.DoEvents(); //必须加注这句代码,否则label1将因为循环执行太快而来不及显示信息
}
so.Close();
st.Close();
}
catch (System.Exception)
{
throw;
}
}
public static string GetServerVersion()
{
string result;
try
{
var url = AppConfiger.GetAppSettingValue("VersionUrl");
System.Net.HttpWebRequest Myrq = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(url);
System.Net.HttpWebResponse myrp = (System.Net.HttpWebResponse)Myrq.GetResponse();
System.IO.Stream st = myrp.GetResponseStream();
byte[] by = new byte[1024];
StreamReader m_streamReader = new StreamReader(st);
result = m_streamReader.ReadLine();
m_streamReader.Close();
st.Close();
}
catch (System.Exception)
{
throw;
}
return result;
}
public static bool IfNoNeedUpdate()
{
var clientVersion = GetClientVision();
var serverVersion = GetServerVersion();
var clVersion = clientVersion.Split('.');
var svVersion = serverVersion.Split('.');
for (int i = 0; i < clVersion.Count(); i++)
{
if (clVersion[i] != svVersion[i])
return false;
}
return true;
// return clientVersion >= serverVersion;
}
private static string GetClientVision()
{
try
{
string res = "";
string appDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
var path = Path.Combine(appDir, "version.ver");
StreamReader sr = new StreamReader(path, System.Text.Encoding.Default);
while (sr.Peek() >= 0)
{
res = sr.ReadLine();
}
sr.Close();
return res;
}
catch
{
return "";
}
}
public static void SetClientVision(string vision)
{
try
{
string appDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
var path = Path.Combine(appDir, "version.ver");
StreamWriter m_streamWriter = new StreamWriter(path);
m_streamWriter.Flush();
// 使用StreamWriter来往文件中写入内容
m_streamWriter.BaseStream.Seek(0, SeekOrigin.Begin);
// 把richTextBox1中的内容写入文件
m_streamWriter.Write(vision);
//关闭此文件
m_streamWriter.Flush();
m_streamWriter.Close();
}catch
{
}
}
以上可以看到,我们的版本使用文本文件控制,本地文件安装包中有version.ver文件,是个文本文件,其中只有一行内容就是版本号,服务器也有个类似的文件,是放在FTP服务器中,我们
使用的时候是通过HTTP下载的方式读取到其中内容,至于版本控制,可以用MSBUILD保证此版本跟客户端代码的AssemblyInfo里面的version保持一致。
这个图就是我们更新使用程序的窗体。下面2个BUTTON是测试时按钮,visiable是否的,所以就请忽略吧。
此程序所有逻辑都是在LOAD方法里面实现并完成,代码如下
private void Form1_Load(object sender, EventArgs e)
{
lableTitle.Text = "程序正在启动...";
this.Show();
Application.DoEvents();
bool notUpdate = false;
try
{
notUpdate = UpdateHelper.IfNoNeedUpdate();
}
catch (Exception)
{
MessageBox.Show("程序启动失败,请检查网络连接");
Application.Exit();
return;
}
if (notUpdate)
{
StartApp(); // 没有更新包
}
else
{
lableTitle.Text = "正在安装更新包...";
Application.DoEvents();
if (DownLoadPathFile())
{
string patchName = GetPatchFile();
if (!UpdateApp(patchName))
{
MessageBox.Show("更新失败,请重启后重新更新系统");
CloseMainForm();
}
UpdateHelper.SetClientVision(UpdateHelper.GetServerVersion());
Application.DoEvents();
StartApp();
}
else
{
StartApp();
}
}
}
所有更新思路都在以上代码。首先调用 notUpdate = UpdateHelper.IfNoNeedUpdate(); 检验是否需要更新
如果不需要更新则直接启动程序(StartApp),如果需要就先下载更新包((DownLoadPathFile()),然后进行更新(UpdateApp(patchName)),最后更新成功后修改客户端版本号
(UpdateHelper.SetClientVision(UpdateHelper.GetServerVersion());),最后启动程序。
更新程序:注意到,这里更新之前首先对2个关键文件做了file.delete方法用来检验文件是否被占用,从而实现安全更新
private bool UpdateApp(string path)
{
try
{
string exePath =// " \\ResidentFlash\\DeviceClient\\CP.Device.exe";
Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase),
"CP.Device.exe");
string dllPath =// " \\ResidentFlash\\DeviceClient\\CP.Device.exe";
Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase),
"CP.Device.Service.dll");
//更新之前删除EXE文件检查该文件是否被占用
if (File.Exists(dllPath))
File.Delete(dllPath);
if (File.Exists(exePath))
File.Delete(exePath);
ProcessStartInfo info = new ProcessStartInfo();
info.FileName = @"wceload.exe";
info.Arguments = @"/noui " + path;
//info.Arguments = path;
System.Diagnostics.Process process = new System.Diagnostics.Process();
process.StartInfo = info;
process.Start();
process.WaitForExit();
return true;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return false;
}
}
启动程序:
private void StartApp()
{
try
{
//先写死路径
string path =// "\\ResidentFlash\\DeviceClient\\CP.Device.exe";
Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase), AppName);
if (File.Exists(path))
{
Process process = new Process();
ProcessStartInfo info = new ProcessStartInfo();
info.FileName = path;
process.StartInfo = info;
process.Start();
CloseMainForm();
}
else
{
MessageBox.Show("没有找到主程序'" + AppName + "'!请重新安装程序!");
Application.DoEvents();
CloseMainForm();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
Application.DoEvents();
CloseMainForm();
}
}
下载程序:主要逻辑是在updateHelper中的download方法实现,这里只设置一下路径问题
private bool DownLoadPathFile()
{
string cabPath = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase), "Update");
if (!Directory.Exists(cabPath))
{
Directory.CreateDirectory(cabPath);
}
string cabFile = Path.Combine(cabPath, "patch.cab");
var patchUrl = AppConfiger.GetAppSettingValue("PatchUrl");
UpdateHelper.DownloadFile(patchUrl, cabFile, progressBar1, labeProcess);
return true;
}
OK,到此,基本上更新就可以完美实现,程序的入口设置成此程序,每次只要有更新之后就增加服务器版本号文件的版本,然后把编译好的补丁包放到对应的位置,每次程序开机时就会实现自动更新。
可以想一想,以上程序只完成了程序入口处检查版本,更新版本。如果我要在程序运行中发布客户端代码,那怎么样处理才是比较好呢...