最近在完善自己开发的移动办公系统,为系统添加了检测版本以及更新版本的功能,检测版本实现很简单,只需要对比服务器返回的版本号就知道需不需要更新,而自动更新功能则相对复杂点,这里记录下。
先来看下后端是如何下载应用程序的。
/*
* app下载
*/
@Override
public String appDownload(Params params,HttpServletResponse response) {
JSONObject jsonObject = new JSONObject();
//从参数中获取文件路径和名称(MD5加密)
String path = ((HttpServletRequest)params.get("Request")).getSession().getServletContext().getRealPath("apk\\release");
String appName = FileUtils.getAllFileName(path).get(0);
//String downloadPath = "C:\\oasystemapp\\oasystem.apk";
String downloadPath = path + "\\" + appName;
logger.info("下载路径:"+path);
logger.info("下载文件名称:"+appName);
//得到要下载的文件
File file = new File(downloadPath);
if (!file.exists()) {
jsonObject.put("code", CodeState.ERROR_CODE);
jsonObject.put("message", "您要下载的资源已被删除!");
return jsonObject.toString();
}
String filename = null;
try {
//转码,免得文件名中文乱码
filename = URLEncoder.encode("oasystem","UTF-8");
//设置文件下载头
response.addHeader("Content-Disposition", "attachment;filename=" + appName);
//1.设置文件ContentType类型,这样设置,会自动判断下载文件类型
response.setContentType("multipart/form-data");
response.setContentLength((int)file.length());//设置文件大小(需要转换成int类型)
// 读取要下载的文件,保存到文件输入流
FileInputStream in = new FileInputStream(downloadPath);
// 创建输出流
OutputStream out = response.getOutputStream();
// 创建缓冲区
byte buffer[] = new byte[1024];
int len = 0;
//循环将输入流中的内容读取到缓冲区当中
while((len = in.read(buffer)) > 0){
out.write(buffer, 0, len);
}
//关闭文件输入流
in.close();
// 关闭输出流
out.close();
}
catch(Exception e) {}
jsonObject.put("code", CodeState.SUCCESS_CODE);
jsonObject.put("message", "");
return jsonObject.toString();
}
流程很简单,首先构建下载文件的路径,接着对文件名称进行以UTF-8的格式进行编码,接着设置请求头相关的信息,这里注意下需要设置文件的大小,因为我们更新app是需要显示进度条的,必须要知道文件总共有多大。接着设置缓冲区大小等等,然后进行文件下载。
在Android使用progressDialog来显示进度条,在AsycTask中进行网络请求,并且通过AsycTask来实现在子线程中更新进度UI来显示下载进度,具体用法如下。
public class APPVersionCodeUtils {
// 外存sdcard存放路径
private static final String FILE_PATH = Environment.getExternalStorageDirectory()+"/"+"update";
// 下载应用存放全路径
private static final String FILE_NAME = FILE_PATH + "/" + "oasystem.apk";
// 准备安装新版本应用标记
private static final int INSTALL_TOKEN = 1;
//Log日志打印标签
private static final String TAG = "Update_log";
private Context context;
//获取新版APK的默认地址
private String apk_downUrl = URLCommon.host+"/downloadAPP";//下载apk文件的url(可修改为自己的文件下载url)
// 下载应用的进度条
private ProgressDialog progressDialog;
/**
* 显示下载进度对话框
*/
public void showDownloadDialog(Context context) {
this.context = context;
progressDialog = new ProgressDialog(context);
progressDialog.setTitle("正在下载...");
progressDialog.setCanceledOnTouchOutside(false);
progressDialog.setCancelable(false);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
new downloadAsyncTask().execute();
}
/**
* 下载新版本apk
*/
private class downloadAsyncTask extends AsyncTask<Void, Integer, Integer> {
@Override
protected void onPreExecute() {
Log.e(TAG, "执行至--onPreExecute");
progressDialog.show();
}
@Override
protected Integer doInBackground(Void... params) {
Log.e(TAG, "执行至--doInBackground");
URL url;
HttpURLConnection connection = null;
InputStream in = null;
FileOutputStream out = null;
try {
url = new URL(apk_downUrl);
connection = (HttpURLConnection) url.openConnection();
in = connection.getInputStream();
int fileLength = connection.getContentLength();
Log.i("下载文件大小",String.valueOf(fileLength));
File file_path = new File(FILE_PATH);
if (!file_path.exists()) {
file_path.mkdir();
}
Log.e("下载文件到目录中",FILE_NAME);
out = new FileOutputStream(new File(FILE_NAME));//为指定的文件路径创建文件输出流
byte[] buffer = new byte[1024 * 1024];
int len = 0;
int readLength = 0;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);//从buffer的第0位开始读取len长度的字节到输出流
readLength += len;
int curProgress = (int) (((float) readLength / fileLength) * 100);
Log.e(TAG, "当前下载进度:" + curProgress);
publishProgress(curProgress);//更新UI进度
if (readLength >= fileLength) {
Log.e(TAG, "执行至--readLength >= fileLength");
break;
}
}
out.flush();
return INSTALL_TOKEN;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (connection != null) {
connection.disconnect();
}
}
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
Log.e(TAG, "异步更新进度接收到的值:" + values[0]);
progressDialog.setProgress(values[0]);//设置进度
}
@Override
protected void onPostExecute(Integer integer) {
//处理完成后安装应用
progressDialog.dismiss();//关闭进度条
Log.e("下载文件大小",String.valueOf(new File(FILE_NAME).length()));
//安装应用
installApp();
}
}
/**
* 安装新版本应用
*/
private void installApp() {
File file = new File(FILE_NAME);
//更新包文件
Intent intent = new Intent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Intent.ACTION_VIEW);
if (Build.VERSION.SDK_INT >= 24)
{ // Android7.0及以上版本 Log.d("-->最新apk下载完毕","Android N及以上版本");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
Uri contentUri = UpdateProvider.getUriForFile(context, "com.zhang.oa" + ".updateProvider", file);
//参数二:应用包名+".fileProvider"(和步骤二中的Manifest文件中的provider节点下的authorities对应)
intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
context.grantUriPermission(resolveInfo.activityInfo.packageName, contentUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
} else {
// Android7.0以下版本 Log.d("-->最新apk下载完毕","Android N以下版本");
intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
context.startActivity(intent);
}
/**
* 获取当前本地apk的版本
*
* @param mContext
* @return
*/
public static int getVersionCode(Context mContext) {
int versionCode = 0;
try {
//获取软件版本号,对应AndroidManifest.xml下android:versionCode
versionCode = mContext.getPackageManager().
getPackageInfo(mContext.getPackageName(), 0).versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return versionCode;
}
/**
* 获取版本号名称
*
* @param context 上下文
* @return
*/
public static String getVerName(Context context) {
String verName = "";
try {
verName = context.getPackageManager().
getPackageInfo(context.getPackageName(), 0).versionName;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return verName;
}
}
介绍下流程,在AsycTask首先执行onPreExecute方法来显示出ProgressDialog组件,接着开始调用doInBackground来进行网络的请求,请求的url为下载最新应用程序的url,可以修改为自己需要的url,接着获取待下载文件的总大小,以及创建本地文件目录,接着通过io流不断的将服务器的应用文件下载到刚刚创建的文件目录内,每次下载的文件大小进行累加来计算下载进度,下载进度的计算方法为(int)(((float) 读取的总长度 / 文件总共大小) * 100),如果我们在后端没有设置文件大小的话,这里将无法获取文件的总大小,紧接着在需要更新文件的地方调用showDownloadDialog方法。
除此之外,还要记录下安装app的方法,自动安装app的功能卡了我有一个下午的时间,第二天总算是解决了,这里打开安装包使用的是FileProvider来实现的,由于Android7.0执行了StrictMode API 政策的原因,因此需要使用FileProvider来实现app的安装,首先在AndroidManifest文件中声明FileProvider,由于我之前做的功能已经包含了FileProvider,我想声明多个FileProvider则必须新建一个类UpdateProvider来继承FileProvider,接着在AndroidManifest中定义我声明的Provider即可,除了声明Provider,还要设置path文件,该文件是xml文件,里面指定共享目录。
类UpdateProvider
public class UpdateProvider extends FileProvider {
}
AndroidManifest声明自定义Provider
<provider
android:name="com.zhang.oa.Provider.UpdateProvider"
android:authorities="com.zhang.oa.updateProvider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
filepaths.xml文件
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path path="Android/data/com.zhang.oa/" name="files_root" />
<root-path name="download" path="" />
</paths>
在installApp()中打开apk的方式如下
实现效果图如下所示。