如何在应用启动时检查版本更新对应用进行升级,解决的方案有多种。本文将阐述利用Android 自带的DownloadManager进行下载apk文件,已经下载成功后自动安装。DownloadManager在后台下载文件时会有进度条的,所以省去了开发进度条的麻烦。
思路:在AndroidManifest.xml文件中定义了versionCode以及versionName,称为旧的,如果使用AndroidStudio开发的话,这些东西都移到了build.gradle文件中。而在服务器端,也要配置(通常在配置文件中)一个版本号以及对应的版本名称,称为最新的,此外还必须配置一个apk文件的下载路径Url,当然还可以配置一些升级或更新的内容。然后在应用启动时,从服务器获取该最新的信息,可以用一个对象封装这些信息。然后对比新旧版本号,若新版本号大于旧版本号,则弹出对话框让用户进行确认升级,确认后用DownloadMananger进行下载获取的Url。DownloadManager在下载完成后会发一个通知给android系统,所以需要定义一个BroadcastReceiver来接收该通知,在onReceive()方法中将该apk进行安装。这样就实现了该功能。
有些设备是没有SD卡的,所以需要妥善设置apk的下载存放位置。
ApplicationHelper.java
package com.jykj.departure.util;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Environment;
import android.provider.Settings;
import android.telephony.TelephonyManager;
import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
public class ApplicationHelper {
public static final CharSequence TOAST_NO_NET = "很遗憾,木有网络";
//判断某个文件是否为apk文件
public static boolean isApkFile(String fileName){
fileName = fileName.toLowerCase();
int idx = fileName.lastIndexOf(".apk");
return idx >=0;
}
/**
* 获得AndroidManifest.xml中的versionCode
*
* @param context 上下文
* @return int
*/
public static int getLocalVersionCode(Context context) {
try {
PackageInfo pi = context.getPackageManager().getPackageInfo(
context.getPackageName(), 0);
return pi.versionCode;
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return 0;
}
/**
* 获得AndroidManifest.xml中的versionName
*
* @param context 上下文
* @return str
*/
public static String getLocalVersionName(Context context) {
try {
PackageInfo pi = context.getPackageManager().getPackageInfo(
context.getPackageName(), 0);
return pi.versionName;
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return null;
}
/**
* 安装apk文件
*
* @param context 上下文
* @param uri uri
*/
public static void installApk(Context context, Uri uri) {
Intent installIntent = new Intent(Intent.ACTION_VIEW);
installIntent.setDataAndType(uri,
"application/vnd.android.package-archive");
installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(installIntent);
}
/**
* 卸载apk程序
*
* @param context 上下文
* @param uri uri
*/
public static void uninstallApk(Context context, Uri uri, String packageName) {
Uri packageURI = Uri.parse("package:" + packageName);
Intent uninstallIntent = new Intent(Intent.ACTION_DELETE, packageURI);
context.startActivity(uninstallIntent);
}
/**
* 判断是否有网络连接
*
* @param context 上下文
* @return bool
*/
public static boolean isNetworkConnected(Context context) {
if (context != null) {
ConnectivityManager mConnectivityManager = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo mNetworkInfo = mConnectivityManager
.getActiveNetworkInfo();
if (mNetworkInfo != null && mNetworkInfo.isConnected()
&& mNetworkInfo.getState() == NetworkInfo.State.CONNECTED) {
return mNetworkInfo.isAvailable();
}
}
return false;
}
}
Apkinfo.java
package com.sudoku.jack.sudoku.model;
import java.io.Serializable;
public class Apkinfo implements Serializable{
private String versionName;
private int versionCode;
private String apkName;
private String upgradeContent;
private String apkURL;
public String getVersionName() {
return versionName;
}
public void setVersionName(String versionName) {
this.versionName = versionName;
}
public int getVersionCode() {
return versionCode;
}
public void setVersionCode(int versionCode) {
this.versionCode = versionCode;
}
/**
* @return 如 Express-v1.0.3.apk
*/
public String getApkName() {
return apkName;
}
public void setApkName(String apkName) {
this.apkName = apkName;
}
public String getUpgradeContent() {
return upgradeContent;
}
public void setUpgradeContent(String upgradeContent) {
this.upgradeContent = upgradeContent;
}
/**
* @return apk下载地址URL
*/
public String getApkURL() {
return apkURL;
}
public void setApkURL(String apkURL) {
this.apkURL = apkURL;
}
}
ParseJSON.java
import com.sudoku.jack.sudoku.model.Apkinfo;
import org.json.JSONException;
import org.json.JSONObject;
public class ParseJSON {
public static final String RetCode = "RetCode";
public static final String RetMsg = "RetMsg";
public static final String ResultObj = "ResultObj";
// 状态码
public static final String CODE_SUCCESS = "0000";
public static Apkinfo parseToApkinfo(@NonNull JSONObject jo) throws JSONException {
String url = jo.getString("apkURL");
if(url==null||url.isEmpty()){
return null;
}
Apkinfo a = new Apkinfo();
a.setVersionCode(jo.getInt("apkVer"));
a.setApkURL(url);
Log.e("TAG","ParseToApkInfo version code:"+ a.getVersionCode());
if(!jo.isNull("apkUpgradeContent")) a.setUpgradeContent(jo.getString("apkUpgradeContent"));
else a.setUpgradeContent("请务必更新以确保功能使用稳定!");
// 解析apkURL
int index = url.lastIndexOf("/");
String name = url.substring(index + 1);
Log.e("TAG","ParseToApkInfo URL:" + url);
a.setApkName(name);// Express-v1.0.3.apk
int idxStart = name.lastIndexOf("v");
if(idxStart == -1) idxStart = name.lastIndexOf("V");
if(idxStart ==-1) idxStart = name.lastIndexOf("-");
if(idxStart ==-1) idxStart = name.lastIndexOf("_");
int idxEnd = name.lastIndexOf(".apk");
if(idxEnd==-1) idxEnd = name.lastIndexOf(".");
try{
String version = name.substring(idxStart+1, idxEnd);
Log.e("TAG","ParseToApkInfo version:"+ version);
a.setVersionName(version);
}catch (Exception e){
e.printStackTrace();
}
return a;
}
}
GetNewestVersionTask.java
package com.sudoku.jack.sudoku.task;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.net.Uri;
import android.os.AsyncTask;
import android.widget.Toast;
import com.sudoku.jack.sudoku.model.Apkinfo;
import com.sudoku.jack.sudoku.util.Helper;
import com.sudoku.jack.sudoku.util.HttpHelper;
import com.sudoku.jack.sudoku.util.ParseJSON;
import org.json.JSONException;
import org.json.JSONObject;
/**
* 获取最新版本的apk信息 的AsyncTask
*
* @author Administrator
*/
public class GetNewestVersionTask extends AsyncTask {
private ProgressDialog pd;
private Context mContext;
private boolean mShowProgress;
private String exceptionInfo;//后台任务处理出现的异常信息
/**
* 构造方法:获取最新版本的apk信息 的AsyncTask
*
* @param context 启动任务的activity
* @param showProgress 是否显示进度条
*/
public GetNewestVersionTask(Context context, boolean showProgress) {
mContext = context;
mShowProgress = showProgress;
}
@Override
protected void onPreExecute() {
pd = new ProgressDialog(mContext);
if (mShowProgress) {
pd.setProgressStyle(ProgressDialog.STYLE_SPINNER);
pd.setTitle("检查更新");
pd.setMessage("获取数据中,请稍后...");
pd.show();
}
}
@Override
protected String doInBackground(Void... params) {
/*
String inputParams = WebServiceHelper.getInputParam(WebServiceHelper
.getNewestVersionParams(Helper.getIMEI(mContext)));
try {
return WebServiceHelper.getService(inputParams, WebServiceHelper.S_getNewestVersion);
} catch (IOException | XmlPullParserException e) {
e.printStackTrace();
exceptionInfo = "获取最新版本时出现异常:" + e.getMessage();
return null;
}
*/
// 以下是用来测试的,实际使用时应使用上面的代码来获取信息,可以是http请求或者Web Service
String url = "http://ftp.daumkakao.com/eclipse/tools/pdt/downloads/pdt-Update-3.6.0.201509151953.zip";
JSONObject obj = new JSONObject();
try {
obj.put("RetCode", "0000");
obj.put("RetMsg", "");
JSONObject apk = new JSONObject();
apk.put("apkVer", 2);
apk.put("apkURL", url);
obj.put("ResultObj", apk);
} catch (JSONException e) {
e.printStackTrace();
}
return obj.toString();
}
@Override
protected void onPostExecute(String result) {
if (mShowProgress&&pd!=null) pd.cancel();
if (result == null) {
Toast.makeText(mContext, exceptionInfo, Toast.LENGTH_SHORT).show();
return;
}
try {
JSONObject obj = new JSONObject(result);
if (ParseJSON.RESULT_SUCCESS.equals(obj.getString(ParseJSON.RetCode))) {
JSONObject jo = obj.getJSONObject(ParseJSON.ResultObj);
final Apkinfo apkinfo = ParseJSON.parseToApkinfo(jo);
//CacheHelper.setCacheApkinfo(apkinfo);
if(apkinfo==null || ApplicationHelper.getLocalVersionCode(mContext)>=apkinfo.getVersionCode()) {
if (mShowProgress) {
Toast.makeText(mContext, "已经是最新版本!", Toast.LENGTH_SHORT).show();
}
}else{
String message = "版本升级:"+ ApplicationHelper.getLocalVersionName(mContext) + "->" + apkinfo.getVersionName() + "\n";
message += apkinfo.getUpgradeContent();
new AlertDialog.Builder(mContext).setTitle("检测到新版本").
setMessage(message).setPositiveButton("立即更新", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//检查是否已经下载过,直接安装
Uri uri = ApplicationHelper.checkFileExist(apkinfo.getApkName());
if (uri != null) {
if(ApplicationHelper.isApkFile(apkinfo.getApkName())) ApplicationHelper.installApk(mContext, uri);
} else {
HttpHelper.download(mContext, apkinfo.getApkName(), Uri.parse(apkinfo.getApkURL()));
}
}
}).setNegativeButton("下次再说", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
}).create().show();
}
} else {
Toast.makeText(mContext, obj.getString(ParseJSON.RetMsg), Toast.LENGTH_SHORT).show();
}
} catch (JSONException e) {
e.printStackTrace();
Toast.makeText(mContext, e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
}
DownloadCompleteReceiver.java
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import com.jykj.departure.util.ApplicationHelper;
import java.io.File;
/**
* DownloadManager下载完后 ,DOWNLOAD_SERVICE 会发送广播提示下载完成
*/
public class DownloadCompleteReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {
/**
* The download manager is a system service that handles
* long-running HTTP downloads.
*/
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);// 从下载服务获取下载管理器
DownloadManager.Query query = new DownloadManager.Query();
long downid = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID,-1);
//downloadManager.getUriForDownloadedFile(downid);
query.setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL);// 设置过滤状态:成功
query.setFilterById(downid);
Cursor c = downloadManager.query(query);// 查询以前下载过的‘成功文件’
if (c!=null&&c.moveToFirst()) {// 移动到最新下载的文件
String fileUri = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
Log.e("TAG","COLUMN_LOCAL_URI:"+fileUri);
int fileNameIdx = c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME);
String fileName = c.getString(fileNameIdx);
File file = new File(fileName);
Log.e("TAG", "======文件名称=====:" + fileName);
if(ApplicationHelper.isApkFile(fileName)) ApplicationHelper.installApk(context, Uri.parse("file://"+fileName));
c.close();
}
// 安装
}
}
}
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<receiver
android:name=".receiver.DownloadBroadcastReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
intent-filter>
<intent-filter>
<action android:name="android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED" />
intent-filter>
receiver>
注意:android:exported=”true” ,如果设置为false,该接收器将接收不到广播。
两个权限也必不可少。
HttpHelper.java
package com.jykj.departure.util;
import android.app.DownloadManager;
import android.app.DownloadManager.Request;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;
import java.io.BufferedReader;
import java.io.DataOutputStream;
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.net.URLEncoder;
import java.util.Map;
import java.util.Map.Entry;
/**
* 通用的与Http相关的辅助类
*/
public class HttpHelper {
private static String JSESSIONID; //定义一个静态的字段,保存sessionID 例如JSESSIONID=AD5F5C9EEB16C71EC3725DBF209F6178
/**
* 利用android 下载管理器 下载文件
*
* @param context 上下文
* @param fileName 文件名
* @param uri
* http或https
* @return 如果已经下载过返回-1,否则返回Download ID
*/
public static long download(Context context, String fileName, Uri uri) {
Toast.makeText(context, "已经转入后台下载!", Toast.LENGTH_SHORT).show();
DownloadManager manager = (DownloadManager) context
.getSystemService(Context.DOWNLOAD_SERVICE); // 初始化下载管理器
Request request = new Request(uri);// 创建请求
request.setAllowedNetworkTypes(Request.NETWORK_MOBILE
| Request.NETWORK_WIFI);// 设置允许使用的网络类型,这里是移动网络和wifi都可以
request.setNotificationVisibility(Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setAllowedOverRoaming(false);// 漫游
/*request.setDestinationInExternalPublicDir(
Environment.DIRECTORY_DOWNLOADS, fileName);*/
//判断是否有SD卡,如果有设置路径,没有则使用默认路径
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
request.setDestinationInExternalFilesDir(context,
Environment.DIRECTORY_DOWNLOADS, fileName);
/* File file = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),fileName);
request.setDestinationUri(Uri.fromFile(file));*/
return manager.enqueue(request);// 将下载请求放入队列
}
/**
* http POST 请求
*
* @param urlString
* http请求
* @param content
* 请求正文,正文内容其实跟get的URL中'?'后的参数字符串一致
* @return str
* @throws IOException IOException
*/
public static String requestPost(String urlString, String content)
throws IOException {
System.out.println("session:"+JSESSIONID+",URL:"+urlString+"?"+content);
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(6 * 1000);
// Read from the connection. Default is true.
conn.setDoInput(true);
// Output to the connection. Default is
// false, set to true because post
// method must write something to the
// connection
// 设置是否向connection输出,因为这个是post请求,参数要放在
// http正文内,因此需要设为true
conn.setDoOutput(true);
// Post cannot use caches
conn.setUseCaches(false);
// Set the post method. Default is GET
conn.setRequestMethod("POST");
// This method takes effects to every instances of this class. URLConnection.setFollowRedirects是static函数,作用于所有的URLConnection对象。
// connection.setFollowRedirects(true);
// This methods only takes effacts to this instance.
conn.setInstanceFollowRedirects(true);
// Set the content type to urlencoded,because we will write some URL-encoded content to the
// connection. Settings above must be set before connect!
// 配置本次连接的Content-type,配置为application/x-www-form-urlencoded的
// 意思是正文是urlencoded编码过的form参数,下面我们可以看到我们对正文内容使用URLEncoder.encode
// 进行编码
conn.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
if(null != JSESSIONID){
conn.setRequestProperty("Cookie",JSESSIONID);
}
// 连接,从postUrl.openConnection()至此的配置必须要在connect之前完成,
// 要注意的是connection.getOutputStream会隐含的进行connect。
conn.connect();
DataOutputStream out = new DataOutputStream(conn.getOutputStream());
// The URL-encoded contend DataOutputStream.writeBytes将字符串中的16位的unicode字符以8位的字符形式写道流里面
out.writeBytes(content);
out.flush();
out.close(); // flush and close
//System.out.println(conn.getResponseMessage());
BufferedReader reader = new BufferedReader(new InputStreamReader(
conn.getInputStream(), "utf-8"));// 设置编码,否则中文乱码
String line;
StringBuilder sb = new StringBuilder();
while ((line = reader.readLine()) != null) {
sb.append(line);
}
String cookieval = conn.getHeaderField("set-cookie");
System.out.println("set-cookie:"+cookieval);
if(cookieval != null) {
JSESSIONID = cookieval.substring(0, cookieval.indexOf(";"));
}
reader.close();
conn.disconnect();
return sb.toString();
}
/**
* http Post请求 ,参考 {@link #requestPost(String,String) requestPost(String,String)}
*
* @param urlString
* 请求url
* @param map
* 封装了正文内容的map
* @return str
* @throws IOException IOException
*
*/
public static String requestPost(String urlString, Map map)
throws IOException {
if(map==null) return requestPost(urlString);
// 正文,正文内容其实跟get的URL中'?'后的参数字符串一致
// String content =
// "[email protected]"
// + "&activatecode=" + URLEncoder.encode("中国聚龙", "utf-8");
String content = "";
for (Entry en : map.entrySet()) {
String key = en.getKey();
String value = URLEncoder.encode(en.getValue(), "utf-8");
if (!content.isEmpty()) {
content += "&";
}
content += key + "=" + value;
}
return requestPost(urlString, content);
}
/**
* http Post请求,不带content ,参考 {@link #requestPost(String,String) requestPost(String,String)}
* @param urlString 请求url
* @return str
* @throws IOException IOException
*/
public static String requestPost(String urlString)
throws IOException {
return requestPost(urlString, "");
}
/**
* http GET 请求
*
* @param url url
* @return str
* @throws IOException IOException
*/
public static String requestGet(String url) throws IOException {
URL getUrl = new URL(url);
// 根据拼凑的URL,打开连接,URL.openConnection函数会根据URL的类型,
// 返回不同的URLConnection子类的对象,这里URL是一个http,因此实际返回的是HttpURLConnection
HttpURLConnection connection = (HttpURLConnection) getUrl
.openConnection();
connection.setConnectTimeout(6 * 1000);
// 进行连接,但是实际上get request要在下一句的connection.getInputStream()函数中才会真正发到
// 服务器
if(null != JSESSIONID){
connection.setRequestProperty("Cookie", JSESSIONID);
}
connection.connect();
// 取得输入流,并使用Reader读取
BufferedReader reader = new BufferedReader(new InputStreamReader(
connection.getInputStream(), "utf-8"));// 设置编码,否则中文乱码
String line;
StringBuilder sb = new StringBuilder();
while ((line = reader.readLine()) != null) {
sb.append(line);
}
String cookieval = connection.getHeaderField("set-cookie");
System.out.println("set-cookie:"+cookieval);
if(cookieval != null) {
JSESSIONID = cookieval.substring(0, cookieval.indexOf(";"));
}
reader.close();
// 断开连接
connection.disconnect();
return sb.toString();
}
/**
* 读取图片
* @param url 图片url
* @return Bitmap
* @throws IOException IOException
*/
public static Bitmap getBitmap(String url) throws IOException {
// 获得连接
HttpURLConnection conn = (HttpURLConnection) new URL(url)
.openConnection();
// 设置超时时间为6000毫秒,conn.setConnectionTiem(0);表示没有时间限制
conn.setConnectTimeout(6000);
// 连接设置获得数据流
conn.setDoInput(true);
// 不使用缓存
conn.setUseCaches(false);
// 这句可有可无,没有影响
conn.connect();
// 得到数据流
InputStream is = conn.getInputStream();
// 解析得到图片
Bitmap bitMap = BitmapFactory.decodeStream(is);
is.close();
return bitMap;
}
}
在登录的Activity或者主Activity的onCreate()方法中进行调用,如
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
//检查版本更新
new GetNewestVersionTask(this,false).execute();
}
这么一个小小的功能却要如此折腾。。。
*309. 《道德经》第六十九章2 悟道之人有三宝
原文:我恒有三宝,持而保之:一曰慈,二曰俭,三曰不敢为天下先。夫慈,故能勇;俭,故能广;不敢为天下先,故能为成事长。
译文:悟道之人一直有三个宝贝,好好地拿在手里:第一是利他,第二是降低自己的欲望,第三是不能把自己的利益放在天下人的前面。因为心怀慈悲,所以做事勇敢;因为对自己节俭,所以机会广;不敢把自己的利益放在前面,所以能被推举为做事的首领。
先天下之忧而忧,后天下之乐而乐,这是真正做事的人的品格。