Android如何实现在线更新的过程案例

一、更新软件的准备 在线更新软件的话需要我们有签名的应用,我们需要把签过名之后的软件放入到服务器中,我的如下: 其中apk是有签名的更新版本! update

一、更新软件的准备 
在线更新软件的话需要我们有签名的应用,我们需要把签过名之后的软件放入到服务器中,我的如下: 
这里写图片描述 
其中apk是有签名的更新版本! 
updateinfo.html代码如下:

{"version":"2.0","description":"有全新版本,请下载!","apkurl":"hhtp://172.23.252.89:8080/MobileSafe2.0.apk"}
  • 1

二、具体客户端软件的实现

项目结构如下:

这里写图片描述

主要的业务逻辑在这里SplashActivity.java

package com.xuliugen.mobilesafe;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

import net.tsz.afinal.FinalHttp;
import net.tsz.afinal.http.AjaxCallBack;

import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.widget.TextView;
import android.widget.Toast;

/**
 * splash界面的作用 
 * 1、用来展现产品的Logo; 
 * 2、应用程序初始化的操作; 
 * 3、检查应用程序的版本; 
 * 4、检查当前应用程序是否合法注册;
 * 
 * 
 * 更新安装的话要使用签名
 * @author xuliugen
 * 
 */
public class SplashActivity extends Activity {


    protected static final int SHOW_UPDATE_DIALOG = 0;
    protected static final int ENTER_HOME = 1;
    protected static final int URL_ERROR = 2;
    protected static final int NETWORK_ERROR = 3;
    protected static final int JSON_ERROR = 40;

    private TextView tv_splash_version;
    private TextView tv_update_info;//升级进度

    private String description;// 版本信息的描述
    private String apkurl;// 版本更新的地址

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);

        tv_splash_version = (TextView) this.findViewById(R.id.tv_splash_version);
        tv_update_info = (TextView) findViewById(R.id.tv_update_info);
        tv_splash_version.setText("版本号:" + getVersionName());

        // 检查更新
        checkUpdate();

        // 设置动画
        AlphaAnimation alphaAnimation = new AlphaAnimation(0.2f, 1.0f);
        alphaAnimation.setDuration(3000);// 设置动画的时长

        //执行动画
        findViewById(R.id.rl_root_splash).startAnimation(alphaAnimation);
    }

    /**
     * 由于更新界面是在主线程中操作
     * 
     * 所以可以使用handler,当子线程中运行结束的时候们可以通知主线程进行相关的操作
     */
    private Handler handler = new Handler(){

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //得到handler发送的message消息进行处理
            switch (msg.what) {
            case SHOW_UPDATE_DIALOG:// 显示升级的对话框
                showUpdateDialog();
                break;
            case ENTER_HOME:// 进入主页面
                enterHome();
                break;

            case URL_ERROR:// URL错误
                enterHome();
                Toast.makeText(getApplicationContext(), "URL错误", 0).show();

                break;

            case NETWORK_ERROR:// 网络异常
                enterHome();
                Toast.makeText(SplashActivity.this, "网络异常", 0).show();
                break;

            case JSON_ERROR:// JSON解析出错
                enterHome();
                Toast.makeText(SplashActivity.this, "JSON解析出错", 0).show();
                break;

            default:
                break;
            }
        }


    };
    /**
     * 检查是否有新版本
     * 
     * 需要请求网络,一般在子线程中使用
     */
    private void checkUpdate() {
        new Thread() {
            public void run() {
                // URL:http://172.23.252.89:8080/updateinfo.json

                Message message = Message.obtain();// 得到一个存在的信息,用于存放更新的信息

                long startTime = System.currentTimeMillis();
                try {
                    URL url = new URL(getString(R.string.serverurl));
                    //URL url = new URL("http://172.23.252.89:8080/updateinfo.json");
                    // 联网操作
                    HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
                    httpURLConnection.setRequestMethod("GET");//设置请求方式
                    httpURLConnection.setConnectTimeout(4000);//设置超时时间

                    int code = httpURLConnection.getResponseCode();// 获得响应码
                    if (code == 200) {// 成功
                        InputStream inputStream = httpURLConnection.getInputStream();

                        //把流转换为一个String类型   
                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        byte[] buffer = new byte[1024];
                        int len = 0;
                        while((len = inputStream.read(buffer))!=-1){
                            baos.write(buffer, 0, len);
                        }
                        inputStream.close();
                        String result = baos.toString();//得到string类型的数据
                        baos.close();

                        //因为得到的数据是一个json的string所以要json解析
                        JSONObject jsonObj = new JSONObject(result);
                        //得到服务器的版本信息
                        String version = (String) jsonObj.get("version");
                        description = (String) jsonObj.get("description");
                        apkurl = (String) jsonObj.get("apkurl");

                        //校验是否有新版本
                        if (getVersionName().equals(version)) {// 版本一致,进入主界面
                            message.what = ENTER_HOME;
                        } else {// 有新版本,弹出一个升级对话框
                            message.what = SHOW_UPDATE_DIALOG;
                        }

                    }
                } catch (MalformedURLException e) {
                    message.what = URL_ERROR;
                    e.printStackTrace();
                } catch (IOException e) {
                    message.what = NETWORK_ERROR;
                    e.printStackTrace();
                } catch (JSONException e) {
                    message.what = JSON_ERROR;
                    e.printStackTrace();
                } finally {

                    long endTime = System.currentTimeMillis();

                    long dTime = endTime-startTime;//花费的时间
                    //在界面中停留3秒
                    if(dTime < 3000){
                        try {
                            Thread.sleep(3000-dTime);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }

                    handler.sendMessage(message);// 将消息发送出去
                }
            }
        }.start();
    }

    /**
     * 得到应用层序的版本名称
     * 
     * @return
     */
    private String getVersionName() {

        // 用于管理安装的apk和未安装的apk
        PackageManager packageManager = getPackageManager();

        try {
            // 得到apk的功能清单文件:为了防止出错直接使用getPackageName()方法获得包名
            // packageManager.getPackageInfo("com.xuliugen.mobilesafe", 0);
            PackageInfo packageInfo = packageManager.getPackageInfo(getPackageName(), 0);

            //返回版本名称
            return packageInfo.versionName;
        } catch (NameNotFoundException e) {
            e.printStackTrace();
            return "";
        }
    }

    /**
     * 弹出升级对话框
     */
    protected void showUpdateDialog() {
        AlertDialog.Builder builder = new Builder(this);
        builder.setTitle("提示升级");
//      builder.setCancelable(false);//强制升级:就是不让用户取消
        builder.setMessage(description);//为dialog设置信息

        builder.setOnCancelListener(new OnCancelListener() { //

            @Override
            public void onCancel(DialogInterface dialog) {
                // 进入主页面
                enterHome();
                dialog.dismiss(); // 取消显示对话框

            }
        });

        builder.setNegativeButton("下次再说", new OnClickListener() {// 取消

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // 进入主页面
                        enterHome();
                        dialog.dismiss(); // 取消显示对话框
                    }
                });

        builder.setPositiveButton("立刻升级", new OnClickListener() {//确认

            @Override
            public void onClick(DialogInterface dialog, int which) {
                // 下载APK,并且替换安装
                if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {//得到sd卡的状态
                            // sdcard存在
                            // 使用afnal下载apk文件
                            FinalHttp finalhttp = new FinalHttp();
                            // 得到sdcard路径
                            String SDCARD_PATH = Environment
                                    .getExternalStorageDirectory()
                                    .getAbsolutePath()
                                    + "/mobilesafe2.0.apk";
                            finalhttp.download(apkurl, SDCARD_PATH,
                            new AjaxCallBack<File>() {

                                @Override
                                public void onFailure(Throwable t, int errorNo,
                                        String strMsg) {
                                    t.printStackTrace();
                                    Toast.makeText(getApplicationContext(), "下载失败", 1).show();
                                    super.onFailure(t, errorNo, strMsg);
                                }
                                /**
                                 * count:为总的大小
                                 * current:为当前下载的大小
                                 */
                                @Override
                                public void onLoading(long count, long current) {//正在下载
                                    super.onLoading(count, current);
                                    tv_update_info.setVisibility(View.VISIBLE);
                                    //当前下载百分比
                                    int progress = (int) (current * 100 / count);
                                    tv_update_info.setText("下载进度:"+progress+"%");
                                }

                                /**
                                 * file t:表示文件的路径
                                 */
                                @Override
                                public void onSuccess(File t) {
                                    super.onSuccess(t);

                                    //成功的时候要安装apk
                                    installAPK(t);
                                }

                                /**
                                 * 安装APK
                                 * @param t :文件下载的路径
                                 */
                                private void installAPK(File t) {
                                  Intent intent = new Intent();
                                  intent.setAction("android.intent.action.VIEW");
                                  intent.addCategory("android.intent.category.DEFAULT");
                                  intent.setDataAndType(Uri.fromFile(t), "application/vnd.android.package-archive");

                                  startActivity(intent);
                                }
                            });
                    } else {// sdcard不存在
                    Toast.makeText(getApplicationContext(), "没有sdcard,请安装上在试",0).show();
                    return;
                }

            }
        });

        builder.show();

    }

    /**
     * 进入主界面
     */
    protected void enterHome() {
        Intent intent = new Intent(this, HomeActivity.class);
        startActivity(intent);
        // 关闭当前页面
        finish();
    }
}

其中使用的把流转化为一个String类型的方法可以封装为一个工具类: 

StreamTools.java

package com.xuliugen.mobilesafe.utils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * 把流转换为一个String类型
 * 
 * @author xuliugen
 * 
 */
public class StreamTools {
    /**
     * @param is  输入流
     * @return String 返回的字符串
     * @throws IOException
     */
    public static String readFromStream(InputStream is) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = is.read(buffer)) != -1) {
            baos.write(buffer, 0, len);
        }
        is.close();
        String result = baos.toString();
        baos.close();
        return result;
    }
}

 

注意:我们的签名一定要牢记,但是如果忘记也有方法解决: 
(1)改签名:这将无法覆盖安装,这是包名不变的情况,这就要求用户先卸载以前的应用,体验不佳! 
(2)改包名:重新签名,这就相当于手机安装两个应用,但是可以用技术卸载第一个应用。