Android软件的自动更新【demo】

1、之前写过一篇说版本升级的,用到广播。感觉乱用,搞的有点复杂,且混乱。现在又用到了版本升级功能,然后梳理下思路,使用回调接口重新写了个。

2、需求同http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2013/0709/1421.html,部分源码已上传。

3、增加了点小功能:

  1>、可以手动检查升级;

  2>、显示升级日志;

  3>、修改上篇博客潜在问题:

             问题:后台查询到更新,提示更新的AlertDialog只能在启动更新的页面弹出;如果离开此页面,抛异常。

             解决:在app的所有页面顶层弹出,参考代码:

//设置dialog
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
//加入权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

4、截图如下:

5、上代码(代码才能说明一切):

(1)、DownloadCallback.java

/**
 * 下载数据接口
 * @author: aokunsang
 * @date: 2012-12-17
 */
public interface DownloadCallback {
    /**
     * 下载前准备
     */
    public void onDownloadPreare();
    /**
     * 下载进度更新
     * @param progress 进度值
     */
    public void onChangeProgress(int progress);
    /**
     * 下载完成
     * @param success  下载成功标示
     * @param errorMsg 下载失败显示内容
     */
    public void onCompleted(boolean success,String errorMsg);
    /**
     * 取消下载
     */
    public boolean onCancel();
}

(2)、DownloadInstall.java

/**
 * @author: aokunsang
 * @date: 2012-12-18
 */
public class DownloadInstall implements DownloadCallback {
    private Context mContext;
    private String apkPath,apkVersion;
    private int apkCode;
    private LayoutInflater inflater;
    private TextView textView;
    private ProgressBar progressView;
    private AlertDialog downloadDialog;    //下载弹出框
    private boolean interceptFlag = false;  //是否取消下载
    public DownloadInstall(Context mContext,String apkPath,String apkVersion,int apkCode) {
        this.mContext = mContext;
        this.apkCode = apkCode;
        this.apkPath = apkPath;
        this.apkVersion = apkVersion;
        inflater = LayoutInflater.from(mContext);
    }
    @Override
    public boolean onCancel() {
        return interceptFlag;
    }
    @Override
    public void onChangeProgress(int progress) {
        progressView.setProgress(progress);   //设置下载进度
        textView.setText("进度:"+progress+"%");
    }
    @Override
    public void onCompleted(boolean success, String errorMsg) {
        if(downloadDialog!=null){
            downloadDialog.dismiss();
        }
        if(success){  //更新成功
            alearyUpdateSuccess();
            installApk();
        }else{
            Toast.makeText(mContext, errorMsg, Toast.LENGTH_SHORT).show();
        }
    }
    @Override
    public void onDownloadPreare() {
        if(IntentUtil.checkSoftStage(mContext)){
            File file = new File(Const.apkSavepath);
            if(!file.exists()){
                file.mkdir();
            }
            Builder builder = new AlertDialog.Builder(mContext);
            builder.setIcon(R.drawable.upgrade).setTitle("正在更新版本");
            //---------------------------- 设置在对话框中显示进度条 --------------------
            View view = inflater.inflate(R.layout.upgrade_apk, null);
            textView = (TextView)view.findViewById(R.id.progressCount_text);
            textView.setText("进度:0");
            progressView = (ProgressBar)view.findViewById(R.id.progressbar);
            builder.setView(view);
            builder.setNegativeButton("取消", new DialogInterface.OnClickListener(){
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                    interceptFlag = true; 
                }
            });
            downloadDialog = builder.create();
            downloadDialog.show();
        }
    }
    /**
     * 升级成功,更新升级日期和版本号,和版本code
     */
    private void alearyUpdateSuccess(){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        SharedPreferences sharedPreference = mContext.getSharedPreferences(UpdateShared.SETTING_UPDATE_APK_INFO, 0);
        sharedPreference.edit().putString(UpdateShared.UPDATE_DATE, sdf.format(new Date()))
        .putString(UpdateShared.APK_VERSION, apkVersion).putInt(UpdateShared.APK_VERCODE, apkCode).commit();
    }
    /**
     * 安装apk
     */
    private void installApk(){ 
        File file = new File(apkPath);
        if(!file.exists()){
            return;
        }
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
        mContext.startActivity(intent);
    }
}

(3)、DownloadAsyncTask.java

/**
 * 异步下载数据
 * @author: aokunsang
 * @date: 2012-12-17
 */
public class DownloadAsyncTask extends AsyncTask<String, Integer, String> {
    private DownloadCallback downCallBack;
    private HttpURLConnection urlConn;
    public DownloadAsyncTask(DownloadCallback downloadCallback){
        this.downCallBack = downloadCallback;
    }
    @Override
    protected void onPreExecute() {
        downCallBack.onDownloadPreare();
        super.onPreExecute();
    }
    @Override
    protected String doInBackground(String... args) {
        String apkDownloadUrl = args\[0\]; //apk下载地址
        String apkPath = args\[1\];   //apk在sd卡中的安装位置
        String result = "";
        if(!IntentUtil.checkURL(apkDownloadUrl)){
            result = "netfail";
        }else{
            InputStream is = null;
            FileOutputStream fos = null;
            try {
                URL url = new URL(apkDownloadUrl);
                urlConn = (HttpURLConnection)url.openConnection();
                is = urlConn.getInputStream();
                int length = urlConn.getContentLength();   //文件大小
                fos = new FileOutputStream(apkPath);
                int count = 0,numread = 0;
                byte buf\[\] = new byte\[1024\];
                while(!downCallBack.onCancel()&& (numread = is.read(buf))!=-1){
                    count+=numread;
                    int progressCount =(int)(((float)count / length) * 100);
                    publishProgress(progressCount);
                    fos.write(buf, 0, numread);
                }
                fos.flush();
                result = "success";
            } catch (Exception e) {
                e.printStackTrace();
                result = "fail";
            }finally{
                try {
                    if(fos!=null)
                        fos.close();
                    if(is!=null)
                        is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    result = "fail";
                }
            }
        }
        return result;
    }
    @Override
    protected void onProgressUpdate(Integer... values) {
        downCallBack.onChangeProgress(values\[0\]);
        super.onProgressUpdate(values);
    }
    @Override
    protected void onPostExecute(String result) {
        if(downCallBack.onCancel()){
            downCallBack.onCompleted(false, "版本更新下载已取消。");
        }else if("success".equals(result)){
            downCallBack.onCompleted(true, null);
        }else if("netfail".equals(result)){
            downCallBack.onCompleted(false, "连接服务器失败,请稍后重试。");
        }else{
            downCallBack.onCompleted(false, "版本更新失败,请稍后重试。");
        }
        super.onPostExecute(result);
    }
    @Override
    protected void onCancelled() {
        if(urlConn!=null){
            urlConn.disconnect();
        }
        super.onCancelled();
    }
}

(4)、DownloadManager.java

/**
 * 下载管理
 * @author: aokunsang
 * @date: 2012-12-18
 */
public class DownloadManager{
    private Context mContext;
    final static int CHECK_FAIL = 0;
    final static int CHECK_SUCCESS = 1;
    final static int CHECK_NOUPGRADE = 2;
    final static int CHECK_NETFAIL = 3;
    private ApkInfo apkinfo;
    private AlertDialog noticeDialog;    //提示弹出框
    private ProgressDialog progressDialog;
    private boolean isAccord;  //是否主动检查软件升级
    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    public DownloadManager(Context mContext,boolean isAccord){
        this.mContext = mContext;
        this.isAccord = isAccord;
    }
    Handler handler = new Handler(){
        public void handleMessage(android.os.Message msg) {
            if(progressDialog!=null){
                progressDialog.dismiss();
            }
            switch(msg.what){
                case CHECK_SUCCESS:{
                    showNoticeDialog();
                    break;
                }
                case CHECK_NOUPGRADE:{  //不需要更新
                    if(isAccord) Toast.makeText(mContext, "当前版本是最新版。", Toast.LENGTH_SHORT).show();
                    break;
                }
                case CHECK_NETFAIL:{
                    if(isAccord) Toast.makeText(mContext, "网络连接不正常。", Toast.LENGTH_SHORT).show();
                    break;
                }
                case CHECK_FAIL:{
                    if(isAccord) Toast.makeText(mContext, "从服务器获取更新数据失败。", Toast.LENGTH_SHORT).show();
                    break;
                }
            }
        };
    };
    /* 检查下载更新 \[apk下载入口\] */
    public void checkDownload(){
        if(isAccord) progressDialog = ProgressDialog.show(mContext, "", "请稍后,正在检查更新...");
        new Thread() {
            @Override
            public void run() {
                if(!IntentUtil.isConnect(mContext)){ //检查网络连接是否正常
                    handler.sendEmptyMessage(CHECK_NETFAIL);
                }else if(checkTodayUpdate() || isAccord){//判断今天是否已自动检查过更新 ;如果手动检查更新,直接进入  
                    String result = HttpRequestUtil.getSourceResult(Const.apkCheckUpdateUrl, null, mContext);
                    try {
                        //从服务器下载数据有中文,所以服务器对数据进行了编码;在这里需要解码
                        result = Escape.unescape(result);
                        JSONObject obj = new JSONObject(result);
                        String apkVersion = obj.getString("apkVersion");
                        int apkCode = obj.getInt("apkVerCode");
                        String apkSize = obj.getString("apkSize");
                        String apkName = obj.getString("apkName");
                        String downloadUrl = obj.getString("apkDownloadUrl");
                        String apkLog = obj.getString("apklog");
                        apkinfo = new ApkInfo(downloadUrl, apkVersion, apkSize, apkCode, apkName, apkLog);
                        if(apkinfo!=null && checkApkVercode()){  //检查版本号
                            alreayCheckTodayUpdate();    //设置今天已经检查过更新
                            handler.sendEmptyMessage(CHECK_SUCCESS);
                        }else{
                            handler.sendEmptyMessage(CHECK_NOUPGRADE);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        handler.sendEmptyMessage(CHECK_FAIL);
                    }
                }
            }
        }.start();
    }
    /* 弹出软件更新提示对话框*/
    private void showNoticeDialog(){
        StringBuffer sb = new StringBuffer();
        sb.append("版本号:"+apkinfo.getApkVersion()+"\\n")
        .append("文件大小:"+apkinfo.getApkSize()+"\\n")
        .append("更新日志:\\n"+apkinfo.getApkLog());
        Builder builder = new AlertDialog.Builder(mContext);
        builder.setIcon(R.drawable.upgrade).setTitle("版本更新").setMessage(sb.toString());
        builder.setPositiveButton("下载", new DialogInterface.OnClickListener(){
            @Override
            public void onClick(DialogInterface dialog, int which) {
                String apkPath = Const.apkSavepath + apkinfo.getApkName();
                DownloadCallback downCallback = new DownloadInstall(mContext, apkPath, apkinfo.getApkVersion(), apkinfo.getApkCode());
                DownloadAsyncTask request = new DownloadAsyncTask(downCallback);
                request.execute(apkinfo.getDownloadUrl(),apkPath);
                dialog.dismiss();
            }
        });
        builder.setNegativeButton("以后再说", new DialogInterface.OnClickListener(){
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
            }
        });
        noticeDialog = builder.create();
        noticeDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);   //设置最顶层Alertdialog
        noticeDialog.show();
    }
    /**
     * 根据日期检查是否需要进行软件升级
     * @throws Exception
     */
    private boolean checkTodayUpdate() {
        SharedPreferences sharedPreference = mContext.getSharedPreferences(UpdateShared.SETTING_UPDATE_APK_INFO, 0);
        String checkDate = sharedPreference.getString(UpdateShared.CHECK_DATE, "");
        String updateDate = sharedPreference.getString(UpdateShared.UPDATE_DATE, "");
        if("".equals(checkDate) && "".equals(updateDate)){  //刚安装的新版本,设置详细信息
            int verCode = IntentUtil.getCurrentVersionCode(mContext);
            String versionName = IntentUtil.getCurrentVersionName(mContext);
            String dateStr = sdf.format(new Date());
            sharedPreference.edit().putString(UpdateShared.CHECK_DATE, dateStr)
            .putString(UpdateShared.UPDATE_DATE, dateStr)
            .putString(UpdateShared.APK_VERSION, versionName)
            .putInt(UpdateShared.APK_VERCODE, verCode).commit();
            return true;
        }
        try {
            //判断defaultMinUpdateDay天内不检查升级
            if((new Date().getTime()-sdf.parse(updateDate).getTime())/1000/3600/24<Const.defaultMinUpdateDay){
                return false;
            }else if(checkDate.equalsIgnoreCase(sdf.format(new Date()))){//判断今天是否检查过升级
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
    /**
     * 设置今天已经检查过升级
     * @return
     */
    private void alreayCheckTodayUpdate(){
        String date = sdf.format(new Date());
        SharedPreferences sharedPreference = mContext.getSharedPreferences(UpdateShared.SETTING_UPDATE_APK_INFO, 0);
        sharedPreference.edit().putString(UpdateShared.CHECK_DATE, date).commit();
    }
    /**
     * 检查版本是否需要更新
     * @return
     */
    private boolean checkApkVercode(){
        SharedPreferences sharedPreference = mContext.getSharedPreferences(UpdateShared.SETTING_UPDATE_APK_INFO, 0);
        int verCode = sharedPreference.getInt(UpdateShared.APK_VERCODE, 0);
        if(apkinfo.getApkCode()>verCode){
            return true;
        }else{
            return false;
        }
    }
    static interface UpdateShared{
       String SETTING_UPDATE_APK_INFO = "cbt_upgrade_setting";
       String UPDATE_DATE = "updatedate";
       String APK_VERSION = "apkversion";
       String APK_VERCODE = "apkvercode";
       String CHECK_DATE = "checkdate";
    }
}

代码内容我不讲解,使用的是回调接口。

(5)、其他操作类:

    a、Const.java是一个常量存储类,保存检查更新地址等;

    b、Escape.java是个URL解码编码类;

    c、HttpRequestUtil.java获取远程数据类;

    d、IntentUtil.java工具类,如:检查网络连接状态等。

(以上类参考附件源码)

/**
 * apk更新信息
 * @author: aokunsang
 * @date: 2012-12-18
 */
public class ApkInfo implements Serializable {
    /**
     *
     */
    private static final long serialVersionUID = 1L;
    private String downloadUrl;  //下载地址
    private String apkVersion;  //apk版本
    private String apkSize;    //apk文件大小
    private int apkCode;   //apk版本号(更新必备)
    private String apkName;  //apk名字
    private String apkLog;   //apk更新日志
        setter and getter...
}

(6)、服务器代码(这里的apklog可以是个txt文档,在UI上下载展示,我做的比较简单):

/**
     *  获取apk更新信息
     * {apkVersion:'1.10',apkSize:'36K',apkVerCode:2,apkName:'1.1.apk',apkDownloadUrl:'http://localhost:8080/myapp/1.1.apk',apklog:'1、修改页面;\\n2、修改字体'} 
     */
    @Action(value="checkUpdateApk")
    public String updateApk(){
        ResourceLoader loader = new DefaultResourceLoader();
        Properties pp = null;
        try {
            InputStream is = loader.getResource("classpath:config/sysconf.properties").getInputStream();
            pp = new Properties();
            pp.load(new InputStreamReader(is, "utf-8"));
        } catch (Exception e) {
            e.printStackTrace();
        }
        if(pp!=null){
            String apkVersion = pp.getProperty("apkVersion");
            String apkDownloadUrl = pp.getProperty("apkDownloadUrl");
            String apkName = pp.getProperty("apkName");
            int apkVerCode = NumberUtils.toInt(pp.getProperty("apkVerCode"),0);
            String apklog = pp.getProperty("apkLog");
            String apkSize = pp.getProperty("apkSize");
            Httptear.ResponseResultByEscape("{apkVersion:'"+apkVersion+"',apkSize:'"+apkSize+"',apkVerCode:"+apkVerCode+",apkName:'"+apkName+"',apkDownloadUrl:'"+apkDownloadUrl+"',apklog:'"+apklog+"'}");
        }else{
            Httptear.ResponseResultByEscape("{}");
        }
        return NONE;
    }

 6、upgrade_apk.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >
    <TextView 
        android:id="@+id/progressCount_text"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textColor="@color/white"
        android:textSize="14dip"
        />
    <ProgressBar 
        android:id="@+id/progressbar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content"
    />
</LinearLayout>

   7、需要加入以下权限:

<!-- 在SD卡中创建和删除文件权限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<!-- 向SD卡中写入东西权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

 8、启动更新检查代码:

  /* 升级程序\[主动\] (因为会弹出一个ProgressDialog窗口,不能使用getApplicationContext())*/
DownloadManager downManger = new DownloadManager(this,true);
downManger.checkDownload();
/* 升级程序启动\[被动\](使用this引用会导致:如在1页面启动升级,当前页面为2页面,此时弹出Dialog抛异常)*/
DownloadManager downManger = new DownloadManager(getApplicationContext(),false);
downManger.checkDownload();

源码下载:http://www.jcodecraeer.com/demo/androidupdatecode.zip