在这篇文章中(参见 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是检查更新,调用下载模块,下载完安装的工具类。
AppVersionpackage 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;
}
}
DownloadServicepackage 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){
}
}
}
}
}
}
所有代码都已经贴出来了。
这个代码是完全可以用的,但是有些细节问题需要改进。