在智能手机上,应用自升级是很常见的功能。研究了一天,基本案例已经出炉。
首先说一下自动升级的基本流程:
1.首先,这肯定是一个需要网络交互的过程,用户打开应用之后会自动给服务器发一个请求,然后服务器给予一个版本信息,
客户端得到后通过解析是否有新的版本。然后根据相关信息提示用户是否更新。
2.在服务器端,我们通过从客户端发来的请求生成一个xml文件传送给客户端,请求的信息包括客户端的版本和应用名称,把这些信息和服务器上的数据库对照,然后给出相关的回馈信息。
3.在客户端,更新检查同过线程进行,解析从服务器端得到的xml文件后半段是否有新版本,如果有提醒用户更新,如果没有,结束线程。
具体实现:
服务器端采用apache和php,mysql。
这里我先在mysql数据表中存一条数据:
然后写一个提供客户端访问的php脚本:
autoupdate.php:
<?php
$app_version = $_GET['version']; //得到当前版本号
$App_name = $_GET['name']; //得到应用名称
$Host = "localhost"; //链接数据库的主机名
$UName = "root"; //进入数据库的用户名
$Password = "123456"; //密码
$DBName = "app_update"; //数据库名
$TBName = "version"; //表名
$conn = mysql_connect($Host,$UName,$Password)or die("connect failed"); //链接
mysql_select_db($DBName,$conn);//选择数据库
$sql = "select * from version where version_id>$app_version and name='$App_name' order by id limit 0,1"; //查询数据库中是否有比当前版本更新的应用
$result = mysql_query($sql,$conn);
$success = 0;
while($row=mysql_fetch_array($result))
{
$success = 1; //查询不为空,说明有更新的版本
list($id,$name,$version,$url) = $row; //取得版本信息
}
//create xml file生成xml文件
header("Content-Type:text/plain");
$doc = new DOMDocument('1.0','utf-8');
$root = $doc->createElement("root");
$doc->appendChild( $root );
$update = $doc->createElement( "update" );
$root->appendChild($update);
$update->setAttribute("success",$success);
if($success){ //如果有更新版本,则把新版本信息发给客户端
$item = $doc->createElement("item");
$update->appendChild($item);
$item->setAttribute("version",$version);
$item->setAttribute("name",$name);
$send_url = $doc->createTextNode($url);
$item->appendChild($send_url);
}
echo $doc->saveXML();
?>
当然,我们需要把指定的应用放到上传到服务器目录下,设定数据库中的url。
从浏览器中访问这个php文件:
再来看看客户端:
还是从效果图说起吧:
MainActivity.java:
package com.xluo.test.autoupdate;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.util.Log;
import android.view.Menu;
import android.widget.Toast;
public class MainActivity extends Activity {
private static final String UPDATE_FILE = "/data/data/update.apk"; //下载后存储的地方
private static final String UPDATE_FILE_TMP = "/data/data/update.apk.tmp";//临时文件
private Toast mToast;
private static String UPDATE_URL = "http://10.16.6.163/test/autoupdate.php?version=20121122&name=AutoUpdate";
/*
* 请求url,为了方便,这里的version设为20121122,在数据库中是20121123,说明有最新版本
*
*/
private static UpdateInfo ui;
private static final String TAG = "MainActivity";
private Handler mHandler = new Handler(){ //处理线程中解析xml文件后的数据
public void handleMessage(Message msg){
ui = (UpdateInfo)msg.obj;
if(ui.URL != ""){
new AlertDialog.Builder(MainActivity.this)
.setTitle(R.string.dialog_title)
.setMessage(R.string.dialo_text)
.setPositiveButton(R.string.dialog_sure, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub
UpdateDownloadTask udt = new UpdateDownloadTask();
udt.execute(ui.URL);
}
})
.setNegativeButton(R.string.dialog_cancel,null)
.show();
}
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG,"Start check update version");
AppUpdate au = new AppUpdate(UPDATE_URL,mHandler);
au.start();//应用打开后开启检测版本线程
mToast = Toast.makeText(MainActivity.this, R.string.update_download_pending, Toast.LENGTH_SHORT);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
private class UpdateDownloadTask extends AsyncTask<String, Integer, Boolean> { //下载类
@Override
protected Boolean doInBackground(String... params) {
// TODO Auto-generated method stub
HttpGet httpRequest = new HttpGet(params[0]);
HttpClient httpClient = new DefaultHttpClient();
long length = -1;
try {
HttpResponse httpResponse = httpClient.execute(httpRequest);
HttpEntity entity = httpResponse.getEntity();
length = entity.getContentLength();
Log.i("UpdateDownloadTask", "begin length:" + length);
InputStream inputStream = entity.getContent();
byte[] b = new byte[4 * 1024];
int readedLength = -1;
File file = new File(UPDATE_FILE_TMP);
if (file.exists()) {
file.delete();
}
OutputStream outputStream = MainActivity.this.openFileOutput(file.getName(),Context.MODE_WORLD_READABLE);
long count = 0;
long show_progress = 0;
long progress_old = 0;
while ((readedLength = inputStream.read(b)) != -1
&& !this.isCancelled()) {
outputStream.write(b, 0, readedLength);
count += readedLength;
show_progress = (count*100/length);
if(progress_old != show_progress){
publishProgress((int)show_progress);
progress_old = show_progress;
}
}
inputStream.close();
outputStream.close();
if (!this.isCancelled()) {
File update_file = new File(UPDATE_FILE);
if (update_file.exists()) {
update_file.delete();
}
file.renameTo(update_file);
} else {
if (file.exists()) {
file.delete();
}
return false;
}
} catch (ClientProtocolException e) {
// TODO Auto-generated catch block
Log.e("UpdateDownloadTask",
"ClientProtocolException Error:" + e.toString());
return false;
} catch (IOException e) {
// TODO Auto-generated catch block
Log.e("UpdateDownloadTask", "IOException Error:" + e.toString());
return false;
}
return true;
}
@Override
protected void onProgressUpdate(Integer... values) {
int progress = values[0];
Log.d("UpdateDownloadTask","publishProgress"+progress);
mToast.setText(getString(R.string.update_download_pending)+progress+"%");
mToast.show();
}
@Override
protected void onPostExecute(Boolean success) {
if (true == success) {
Intent launchIntent = new Intent(Intent.ACTION_VIEW);
Uri path = Uri.fromFile(new File(UPDATE_FILE));
String mimetype = "application/vnd.android.package-archive";
launchIntent.setDataAndType(path, mimetype);
launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try{
startActivity(launchIntent);
}catch (ActivityNotFoundException ex)
{
Log.e("UpdateDownloadTask","ActivityNotFoundException Error:" + ex.toString());
}
MainActivity.this.finish();
} else {
Log.e("UpdateDownloadTask","Download failed");
}
}
}
}
AppUpdate.java:
package com.xluo.test.autoupdate;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
public class AppUpdate extends Thread{
private String updateURL;
private Handler mHandler;
public AppUpdate(String url,Handler h){
this.updateURL = url; //访问地址
this.mHandler = h; //处理消息的handler
}
public void run(){
try{
URL url = new URL(updateURL);
URLConnection con = url.openConnection();
if (((HttpURLConnection) con).getResponseCode() != HttpURLConnection.HTTP_OK) {
return ;
}
InputStream is = con.getInputStream();
UpdateInfo info = xmlAnalysis.getXML(is); //得到解析后的xml中的信息
is.close();
if(info != null){
Message msg = new Message();
msg.obj = info;
mHandler.removeCallbacksAndMessages(msg);
mHandler.sendMessage(msg); //send给MainActivity中的handler
}
}catch(InterruptedException e){
e.printStackTrace();
}
catch(Exception e){
e.printStackTrace();
}
}
}
解析xml文件的类:
xmlAnalysis.java:
package com.xluo.test.autoupdate;
import java.io.InputStream;
import org.xmlpull.v1.XmlPullParser;
import android.util.Log;
import android.util.Xml;
public class xmlAnalysis {
private static final int UNKNOWN = 0;
private static final int FORCE = 1;
private static UpdateInfo ui;
public static UpdateInfo getXML(InputStream is) throws Exception{
ui = new UpdateInfo();
XmlPullParser parser = Xml.newPullParser();
parser.setInput(is, "utf-8");
int event = parser.getEventType();
while(event != XmlPullParser.END_DOCUMENT){
switch(event){
case XmlPullParser.START_DOCUMENT:
break;
case XmlPullParser.START_TAG:
if("update".equals(parser.getName())){
ui.SUCCESS = parser.getAttributeValue("", "success");
}else if("item".equals(parser.getName())){
ui.NAME = parser.getAttributeValue(null, "name");
ui.VERSION = parser.getAttributeValue(null,"version");
}
break;
case XmlPullParser.TEXT:
ui.URL = parser.getText();
default:
break;
}
event = parser.next();
}
return ui;
}
}
还有一个装载升级信息的类就不写了,很简单。
在AndroidManifest.xml中把相关权限打开,这里设计到上网,读写文件,下载权限。
这里应用没有存储版本信息的功能,只是做了一个测试。那样也很容易,我们可以在安装应用的时候把版本信息用sharedpreference存储,它便于存储一些简单的信息。然后每次在打开应用的时候都做存和取的操作就ok了。