在这篇文章中(参见 android中如何下载文件并显示下载进度 )我们讲到了如何下载文件的问题,今天我介绍如何实现应用的自动更新,其中下载apk模块用到了前一篇文章中的知识。当然这只是一个实现的框架,你需要根据自己的需求是改变一些细节。
自动更新的原理
其实就是客户端将自己的版本号与服务端的版本号进行比对,版本号小于服务端则意味着有新版本,当然服务端的版本号是需要人工放上去的。
先看看效果:
为了代码的简洁,我这里用原生的alert对话框。
点击下载之后:
下载完成安装的界面我就不张贴了。
使用方法:
很简单,在需要检查更新的地方加入如下代码:
updateChecker.setCheckUrl( "http://jcodecraeer.com/update.php" );
updateChecker.checkForUpdates();
其中http://jcodecraeer.com/update.php返回的是服务器段存放的版本信息。服务端的版本信息分为三部分:
1.版本号;
2.版本描述;
3.存放apk的url(告诉客户端,在哪里下载新版本的apk);
以http://jcodecraeer.com/update.php返回的结果为例,返回的字符串具体是这样的:
{ "url" : "http://www.jcodecraeer.com/***.apk" , "versionCode" : "2" , "updateMessage" : "1.修改了app图标 2.设备详情的显示方式" }
这是一个json格式的字符串。
实现
有三个类:
其中
AppVersion是版本信息的模型类,基本上和服务端返回的东西是相对应的。
DownloadService是下载模块。
UpdateChecker是检查更新,调用下载模块,下载完安装的工具类。
AppVersion
package com.jcodecraeer.jcode.update;
public class AppVersion {
private String updateMessage;
private String apkUrl;
private int apkCode;
public static final String APK_DOWNLOAD_URL = "url" ;
public static final String APK_UPDATE_CONTENT = "updateMessage" ;
public static final String APK_VERSION_CODE = "versionCode" ;
public void setUpdateMessage(String updateMessage) {
this .updateMessage = updateMessage;
}
public String getUpdateMessage() {
return updateMessage;
}
public void setApkUrl(String apkUrl) {
this .apkUrl = apkUrl;
}
public String getApkUrl() {
return apkUrl;
}
public void setApkCode(int apkCode) {
this .apkCode = apkCode;
}
public int getApkCode() {
return apkCode;
}
}
DownloadService
package com.jcodecraeer.jcode.update;
import java.io.BufferedInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import com.jcodecraeer.PullToRefreshListView;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.IntentService;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.ResultReceiver;
import android.provider.ContactsContract;
import android.provider.MediaStore.Images;
import android.provider.MediaStore.Images.ImageColumns;
import android.support.v4.app.Fragment;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.GridView;
import android.widget.ShareActionProvider;
import android.widget.TextView;
import android.widget.Toast;
import android.view.ActionMode;
public class DownloadService extends IntentService {
public static final int UPDATE_PROGRESS = 8344;
public DownloadService() {
super ( "DownloadService" );
}
@Override
protected void onHandleIntent(Intent intent) {
String urlToDownload = intent.getStringExtra( "url" );
String fileDestination = intent.getStringExtra( "dest" );
ResultReceiver receiver = (ResultReceiver) intent.getParcelableExtra( "receiver" );
try {
URL url = new URL(urlToDownload);
URLConnection connection = url.openConnection();
connection.connect();
// this will be useful so that you can show a typical 0-100% progress bar
int fileLength = connection.getContentLength();
// download the file
InputStream input = new BufferedInputStream(connection.getInputStream());
OutputStream output = new FileOutputStream(fileDestination);
byte data[] = new byte[100];
long total = 0;
int count;
while ((count = input.read(data)) != -1) {
total += count;
// publishing the progress....
Bundle resultData = new Bundle();
resultData.putInt( "progress" ,(int) (total * 100 / fileLength));
receiver.send(UPDATE_PROGRESS, resultData);
output.write(data, 0, count);
}
output.flush();
output.close();
input.close();
} catch (IOException e) {
e.printStackTrace();
}
Bundle resultData = new Bundle();
resultData.putInt( "progress" ,100);
receiver.send(UPDATE_PROGRESS, resultData);
}
}
UpdateChecker
package com.jcodecraeer.jcode.update;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.zip.GZIPInputStream;
import org.json.JSONException;
import org.json.JSONObject;
import com.jcodecraeer.jcode.Code;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.os.ResultReceiver;
import android.util.Log;
import android.widget.Toast;
public class UpdateChecker{
private static final String TAG = "UpdateChecker" ;
private Context mContext;
//检查版本信息的线程
private Thread mThread;
//版本对比地址
private String mCheckUrl;
private AppVersion mAppVersion;
//下载apk的对话框
private ProgressDialog mProgressDialog;
private File apkFile;
public void setCheckUrl(String url) {
mCheckUrl = url;
}
public UpdateChecker(Context context) {
mContext = context;
// instantiate it within the onCreate method
mProgressDialog = new ProgressDialog(context);
mProgressDialog.setMessage( "正在下载" );
mProgressDialog.setIndeterminate( false );
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
mProgressDialog.setCancelable( true );
mProgressDialog.setOnCancelListener( new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
}
});
mProgressDialog.setOnDismissListener( new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
// TODO Auto-generated method stub
}
});
}
public void checkForUpdates() {
if (mCheckUrl == null ) {
//throw new Exception("checkUrl can not be null");
return ;
}
final Handler handler = new Handler(){
public void handleMessage(Message msg) {
if (msg.what == 1) {
mAppVersion = (AppVersion) msg.obj;
try {
int versionCode = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0).versionCode;
if (mAppVersion.getApkCode() > versionCode) {
showUpdateDialog();
} else {
//Toast.makeText(mContext, "已经是最新版本", Toast.LENGTH_SHORT).show();
}
} catch (PackageManager.NameNotFoundException ignored) {
//
}
}
}
};
mThread = new Thread() {
@Override
public void run() {
//if (isNetworkAvailable(mContext)) {
Message msg = new Message();
String json = sendPost();
Log.i( "jianghejie" , "json = " +json);
if (json!= null ){
AppVersion appVersion = parseJson(json);
msg.what = 1;
msg.obj = appVersion;
handler.sendMessage(msg);
} else {
Log.e(TAG, "can't get app update json" );
}
}
};
mThread.start();
}
protected String sendPost() {
HttpURLConnection uRLConnection = null ;
InputStream is = null ;
BufferedReader buffer = null ;
String result = null ;
try {
URL url = new URL(mCheckUrl);
uRLConnection = (HttpURLConnection) url.openConnection();
uRLConnection.setDoInput( true );
uRLConnection.setDoOutput( true );
uRLConnection.setRequestMethod( "POST" );
uRLConnection.setUseCaches( false );
uRLConnection.setConnectTimeout(10 * 1000);
uRLConnection.setReadTimeout(10 * 1000);
uRLConnection.setInstanceFollowRedirects( false );
uRLConnection.setRequestProperty( "Connection" , "Keep-Alive" );
uRLConnection.setRequestProperty( "Charset" , "UTF-8" );
uRLConnection.setRequestProperty( "Accept-Encoding" , "gzip, deflate" );
uRLConnection.setRequestProperty( "Content-Type" , "application/json" );
uRLConnection.connect();
is = uRLConnection.getInputStream();
String content_encode = uRLConnection.getContentEncoding();
if ( null != content_encode && ! "" .equals(content_encode) && content_encode.equals( "gzip" )) {
is = new GZIPInputStream(is);
}
buffer = new BufferedReader( new InputStreamReader(is));
StringBuilder strBuilder = new StringBuilder();
String line;
while ((line = buffer.readLine()) != null ) {
strBuilder.append(line);
}
result = strBuilder.toString();
} catch (Exception e) {
Log.e(TAG, "http post error" , e);
} finally {
if (buffer!= null ){
try {
buffer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is!= null ){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (uRLConnection!= null ){
uRLConnection.disconnect();
}
}
return result;
}
private AppVersion parseJson(String json) {
AppVersion appVersion = new AppVersion();
try {
JSONObject obj = new JSONObject(json);
String updateMessage = obj.getString(AppVersion.APK_UPDATE_CONTENT);
String apkUrl = obj.getString(AppVersion.APK_DOWNLOAD_URL);
int apkCode = obj.getInt(AppVersion.APK_VERSION_CODE);
appVersion.setApkCode(apkCode);
appVersion.setApkUrl(apkUrl);
appVersion.setUpdateMessage(updateMessage);
} catch (JSONException e) {
Log.e(TAG, "parse json error" , e);
}
return appVersion;
}
public void showUpdateDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
//builder.setIcon(R.drawable.icon);
builder.setTitle( "有新版本" );
builder.setMessage(mAppVersion.getUpdateMessage());
builder.setPositiveButton( "下载" ,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
downLoadApk();
}
});
builder.setNegativeButton( "忽略" ,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
}
});
builder.show();
}
public void downLoadApk() {
String apkUrl = mAppVersion.getApkUrl();
String dir = mContext.getExternalFilesDir( "apk" ).getAbsolutePath();
File folder = Environment.getExternalStoragePublicDirectory(dir);
if (folder.exists() && folder.isDirectory()) {
//do nothing
} else {
folder.mkdirs();
}
String filename = apkUrl.substring(apkUrl.lastIndexOf( "/" ),apkUrl.length());
String destinationFilePath = dir + "/" + filename;
apkFile = new File(destinationFilePath);
mProgressDialog.show();
Intent intent = new Intent(mContext, DownloadService.class);
intent.putExtra( "url" , apkUrl);
intent.putExtra( "dest" , destinationFilePath);
intent.putExtra( "receiver" , new DownloadReceiver( new Handler()));
mContext.startService(intent);
}
private class DownloadReceiver extends ResultReceiver{
public DownloadReceiver(Handler handler) {
super (handler);
}
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
super .onReceiveResult(resultCode, resultData);
if (resultCode == DownloadService.UPDATE_PROGRESS) {
int progress = resultData.getInt( "progress" );
mProgressDialog.setProgress(progress);
if (progress == 100) {
mProgressDialog.dismiss();
//如果没有设置SDCard写权限,或者没有sdcard,apk文件保存在内存中,需要授予权限才能安装
String[] command = { "chmod" , "777" ,apkFile.toString()};
try {
ProcessBuilder builder = new ProcessBuilder(command);
builder.start();
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive" );
mContext.startActivity(intent);
} catch (Exception e){
}
}
}
}
}
}
所有代码都已经贴出来了。
这个代码是完全可以用的,但是有些细节问题需要改进。