from:https://blog.csdn.net/hulinhulin/article/details/46839107
軟件自動更新解決放案及QT實現...1
1 文件的版本控制-XML.2
2 更新程序的實現...2
2.1 界面設置...2
2.2 程序功能...3
2.2.1 下載網絡數據...3
2.2.2 XML文件的分析...6
2.2.3 下載XML文件的DownLoadXML函數...8
2.2.4 返回指定XML文件中的name版本號...8
2.2.5 返回指定XML文件中的name版本號...10
2.2.6 比較兩個XML文件CheckUpdateFiles.11
2.2.7 下載文件DownLoadUpdateFiles.13
2.2.7 退出當前程序,并啟動指定的程序...16
3 更新程序的啟動...16
3.1 從主程序啟動更新程序...16
3.2 主程序的關閉...17
3.3 更新程序關閉時啟動主程序...17
4 程序調試...17
軟件自動更新解決放案及QT實現
需要考慮解決的問題:
1)?需要知道哪些文件需要更新,哪些不需要;
2)?從哪里下載更新文件;
3)?將舊的文件用新的文件替換掉(包含版本控制文件);
4)?更新完畢后重新啟動主程序;
?
第一個問題,可以為程序所使用的文件都設定一個版本號,版本號都記錄在一個 XML 文件中,升級時,檢查最新程序的版本控制文件和當前的版本控制文件,最新版本號較大時,表示該文件需要更新。如果一個文件不再需要了,則將該文件的版本信息從最新的版本控制文件中刪除,通過對比控制文件,就知道該文件不再需要了,可以將之刪除;
第二個問題,最新的版本控制文件需要放在一個可供方便下載的地方,例如FTP或者一個固定的IP;
第三個問題,通過對比新的版本控制文件和舊的版本控制文件來確定哪些需要替換或者刪除;
第四個問題,更新程序運行完后,啟動主程序即可,需要一個標識來說明是否更新完成;
?
按照以上的思路,下面對每一個步驟結合程序進行詳細地闡述,軟件使用QT5.1實現。
?
1?文件的版本控制-XML
下面是使用XML文件來表示的版本控制文件:
<?xml version="1.0"encoding="utf-8"?>?
<filelist>?
?<filename="qico.dll" dir="imageformats"version="1.0"/>
?<filename="qminimal.dll" dir="imageformats"version="1.0"/>
…..
?<file name="main.exe"version="1.0"/>
</filelist>?
Name表示文件的名稱;
dir表示所在的目錄(相對目錄);
version表示當前文件的版本;
2?更新程序的實現
更新程序使用QT5.1來實現,最終生成一個可執行文件(exe文件)。新建工程時選擇QT GUI應用,其他都默認,工程名為Updater。
2.1?界面設置
在構造函數中設置界面。
應用程序在屏幕中間:
QDesktopWidget *deskdop = QApplication::desktop();
this->move((deskdop->width() -this->width())/2, (deskdop->height() - this->height())/2);
?
無標題欄:
this->setWindowFlags(Qt::FramelessWindowHint);//沒有標題欄
?
隱藏菜單欄和工具欄??
this->ui->menuBar->hide();
this->ui->mainToolBar->hide();
?
固定高和寬:
this->setFixedSize(400,200);
???
設置背景顏色(兩種方法都可以)
???//this->setStyleSheet("QMainWindow{background:rgb(240,250,250)}");
QPalette pal;
pal.setColor(QPalette::Background,QColor(255,245,225) );
this->setPalette(pal);
this->setAutoFillBackground(true);
2.2?程序功能
??? 程序功能包括從網絡下載數據,分析XML文件,比較當前的XML及下載的XML文件,并最終確定哪些文件需要更新或者添加
2.2.1?下載網絡數據
新建類CHttpDownLoadFile,類功能:從指定網絡中下載指定的文件,并且存儲到指定的本地文件目錄中。
主要的成員函數及槽函數:
public slots:
???void ReplyNewDataArrived();//響應m_netReply有新的數據到達
???void ReplyFinished();//響應數據接收完成
public:
???QNetworkAccessManager *m_netAccessManager;//網絡參數
???QNetworkReply *m_netReply;
?
???QUrl m_urlAdress;//網絡地址
???QString m_strFileName;//需要下載的文件名
???QString m_strDir;//文件的存儲位置
?
???QFile *m_file;//下載的文件
?
??? ???qint64m_nReceived;//下載文件時,已經接收的文件大小和總共大小
???qint64m_nTotal;
主要函數及功能:
a.構造函數
CHttpDownloadFile(QString url,QStringfileName,QString dir,QObject *parent = 0);
url表示文件的網絡地址;
filename表示文件名;
dir表示文件存儲路徑
??? 如果fileName不為空,那么文件名使用fileName,否則從url提取(注:不需要加后綴)。如果dir不為空,那么將文件存儲到dir指向的文件夾中,否則存儲在默認路徑中(即與可執行文件在同一個文件夾中),如果文件夾不存在,那么會創建,dir舉例:c:/temp/,或者c:/temp,如果前面不加盤符,那么將會在默認文件夾中創建。
b. 開始下載文件的函數:void DownLoadFile()
m_netReply=m_netAccessManager->get(QNetworkRequest(m_urlAdress));
connect(m_netReply,SIGNAL(readyRead()),
this,SLOT( ReplyNewDataArrived()) );//當有新數據到達時就會觸發此信號
connect(m_netReply, SIGNAL(finished()),
this,SLOT( ReplyFinished()) );//完成數據接收后發送此信號
connect(m_netReply, SIGNAL(error(QNetworkReply::NetworkError)),this, SLOT( ReplyError(QNetworkReply::NetworkError)) );//出現錯誤時發送此信號;
connect(m_netReply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(ReplyDownloadProgress(qint64,qint64) ) );//用來提示文件下載進度
?
???/*************存儲文件的檢測及使用************/
???if( m_strFileName.isEmpty() )//文件名
??? {
???????QFileInfo fileInfo(m_urlAdress.path());
???????m_strFileName = fileInfo.fileName();
??? }
?
???if( !m_strDir.isEmpty() )//文件夾
??? {
???????QDir directory( m_strDir );
?
???????if( !directory.exists() )//沒有此文件夾,則創建
???????{
???????????directory.mkpath( m_strDir );
???????}
?
???????m_strFileName = m_strDir + "/"+m_strFileName;//添加/是為了防止用戶名沒有加/,因為對于文件夾來說兩個/都會當成一個/
??? }
???if( QFile::exists(m_strFileName) )//如果文件已經存在,那么刪除
??? {
???????QFile::remove(m_strFileName);
??? }
???m_file = new QFile( m_strFileName );
???if (!m_file->open(QIODevice::WriteOnly))
??? {
???????qDebug()<<"不能存儲文件:"<<m_strFileName;
???????delete m_file;
???????m_file = NULL;
???????return;
??? }
c.槽函數void ReplyFinished(),當下載網絡數據結束時響應此函數,主要是釋放資源
???m_netAccessManager->deleteLater();
???m_netReply->deleteLater();
???m_file->close();
???m_file->deleteLater();
?
d.槽函數voidReplyNewDataArrived()—當數據到達時調用此函數,并存儲到指定的文件中
??? if(m_file)
??? {
??????? m_file->write(m_netReply->readAll());
??????? m_file->flush();//注意需要刷新
??? }
??? else
??? {
??????? qDebug()<<m_netReply->readAll();
??? }
2.2.2 XML文件的分析
??? QFile file(filename);
??? if(!file.open(QIODevice::ReadOnly | QFile::Text)) {
???????qDebug()<<"open for read error..." ;
??? }
??? QString errorStr;
??? int errorLine;
??? int errorColumn;
??? QDomDocument doc;
???if(!doc.setContent(&file, false, &errorStr, &errorLine,&errorColumn)) {
???????qDebug()<<"setcontent error..." ;
??????? file.close();
??? }
??? file.close();
??? QDomElement root =doc.documentElement();
??? if (root.tagName() !="filelist")
??? {
??????qDebug()<<"root.tagname != filelist.." ;
??? }
??? else
??? {
??????? QDomNodeListnodeList = root.elementsByTagName("file");
??????? for(inti=0;i<nodeList.size();i++)
??????? {
???????? ???qDebug()<<nodeList.at(i).toElement().attribute("name")
?????????????????????<<nodeList.at(i).toElement().attribute("dir")
???????????????????????<<nodeList.at(i).toElement().attribute("version");
??????? }
}???????
}
這里只是將XML文件的內容輸出,詳細的代碼在工程Updater的其他函數中。
2.2.3?下載XML文件的DownLoadXML函數
函數下載XML文件,當XML文件下載完成時發送下載完成信號,提示進行下一步操作。
????/**從網頁下載XML版本控制文件,里面記錄了最新的文件版本**/
????QStringstrCurrentDir=QDir::currentPath();//當前程序運行路徑
????QStringstrDownLoad=strCurrentDir+"/download/";//存放下載文件的路徑
?
????QDirdirectory(strDownLoad);//如果路徑不存在,則創建
????if(!directory.exists())
????{
???????directory.mkpath(strDownLoad);
????}
?
????m_httpXML=newCHttpDownloadFile("http://www.***.com/download/**.xml","",strDownLoad,this);//調用下載文件的類
????connect(m_httpXML,SIGNAL(DownloadFinishedSignal()),this,SLOT(ReplyHttpFinished()));//發生錯誤時一樣會發送此信號
m_httpXML->DownLoadFile();
在ReplyHttpFinished()函數中需要錯誤處理。
實例化m_httpXML時,需要將parent設置為this,這樣當程序結束時,會自動釋放。
2.2.4?返回指定XML文件中的name版本號
????/***********在xml中查找名字與name相同的元素,并返回版本號**
?????*xml:xml文件的路徑;
?????*name:需要查找的元素名稱;
?????*
?????*return:QString:版本號,如果有則返回,沒有則為空
?????************************************************/
?QStringGetElementVersion(QStringxml,QStringname);
?
實現:
GetElementVersion(QStringxml,QStringname)
{
????QStringresult="";
????if(xml.isEmpty()||name.isEmpty())
????{
???????qDebug()<<"名稱或者xml文件路徑為空";
???????returnresult;
????}
?
????if(!QFile::exists(xml))
????{
???????qDebug()<<"xml文件不存在";
???????returnresult;
????}
?
????QFilefile(xml);
????if(file.open(QIODevice::ReadOnly|QFile::Text))//文件打開成功
????{
???????QDomDocumentdoc;
???????if(doc.setContent(&file))
???????{
???????????QDomElementroot=doc.documentElement();
???????????if(root.tagName()=="filelist")
???????????{
????????????????inti=0;
????????????????QDomNodeListnodeList=root.elementsByTagName("file");
????????????????for(;i<nodeList.size();i++)
????????????????{
????????????????????QStringtempName???=nodeList.at(i).toElement().attribute("name");
????????????????????//QStringdir????=nodeList.at(i).toElement().attribute("dir");
????????????????????QStringversion=nodeList.at(i).toElement().attribute("version");
?
????????????????????if(name==tempName)
????????????????????{
????????????????????????qDebug()<<"find!"<<name;
????????????????????????result=version;
????????????????????????break;
????????????????????}
????????????????}
?
????????????????if(i==nodeList.size())
????????????????{
????????????????????qDebug()<<"can'tfind!"<<name;
????????????????}
???????????}
???????????else
???????????{
????????????????qDebug()<<"root.tagname!=filelist..";
???????????}
???????}
???????else
???????{
???????????qDebug()<<"setcontenterror...";
???????}
?
???????file.close();
????}
????else
????{
???????qDebug()<<"openforreaderror...";
????}
?
????returnresult;
}
2.2.5?返回指定XML文件中的name版本號
????/**************比較兩個版本號*******************
?????*@params:
?????*?v1,v2:兩個版本號,格式:1.1.0,不能為空
?????*
?????*@return:
?????*?true:如果兩個版本號碼相同;
?????*?false:兩個版本號不同
?????*
?????*說明:暫時只比較了是否相同
?????*******************************************/
?boolCheckVersion(QStringv1,QStringv2);
實現比較簡單,就不貼代碼了。
2.2.6?比較兩個XML文件CheckUpdateFiles
通過比較兩個XML文件來確定需要下載的文件。
/************比較兩個XML文件****************
?????*@params:
?????*?name1,name2:兩個XML文件,不能為空,并且文件需要存在,name1必須是最新的xml文件(剛剛下載下來的),name2是本地的xml文件;
?????*
?????*@return:
?????*0-someerrorhappens,elsesuccess
?????*將需要更新的文件名存儲到m_listFileName中
?????*路徑存儲到m_listFileDir中
?????*
?????*注意:m_listFileDir和m_listFileName的個數是一樣的,
?????*????如果沒有元素表示所有的文件都不用更新
?????******************************************/
?intCheckUpdateFiles(QStringname1,QStringname2);
?
實現:
CheckUpdateFiles(QStringname1,QStringname2)
{
????m_listFileDir.clear();
????m_listFileName.clear();
?
????if(name1.isEmpty()||name2.isEmpty())return0;
?
????if(QFile::exists(name2))
????{
???????if(QFile::exists(name1))
???????{
???????????m_strTip="檢查需要更新的文件...";
?
???????????QFilefile(name1);
???????????if(file.open(QIODevice::ReadOnly|QFile::Text))//文件打開成功
???????????{
????????????????QStringerrorStr;
????????????????interrorLine;
????????????????interrorColumn;
?
????????????????QDomDocumentdoc;
????????????????if(doc.setContent(&file,false,&errorStr,&errorLine,&errorColumn))
????????????????{
????????????????????QDomElementroot=doc.documentElement();
????????????????????if(root.tagName()=="filelist")
????????????????????{
????????????????????????QDomNodeListnodeList=root.elementsByTagName("file");
????????????????????????for(inti=0;i<nodeList.size();i++)
????????????????????????{
????????????????????????????QStringname???=nodeList.at(i).toElement().attribute("name");
????????????????????????????QStringdir????=nodeList.at(i).toElement().attribute("dir");
????????????????????????????QStringversion=nodeList.at(i).toElement().attribute("version");
?
????????????????????????????QStringversionDownload=GetElementVersion(name2,name);//獲取本地xml文件對應文件(name)的版本信息
????????????????????????????if(versionDownload.isEmpty())//本地XML沒有此文件:下載,并放到相應的目錄中
????????????????????????????{
????????????????????????????????m_listFileDir.append(dir);
????????????????????????????????m_listFileName.append(name);
????????????????????????????}
????????????????????????????else
????????????????????????????{
????????????????????????????????/**檢查版本,如果本地版本低于下載的版本,則下載**/
????????????????????????????????if(!CheckVersion(version,versionDownload))
???????????????????????? ???????{
????????????????????????????????????m_listFileDir.append(dir);
????????????????????????????????????m_listFileName.append(name);
????????????????????????????????}
????????????????????????????????else
????????????????????????????????{
?????????????????????????????????????qDebug()<<name<<"文件是最新版本,不需要更新";
????????????????????????????????}
????????????????????????????}
????????????????????????}
?
????????????????????????return1;//此時要退出,避免關閉程序
????????????????????}
????????????????????else
????????????????????{
????????????????????????m_strTip="XML內容錯誤!";
????????????????????????return0;
????????????????????}
????????????????}
????????????????else
????????????????{
????????????????????qDebug()<<"setcontenterror...";
????????????????????return0;
????????????????}
?
????????????????file.close();
???????????}
???????????else
???????????{
????????????????m_strTip="不能打開更新文件!";
????????????????return0;
???????????}
???????}
???????else
???????{
???????????m_strTip="下載更新文件錯誤!";
???????????return0;
???????}
????}
????else
????{
???????m_strTip="本地的更新文件不存在!";
???????return0;
????}
}
2.2.7?下載文件DownLoadUpdateFiles
? 下載需要的文件,完成后啟動主程序 。
/********下載最新的版本的文件,并替換或者增加*********************
?????*舊XML中有的文件,新XML沒有的,此文件不做處理。
?????*下載信息由m_listFileName和m_listFileDir提供
?????*****************************************************/
???voidDownLoadUpdateFiles();
實現:
DownLoadUpdateFiles()
{
????QStringstrServer="http://www.***.com/download/";//需要下載的文件存儲位置
????QStringstrCurrentDir=QDir::currentPath();//當前程序運行路徑
?
????if(m_listFileDir.isEmpty()||m_listFileDir.isEmpty())
????{
???????qDebug()<<"沒有需要下載的文件1";
???????ExitApp(strCurrentDir+"/main.exe");
???????return;
????}
?
????m_strTip="開始下載更新文件...";
?
?
?
????m_bIsFinished=false;
?
????for(inti=0;i<m_listFileName.size();i++)
????{
???????m_strTip="正在下載文件"+m_listFileName.at(i);
???????m_progUpdate->setValue(100*i/m_listFileName.size());
?
???????/**放置下載的文件的路徑**/
???????QStringstrPlaceDir=strCurrentDir+"/download/"+m_listFileDir.at(i);
???????QDirdirectory(strPlaceDir);//如果路徑不存在,則創建
???????if(!directory.exists())directory.mkpath(strPlaceDir);
?
???????QStringstrFileDirServer=strServer+m_listFileDir.at(i)+"/"+m_listFileName.at(i);//文件在服務器中的存儲位置
???????CHttpDownloadFile*http=newCHttpDownloadFile(strFileDirServer,"",strPlaceDir,this);//調用下載文件的類
???????http->DownLoadFile();
???????while(!http->m_bIsFinished)
???????{
???????????if(http->m_nTotal==-1)
???????????{
????????????????m_progDownload->setValue(1);
???????????}
???????????else
???????????{
????????????????m_progDownload->setValue(100*http->m_nReceived/http->m_nTotal);
???????????}
???????????QCoreApplication::processEvents();
???????}
?
???????m_strTip="文件"+m_listFileName.at(i)+"下載完成";
?
?
???????/**將下載好的文件復制到主目錄中,先刪除原先的文件**/
???????QStringstrLocalFileName=strCurrentDir+"/"+m_listFileDir.at(i)+"/"+m_listFileName.at(i);
???????if(QFile::exists(strLocalFileName))QFile::remove(strLocalFileName);
?
???????QDirdirectory1(strCurrentDir+"/"+m_listFileDir.at(i));//如果路徑不存在,則創建
???????if(!directory1.exists())directory1.mkpath(strCurrentDir+"/"+m_listFileDir.at(i));
?
???????QFile::copy(strPlaceDir+"/"+m_listFileName.at(i),strLocalFileName);
????}
?
????m_bIsFinished=true;
????m_strTip="更新完成!";
?
????/**替換舊的xml文件**/
????QStringstrNewXML=strCurrentDir+"/download/**.xml";//最新的XML文件
????QStringstrOldXML=strCurrentDir+"/"+"**.xml";//舊的XML文件
?
????QFile::remove(strOldXML);
????QFile::copy(strNewXML,strOldXML);
?
????ExitApp(strCurrentDir+"/main.exe");
}
2.2.7?退出當前程序,并啟動指定的程序
????/********退出當前程序,并且啟動需要的程序*************************
?????*name:需要啟動的程序,可以使用相對位置
?????*程序不能使用exit(0),會發生線程錯誤,這里使用this->close()函數
?????*****************************************************/
實現:
ExitApp(QStringname)
{
????if(!name.isEmpty())
????{
???????/**運行主程序,并且退出當前更新程序(說明:主程序在上上一級目錄中)**/
???????if(!QProcess::startDetached(name))//啟動主程序,主程序在其上一級目錄
???????{
???????????QMessageBox::warning(this,"警告信息","啟動主程序錯誤!\n可能主程序不存在或者被破壞!\n解決辦法:重新安裝程序!");
???????}
????}
?
????this->close();
}
3?更新程序的啟動
??? 更新程序是獨立的可執行文件,所以在啟動主程序時,首先啟動更新程序(注:網絡檢查放在了更新程序中),然后關閉主程序,更新完成后,再重新啟動主程序。
3.1?從主程序啟動更新程序
??? 首先需要判斷是否需要啟動更新程序,避免更新后再次啟動更新程序;
??? 將是否需要啟動更新程序的參數放在軟件的初始參數文件中,例如params.txt文件中,初始值為true,表示需要啟動更新程序。主程序啟動時,先獲取里面的參數,并判斷是否需要啟動更新程序。完成更新后,將此參數設置為false,表示下次再次啟動主程序時不需更新,當點擊關閉主程序時再次將此參數設置為true,這樣就可以保證每次啟動時都會檢查更新;
啟動更新程序調用QProcess::startDetached()方法,啟動外部程序后立即返回(也就是主程序也運行),即使主程序關閉,啟動的程序也會運行,本程序使用這個方法,因為更新后。
注:如果調用QProcess::execute函數,此方法可以阻塞主程序的運行,直到啟動的程序關閉,因為需要關閉主程序,還要重新啟動更新后得程序,所以不能使用這個方法。
3.2?主程序的關閉
??? 如果QProcess::startDetached方法返回true,則關閉主程序,調用exit(0),或者quit()方法;
3.3?更新程序關閉時啟動主程序
更新程序結束時調用QProcess::startDetached("main.exe"),主程序和更新程序在同一目錄。但是在關閉更新程序的時候使用exit會發送錯誤,錯誤原因:會出現夸線程發送信號的情況,這是不容許的。
當所有文件都下載完成時,使用close函數。
4?程序調試
需要調試以下功能:
(1)主程序啟動時,檢測是否需要啟動更新程序, 檢測params.cq文件里面的is_update參數的值,如果為1則啟動,否則不啟動(代碼在CLoginBox的構造函數中);
(2)如果is_update=1,那么啟動更新程序,更新程序啟動后,is_update設置為0;
(3)主程序退出時,將is_update設置為1,即每次重新啟動主程序的時候都需要檢查更新;
(4)正確下載xml文件,存儲位置:與主程序同目錄下的download文件 夾中(特別提醒:由于更新程序是從主程序中調用的,所以在更新程序中調用獲取當前運行程序的目錄是得到的是主程序的的目錄,但是如果更新程序單獨運行的話,得到的目錄是和更新程序目錄一樣的,所以最好的方式就是講主程序和更新程序放在同一個目錄中);
(5)是否正確調用了更新程序里面的CheckUpdateFiles函數,主要是檢查是否輸入正確的xml文件目錄;
(6)下載的更新文件是否正確放置;
(7)下載完后舊的XML是否換成新的XML;
(8)運行主程序的電腦或者服務端沒有網絡的情況;