如何在应用启动时检查版本更新对应用进行升级,解决的方案有多种。本文将阐述利用Android 自带的DownloadManager进行下载apk文件,已经下载成功后自动安装。DownloadManager在后台下载文件时会有进度条的,所以省去了开发进度条的麻烦。

思路:在AndroidManifest.xml文件中定义了versionCode以及versionName,称为旧的,如果使用AndroidStudio开发的话,这些东西都移到了build.gradle文件中。而在服务器端,也要配置(通常在配置文件中)一个版本号以及对应的版本名称,称为最新的,此外还必须配置一个apk文件的下载路径Url,当然还可以配置一些升级或更新的内容。然后在应用启动时,从服务器获取该最新的信息,可以用一个对象封装这些信息。然后对比新旧版本号,若新版本号大于旧版本号,则弹出对话框让用户进行确认升级,确认后用DownloadMananger进行下载获取的Url。DownloadManager在下载完成后会发一个通知给android系统,所以需要定义一个BroadcastReceiver来接收该通知,在onReceive()方法中将该apk进行安装。这样就实现了该功能。

有些设备是没有SD卡的,所以需要妥善设置apk的下载存放位置。

1.为了完成上述工作,定义如下一个辅助类.

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;    }}

2.从服务器获取的信息的封装类

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;    }}

3.将服务器端的信息JSON格式解析为ApkInfo对象

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;    }}

4.从服务器获取信息的任务类

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();        }    }}

5.下载完成后需要一个广播接收器,用来自动安装文件

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();            }            // 安装        }    }}

6.最后别忘了在AndroidManifest.xml文件中加上如下配置

<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,该接收器将接收不到广播。
两个权限也必不可少。

7.利用DownloadManager下载apk文件

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 =        // "key=j0r53nmbbd78x7m1pqml06u2&type=1&toemail=cngolon@gmail.com"        // + "&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;    }}

8.使用

在登录的Activity或者主Activity的onCreate()方法中进行调用,如

 @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        ...         //检查版本更新        new GetNewestVersionTask(this,false).execute();    }

这么一个小小的功能却要如此折腾。。。

*309. 《道德经》第六十九章2 悟道之人有三宝
原文:我恒有三宝,持而保之:一曰慈,二曰俭,三曰不敢为天下先。夫慈,故能勇;俭,故能广;不敢为天下先,故能为成事长。
译文:悟道之人一直有三个宝贝,好好地拿在手里:第一是利他,第二是降低自己的欲望,第三是不能把自己的利益放在天下人的前面。因为心怀慈悲,所以做事勇敢;因为对自己节俭,所以机会广;不敢把自己的利益放在前面,所以能被推举为做事的首领。

先天下之忧而忧,后天下之乐而乐,这是真正做事的人的品格。

更多相关文章

  1. linux学习笔记《一.烧写篇_android》
  2. android 从第三方app打开方式添加自己的app
  3. 转:Activity_dialog效果
  4. android Thumbnail攻略
  5. Android(安卓)RRO机制的运用-----google开机向导客制化
  6. android中的Handler(1)
  7. Mac下用Charles实现Android(安卓)http和https抓包
  8. SSDP协议的Android实现以及使用
  9. Android文件目录结构

随机推荐

  1. Scrapy框架的使用之Scrapy入门
  2. 记一次网络中断故障处理
  3. 【机器学习笔记】:大话线性回归(二)
  4. 【机器学习笔记】:一文让你彻底记住什么是
  5. 【SQL刷题系列】:leetcode178 Rank Scores
  6. 谁才是权游的真正主角。
  7. Scrapy框架的使用之Downloader Middlewar
  8. 【SQL刷题系列】:leetcode183 Customers W
  9. Python操作MySQL存储,这些你都会了吗?
  10. Scrapy框架的使用之Spider Middleware的