[转]Android应用的自动更新

软件的自动更新一般都与Splash界面绑定在一起, 由于需要维护的软件界面很复杂, 一个Activity中嵌入ViewPager, 并且逻辑比较复杂, 索性重新写一个Activity, 现在的软件都很流行使用Splash界面, 正好与自动更新配套在一起;

在这个自动更新Splash中, 使用到了 动画设置 ,SharedPerference ,pull解析 ,dialog对话框 ,http网络编程 ,handler 等.

注意一个错误 : 已安装具有该名称和不同签名的数据包 , 早上测试人员报告突然出现这个问题, 在开发的时候我直接将eclipse上编译的版本放到了服务器上, 最后出现了这个问题, 开发的时候明明是好的啊, 怎么测试的时候出问题了呢.

编译环境不同, 产生的签名是不一样的, 在eclipse上编译生成 与 正式版本在linux下编译 所产生的 数字签名 是不一样的.

一. 创建Activity

1. 创建Activity大概流程

a. 设置全屏显示.

b. 设置布局, 并在布局中显示当前版本号, 为Splash界面添加动画.

c. 获取当前时间.

d. 获取SharedPerence配置文件.

e. 开启检查版本号线程, 后续的操作都在这个线程中执行.

2. 设置窗口样式

(1) 设置全屏显示

a. 代码实现 : 由于是Splash界面, 这里需要设置成无标题, 并且全屏显示, 注意下面的两行代码需要在setContentView()方法之前调用;

 //隐藏标题栏
requestWindowFeature(Window.FEATURE_NO_TITLE);
//隐藏状态栏
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN); 

b. 配置实现 :

 AndroidManifest.xml
    <activity
        android:name="myAcitivty"
        android:theme="@android:style/Theme.NoTitleBar.Fullscreen" />  

(2) 关于窗口的其它设置

 //①设置窗体始终点亮
getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,WindowManager.LayoutParams
//②设置窗体始终点亮
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);  

设置窗体始终点亮的配置文件实现

 //③AndroidManifest.xml添加权限
    <uses-permission android:name="android.permission.WAKE_LOCK" />  
 //设置窗体背景模糊
getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND,WindowManager.LayoutParams.FLAG_B

(3) 屏幕方向设置

a. 配置文件实现

 //设置横屏
<activity android:name="myAcitivty"  android:screenOrientation="landscape" />       

//设置竖屏
<activity android:name="myAcitivty"  android:screenOrientation="portrait" />  

b. 代码实现

//设置横屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);  

//设置竖屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);  

c. 获取屏幕方向

 //获取横屏方向
int orientation = this.getResources().getConfiguration().orientation;  

其中的orientation方向可以使 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE 或者 ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE .

3. 设置动画

为了更好的用户体验, 这里给Splash界面添加一个动画, 这个动画加给整个界面.

(1) 创建动画

AlphaAnimation animation = new AlphaAnimation(0.0f, 1.0f);<span style="white-space:pre">    </span>//创建动画
animation.setDuration(2000);<span style="white-space:pre">  </span>//设置渐变
splash_rl.setAnimation(animation);<span style="white-space:pre">    </span>//设置动画载体

创建动画吧: 创建的这个动画是透明度渐变动画, 传入浮点型参数, 0代表完全透明, 1代表不透明, 传入参数代表透明度从完全透明到不透明.

设置时间 : 设置的duration是动画渐变过程所消耗的时间.

设置动画 : 最后使用setAnimation()方法将穿件的动画设置给Splash界面.

(2) 动画常用方法

a. 普通设置

    alphaAnimation.setRepeatCount(5);//设置重复次数
    alphaAnimation.setFillAfter(true);//动画执行完是否停留在执行完的状态
    alphaAnimation.setStartOffset(1000);//动画执行前等待的时间, 单位是毫秒
    alphaAnimation.start();//开始动画  

b. 设置监听器

    alphaAnimation.setAnimationListener(new AnimationListener() {
                //动画开始时回调
                @Override
                public void onAnimationStart(Animation animation) {
                }
                //动画重复执行时回调
                @Override
                public void onAnimationRepeat(Animation animation) {
                }
                //动画执行结束时回调
                @Override
                public void onAnimationEnd(Animation animation) {
                }
            });  

4. SharedPerference使用

    //获取SharedPerference
    SharedPreferences sharedPreferences = getSharedPreferences("sp", Context.MODE_PRIVATE);  

    Editor editor = sharedPreferences.edit();   //获取Editor对象
    editor.putBoolean("isUpdate", true);        //向sp中写入数据
    editor.commit();                            //提交  

    sharedPreferences.getBoolean("isUpdate", true);//获取sp中的变量  

5. onCreate()方法代码

    /**
         * 创建Activity时调用
         *
         * ① 设置全屏显示, 由于是Splash界面, 因此不能有标题
         * ② 设置布局, 版本号, 执行动画
         * ③ 设置当前时间
         * ④ 获取SharedPerference配置文件
         * ⑤ 开启检查版本号线程, 后续操作都在改线程中操作
         *
         */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            //隐藏标题栏
            requestWindowFeature(Window.FEATURE_NO_TITLE);
            //隐藏状态栏
            getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                    WindowManager.LayoutParams.FLAG_FULLSCREEN);
            //设置布局
            setContentView(R.layout.splash);  

            /*
             *  显示当前软件的版本号
             *  获取布局中的TextView控件, 将版本号设置到这个TextView控件中
             */
            tv_version = (TextView) findViewById(R.id.tv_version);
            version =getString(R.string.current_version) + " " + getVersion();
            tv_version.setText(version);  

            /*
             *  在界面设置一个动画, 用来表明正在运行
             *  a. 获取布局
             *  b. 创建一个动画对象
             *  c. 将动画设置到布局中
             */
            splash_rl = (RelativeLayout) findViewById(R.id.splash_rl);
            AlphaAnimation animation = new AlphaAnimation(0.0f, 1.0f);
            animation.setDuration(2000);
            splash_rl.setAnimation(animation);  

            /*
             * 这个时间值是用来控制Splash界面显示时间的
             * 记录下这个值, 然后执行到下面, 如果时间差在3秒以内,
             * 就执行下面的操作, 如果时间差不足3秒, 就Thread.sleep时间差
             * 等够3秒在执行下面的操作
             */
            time = System.currentTimeMillis();  

            //从SharedPreference中获取一些配置
            sp = getSharedPreferences("config", Context.MODE_PRIVATE);  

            //开启检查版本号线程
            new Thread(new CheckVersionTask()).start();
        }  

二. 检查版本号

1. 检查版本号线程

流程 :

a. 保持Splash持续时间 : 获取当前时间与time进行比较, 如果不足3秒, 人为使Splash保持3秒时间;

b. 查看更新设置 : 从sp中获取更新设置, 如果sp中自动更新为true, 那么就执行下面的更新流程, 如果sp中自动更新为false, 那么直接进入主界面.

c. 获取信息 : 从网络中获取更新信息, 根据是否成功获取信息执行不同的操作.

源码 :

    private final class CheckVersionTask implements Runnable{
        public void run() {
            try {
                /*
                 * 获取当前时间, 与onCreate方法中获取的时间进行比较
                 * 如果不足3秒, 在等待够3秒之后在执行下面的操作
                 */
                long temp = System.currentTimeMillis();
                if(temp - time < 3000){
                    SystemClock.sleep(temp - time);
                }  

                /*
                 * 检查配置文件中的设置, 是否设置了自动更新;
                 * 如果设置了自动更新, 就执行下面的操作,
                 * 如果没有设置自动更新, 就直接进入主界面
                 */
                boolean is_auto_update = sp.getBoolean("is_auto_update", true);
                if(!is_auto_update){
                    loadMainUI();
                    return;
                }  

                /*
                 * 获取更新信息
                 * 如果信息不为null, 向handler发信息SUCESS_GET_UPDATEINOF, 执行后续操作
                 * 如果信息为null, 向handler发信息ERROR_GET_UPDATEINOF, 执行后续操作
                 * 如果出现异常, 向handler发信息ERROR_GET_UPDATEINOF, 执行后续操作
                 */
                updateInfo = getUpdateInfo(SettingsFactory.readWebLoadUrl(getApplicationContext()) + UPDATE_FOLDER_DIRECTORY + XML_FILE_DIRECTORY);
                if(updateInfo != null){
                    Message msg = new Message();
                    msg.what = SUCESS_GET_UPDATEINOF;
                    mHandler.sendMessage(msg);
                }else{
                    Message msg = new Message();
                    msg.what = ERROR_GET_UPDATEINOF;
                    mHandler.sendMessage(msg);
                }
            } catch (Exception e) {
                e.printStackTrace();
                Message msg = new Message();
                msg.what = ERROR_GET_UPDATEINOF;
                mHandler.sendMessage(msg);
            }
        }
       }  

2. 获取版本号方法

流程 :

a. 创URL建对象;

b. 创建HttpURLConnection对象;

c. 设置超时时间;

d. 设置获取方式;

e. 查看链接是否成功;

f. 解析输入流信息;

源码 :

    /**
     * 获取更新信息
     *      ① 根据字符串地址创建URL对象
     *      ② 根据URL对象创建HttpURLConnection链接对象
     *      ③ 设置链接对象5秒超时
     *      ④ 设置链接对象获取的方式为get方式
     *      ⑤ 如果成功连接, conn.getRequestCode值就是200, 此时就可以获取输入流
     *      ⑥ 解析输入流获取更新信息
     *
     */
    private UpdateInfo getUpdateInfo(String path){
        try {
            URL url = new URL(path);    //创建URL对象
            //创建连接对象
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            //设置链接超时
            conn.setConnectTimeout(5000);
            //设置获取方式
            conn.setRequestMethod("GET");
            //如果连接成功, 获取输入流
            if(conn.getResponseCode() == 200){
                InputStream is = conn.getInputStream();
                //解析输入流中的数据, 返回更新信息
                return parserUpdateInfo(is);
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (ProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }  

3. 更新信息对象

将从网上获取的更新信息 包括 版本号, apk文件地址, 软件描述等信息封装在一个类中.

    public class UpdateInfo {
        private String version; //当前软件版本号
        private String url;     //获取到的软件地址
        private String description; //软件描述  

        public String getVersion() {
            return version;
        }
        public void setVersion(String version) {
            this.version = version;
        }
        public String getUrl() {
            return url;
        }
        public void setUrl(String url) {
            this.url = url;
        }
        public String getDescription() {
            return description;
        }
        public void setDescription(String description) {
            this.description = description;
        }
        @Override
        public String toString() {
            return "UpdateInfo [version=" + version + ", url=" + url
                    + ", description=" + description + "]";
        }
    }  

4. pull解析输入流

(1) pull解析流程

a. 获取pull解析器 : XmlPullParser parser = Xml.newPullParser();

b. 为pull解析器设置编码 : parser.setInput(inputStream, "UTF-8");

c. 获取pull解析器事件 : int eventType = parser.getEventType(), 之后的解析都要根据这个解析事件进行, 例如开始解析标签的事件时 XmlPullParser.START_TAG, 文档结束的事件时 XmlPullParser.END_DOCUMENT.

d. 解析流程控制 : 解析的时候, 如果没有解析到文档最后就一直解析, 这里使用while循环, eventType != XmlPullParser.END_DOCUMENT 就一直循环, 循环玩一个元素之后, 调用parser.next()遍历下一个元素.

e. 获取标签名 : 在事件解析标签的时候 ( eventType == XmlPullParser.START_TAG ) , 调用parser.getName()可以获取这个标签的标签名, 如果我们想要获取这个标签下的文本元素, 可以使用parser.nextText()来获取.

(2) 更新xml文件

    <?xml version="1.0" encoding="UTF-8"?>
    <updateInfo>
      <version>3.2</version>
      <url>http://127.0.0.1:8080/web/mobilesafe.apk</url>
      <description>客户端更新</description>
    </updateInfo>  

(3) 源码

    /**
     * 获取更新信息
     *      ① 创建pull解析器
     *      ② 为解析器设置编码格式
     *      ③ 获取解析事件
     *      ④ 遍历整个xml文件节点, 获取标签元素内容
     */
    private UpdateInfo parserUpdateInfo(InputStream is){
        try {
            UpdateInfo updateInfo = null;
            //1. 创建pull解析解析器
            XmlPullParser parser = Xml.newPullParser();
            //2. 设置解析编码
            parser.setInput(is, "UTF-8");
            //3. 获取解析器解事件, 如解析到文档开始 , 结尾, 标签等
            int eventType = parser.getEventType();
            //4. 在文档结束前一直解析
            while (eventType != XmlPullParser.END_DOCUMENT) {
                switch (eventType) {
                //只解析标签
                case XmlPullParser.START_TAG:
                    if ("updateInfo".equals(parser.getName())) {
                        //当解析到updateInfo标签的时候, 跟标签开始, 创建一个UpdateInfo对象
                        updateInfo = new UpdateInfo();
                    } else if ("version".equals(parser.getName())) {
                        //解析版本号标签
                        updateInfo.setVersion(parser.nextText());
                    } else if ("url".equals(parser.getName())) {
                        //解析url标签
                        updateInfo.setUrl(parser.nextText());
                    } else if ("description".equals(parser.getName())) {
                        //解析描述标签
                        updateInfo.setDescription(parser.nextText());
                    }
                    break;
                default:
                    break;
                }
                //每解析完一个元素, 就将解析标志位下移
                eventType = parser.next();
            }
            is.close();
            return updateInfo;
        } catch (XmlPullParserException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }  

三. Handler对象

Handler对象用来控制整个更新过程的进行;

    private Handler mHandler = new Handler(){
        public void handleMessage(android.os.Message msg) {
            switch (msg.what) {
            /*
             * 获取更新信息错误 , 在断网或者获取信息出现异常执行
             * 提示一下, 之后进入主界面
             */
            case ERROR_GET_UPDATEINOF:
                ToastHint.getInstance().showHint(R.string.fail_to_get_updateinfo);
                loadMainUI();
                break;
            /*
             * 成功获取更新信息, 一般在成功从网上获取xml文件并解析出来
             * 如果版本号相同, 说明不用更新, 直接进入主界面
             * 如果版本号不同, 需要弹出更新对话框
             */
            case SUCESS_GET_UPDATEINOF:
                if(updateInfo.getVersion().equals(version)){
                    loadMainUI();
                }else{
                    showUpdateDialog();
                }
                break;
            /*
             * 下载apk文件出现错误, 中途断网 出现异常等情况
             * 提示后进入主界面
             */
            case ERROR_DOWNLOAD_APK:
                mPb.dismiss();
                ToastHint.getInstance().showHint(R.string.fail_to_get_apk);
                loadMainUI();
                break;
            /*
             * 成功下载apk文件之后执行的操作
             * 取消进度条对话框, 之后安装apk文件
             */
            case SUCCESS_DOWNLOAD_APK:
                mPb.dismiss();
                installApk();
                break;
            default:
                break;
            }
        };
    };  

四. 下载安装apk文件

1. 更新对话框

(1) 更新流程

先弹出更新对话框提示, 点击确定就弹出进度条对话框, 下载apk文件 . 如果点击取消, 直接进入主界面

更新对话框 : 这是一个AlertDialog , 先创建builder, 然后设置标题, 显示内容, 设置积极消极按钮, 创建对话框 之后显示对话框;

进度条对话框 : 这是一个ProgressDialog, 直接使用new创建, 设置信息与显示样式, 最后显示对话框.

(2) 创建对话框流程

创建一个对话框的流程 :

a. 创建builder对象 : Builder builder = new Builder(context);

b. 设置标题 : builder.setTittle("");

c. 设置显示信息 : builder.setMessage("");

d. 设置按钮 : builder.setPositiveButton("", onClickListener);

e. 创建对话框 : Dialog dialog = builder.create();

f. 显示对话框 : dialog.show();

创建进度条对话框流程 :

a. 创建进度条对话框 : ProgressDialog progressDialog = new ProgressDialog(context);

b. 设置进度条对话框样式 : progressDialog.setProgressStyle();

c. 设置显示信息 : progressDialog.setMessage();

d. 显示对话框 : progressDialog.show();

(3) 源码

    /**
     * 弹出更新对话框
     *
     * a. 创建builder对象
     * b. 设置标题
     * c. 设置对话框显示信息
     * d. 设置该对话框不可回退, 如果回退的话就会卡在本界面
     * e. 设置确定按钮
     * f. 设置取消按钮
     * g. 创建对话框
     * h. 显示对话框
     *
     * 确定按钮按下显示进度条对话框
     * a. 创建一个进度条对话框
     * b. 设置该对话框不能回退
     * c. 设置进度条样式
     * d. 设置进度条的信息
     * e. 显示进度条对话框
     * f. 开启一个线程, 下载apk文件
     */
    protected void showUpdateDialog() {
        //创建builder对象
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        //设置标题
        builder.setTitle(getString(R.string.update_dialog_tittle));
        //设置对话框信息
        builder.setMessage(updateInfo.getDescription());
        //设置不可回退
        builder.setCancelable(false);
        //设置确定按钮
        builder.setPositiveButton(getString(R.string.confirm), new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                //创建进度条对话框
                mPb = new ProgressDialog(SplashActivity.this);
                //设置进度条对话框不可回退
                mPb.setCancelable(false);
                //设置进度条对话框样式
                mPb.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                //设置进度条对话框的信息
                mPb.setMessage(getString(R.string.update_dialog_messsage));
                //显示进度条对话框
                mPb.show();
                //开启显示进度条对话框线程
                new Thread(new DownloadApkTask()).start();
            }
        });
        builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                loadMainUI();
            }
        });
        //创建更新信息提示对话框
        mUpdateInfoDialog = builder.create();
        //显示更新信息提示对话框
        mUpdateInfoDialog.show();
    }  

2. 下载apk线程

    /**
     * 在这个线程中主要执行downloadApk方法, 这个方法传入apk路径和进度条对话框
     * 注意 : 下载的前提是sd卡的状态是挂载的
     */
    private final class DownloadApkTask implements Runnable{
        public void run() {
            if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
                try {
                    SystemClock.sleep(2000);
                    apkFile = downloadApk(SettingsFactory.readWebLoadUrl(getApplicationContext()) + UPDATE_FOLDER_DIRECTORY + updateInfo.url,mPb);
                    Message msg = new Message();
                    msg.what = SUCCESS_DOWNLOAD_APK;
                    mHandler.sendMessage(msg);
                } catch (Exception e) {
                    e.printStackTrace();
                    Message msg = new Message();
                    msg.what = ERROR_DOWNLOAD_APK;
                    mHandler.sendMessage(msg);
                }
            }
        }
       }  

3. 下载apk核心方法

从网络下载文件流程 :

a. 创建URL对象 : 这个对象一般根据字符串地址创建, URL url = new URL(path);

b. 创建HttpURLConnection对象 : 这个对象根据URL对象创建, HttpURLConnection conn = (HttpURLConnection)url.openConnection();

c. 设置超时时间 : 单位是毫秒, conn.setConnectionTimeout(5000);

d. 设置请求方式 : conn.setRequestMethod("GET");

e. 成功连接 : 如果成功连接, 那么conn.getResponseCode()的值为200;

进度条对话框设置 :

a. 设置进度条最大值 : mProgressDialog.setMax(int max);

b. 设置进度条当前值 : mProgressDialog.setProgress(int curr);

    /**
     * 下载apk更新文件
     *
     * a. 根据SD卡路径创建文件对象, 这个文件用来保存下载的文件
     * b. 创建URL对象
     * c. 创建HttpUrlConnection对象
     * d. 设置链接对象超时时间
     * e. 设置请求方式 get
     * f. 如果请求成功执行下面的操作
     *
     * g. 通过链接对象获取网络资源的大小
     * h. 将文件大小设置给进度条对话框
     * i. 获取输入流, 并且读取输入流信息
     * j. 根据读取到的字节数, 将已经读取的数据设置给进度条对话框
     */
    public File downloadApk(String path,ProgressDialog pb) throws Exception{
        //创建本地文件对象
        File file = new File(Environment.getExternalStorageDirectory(), getFileName(path));
        //创建HttpURL连接
        URL url = new URL(path);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setConnectTimeout(5000);
        conn.setRequestMethod("GET");
        if(conn.getResponseCode() == 200){
            int max = conn.getContentLength();
            //设置进度条对话框的最大值
            pb.setMax(max);
            int count = 0;
            InputStream is = conn.getInputStream();
            FileOutputStream fos = new FileOutputStream(file);
            byte[] buffer = new byte[1024];
            int len = 0;
            while((len = is.read(buffer)) != -1){
                fos.write(buffer, 0, len);
                //设置进度条对话框进度
                count = count + len;
                pb.setProgress(count);
            }
            is.close();
            fos.close();
        }
        return file;
    }  

4. 安装apk文件

 /**
 * 安装apk文件流程
 *
 * a. 设置Action : Intent.ACTION_VIEW.
 * b. 设置数据和类型 : 设置apk文件的uri 和 MIME类型
 * c. 开启安装文件的Activity.
 */
protected void installApk() {
    Intent intent = new Intent();
    intent.setAction(Intent.ACTION_VIEW);
    intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
    startActivity(intent);
}

五. 相关的源码

(1) 布局文件

splash.xml

 <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/ivt_splash"
    android:id="@+id/splash_rl">  

    <ProgressBar android:id="@+id/pb"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="30dip"/>  

    <TextView android:id="@+id/tv_version"
       android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_above="@id/pb"
        android:layout_marginBottom="60dip"
        android:textSize="30sp"
        android:textColor="#17A6E8"
        android:text="version"
        />
</RelativeLayout> 

(2) Activity页面切换动画

main_in.xml

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
         >
        <translate
            android:fromXDelta="100%p"
            android:toXDelta="0"
            android:fromYDelta="0"
            android:toYDelta="0"
            android:duration="200"
            />
    </set>  

splash_out.xml

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android"
         >
        <translate
            android:fromXDelta="0"
            android:toXDelta="-100%p"
            android:fromYDelta="0"
            android:toYDelta="0"
            android:duration="200"
            />
    </set>  

(3) SplashActivity源码

SplashActivity.java

        public class SplashActivity extends Activity {  

            private static final String TAG = "SplashActivity";  

            public static final int ERROR_GET_UPDATEINOF = 0;
            public static final int SUCESS_GET_UPDATEINOF = 1;
            public static final int ERROR_DOWNLOAD_APK = 2;
            public static final int SUCCESS_DOWNLOAD_APK = 3;  

            private static final String XML_FILE_DIRECTORY = "updateinfo.xml";
            private static final String UPDATE_FOLDER_DIRECTORY = "/webupdate/";  

            private TextView tv_version;
            private PackageManager pm;
            private String version;
            private UpdateInfo updateInfo;  

            private Dialog mUpdateInfoDialog;
            private ProgressDialog mPb;
            private File apkFile;  

            private RelativeLayout splash_rl;
            private long time;
            private SharedPreferences sp;  

            private Handler mHandler = new Handler(){
                public void handleMessage(android.os.Message msg) {
                    switch (msg.what) {
                    /*
                     * 获取更新信息错误 , 在断网或者获取信息出现异常执行
                     * 提示一下, 之后进入主界面
                     */
                    case ERROR_GET_UPDATEINOF:
                        ToastHint.getInstance().showHint(R.string.fail_to_get_updateinfo);
                        loadMainUI();
                        break;
                    /*
                     * 成功获取更新信息, 一般在成功从网上获取xml文件并解析出来
                     * 如果版本号相同, 说明不用更新, 直接进入主界面
                     * 如果版本号不同, 需要弹出更新对话框
                     */
                    case SUCESS_GET_UPDATEINOF:
                        if(updateInfo.getVersion().equals(version)){
                            loadMainUI();
                        }else{
                            showUpdateDialog();
                        }
                        break;
                    /*
                     * 下载apk文件出现错误, 中途断网 出现异常等情况
                     * 提示后进入主界面
                     */
                    case ERROR_DOWNLOAD_APK:
                        mPb.dismiss();
                        ToastHint.getInstance().showHint(R.string.fail_to_get_apk);
                        loadMainUI();
                        break;
                    /*
                     * 成功下载apk文件之后执行的操作
                     * 取消进度条对话框, 之后安装apk文件
                     */
                    case SUCCESS_DOWNLOAD_APK:
                        mPb.dismiss();
                        installApk();
                        break;
                    default:
                        break;
                    }
                };
            };  

            /**
             * 创建Activity时调用
             *
             * ① 设置全屏显示, 由于是Splash界面, 因此不能有标题
             * ② 设置布局, 版本号, 执行动画
             * ③ 设置当前时间
             * ④ 获取SharedPerference配置文件
             * ⑤ 开启检查版本号线程, 后续操作都在改线程中操作
             *
             */
            @Override
            public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                //隐藏标题栏
                requestWindowFeature(Window.FEATURE_NO_TITLE);
                //隐藏状态栏
                getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                        WindowManager.LayoutParams.FLAG_FULLSCREEN);
                //设置布局
                setContentView(R.layout.splash);  

                /*
                 *  显示当前软件的版本号
                 *  获取布局中的TextView控件, 将版本号设置到这个TextView控件中
                 */
                tv_version = (TextView) findViewById(R.id.tv_version);
                version =getString(R.string.current_version) + " " + getVersion();
                tv_version.setText(version);  

                /*
                 *  在界面设置一个动画, 用来表明正在运行
                 *  a. 获取布局
                 *  b. 创建一个动画对象
                 *  c. 将动画设置到布局中
                 */
                splash_rl = (RelativeLayout) findViewById(R.id.splash_rl);
                AlphaAnimation alphaAnimation = new AlphaAnimation(0.0f, 1.0f);
                alphaAnimation.setDuration(2000);
                splash_rl.setAnimation(alphaAnimation);  

                /*
                 * 这个时间值是用来控制Splash界面显示时间的
                 * 记录下这个值, 然后执行到下面, 如果时间差在3秒以内,
                 * 就执行下面的操作, 如果时间差不足3秒, 就Thread.sleep时间差
                 * 等够3秒在执行下面的操作
                 */
                time = System.currentTimeMillis();  

                //从SharedPreference中获取一些配置
                sp = getSharedPreferences("config", Context.MODE_PRIVATE);  

                //开启检查版本号线程
                new Thread(new CheckVersionTask()).start();
            }  

            private final class CheckVersionTask implements Runnable{
                public void run() {
                    try {
                        /*
                         * 获取当前时间, 与onCreate方法中获取的时间进行比较
                         * 如果不足3秒, 在等待够3秒之后在执行下面的操作
                         */
                        long temp = System.currentTimeMillis();
                        if(temp - time < 3000){
                            SystemClock.sleep(temp - time);
                        }  

                        /*
                         * 检查配置文件中的设置, 是否设置了自动更新;
                         * 如果设置了自动更新, 就执行下面的操作,
                         * 如果没有设置自动更新, 就直接进入主界面
                         */
                        boolean is_auto_update = sp.getBoolean("is_auto_update", true);
                        if(!is_auto_update){
                            loadMainUI();
                            return;
                        }  

                        /*
                         * 获取更新信息
                         * 如果信息不为null, 向handler发信息SUCESS_GET_UPDATEINOF, 执行后续操作
                         * 如果信息为null, 向handler发信息ERROR_GET_UPDATEINOF, 执行后续操作
                         * 如果出现异常, 向handler发信息ERROR_GET_UPDATEINOF, 执行后续操作
                         */
                        updateInfo = getUpdateInfo(SettingsFactory.readWebLoadUrl(getApplicationContext()) + UPDATE_FOLDER_DIRECTORY + XML_FILE_DIRECTORY);
                        if(updateInfo != null){
                            Message msg = new Message();
                            msg.what = SUCESS_GET_UPDATEINOF;
                            mHandler.sendMessage(msg);
                        }else{
                            Message msg = new Message();
                            msg.what = ERROR_GET_UPDATEINOF;
                            mHandler.sendMessage(msg);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        Message msg = new Message();
                        msg.what = ERROR_GET_UPDATEINOF;
                        mHandler.sendMessage(msg);
                    }
                }
            }  

            /**
             * 安装apk文件流程
             *
             * a. 设置Action : Intent.ACTION_VIEW.
             * b. 设置数据和类型 : 设置apk文件的uri 和 MIME类型
             * c. 开启安装文件的Activity.
             */
            protected void installApk() {
                Intent intent = new Intent();
                intent.setAction(Intent.ACTION_VIEW);
                intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
                startActivity(intent);
            }  

            /**
             * 弹出更新对话框
             *
             * a. 创建builder对象
             * b. 设置标题
             * c. 设置对话框显示信息
             * d. 设置该对话框不可回退, 如果回退的话就会卡在本界面
             * e. 设置确定按钮
             * f. 设置取消按钮
             * g. 创建对话框
             * h. 显示对话框
             *
             * 确定按钮按下显示进度条对话框
             * a. 创建一个进度条对话框
             * b. 设置该对话框不能回退
             * c. 设置进度条样式
             * d. 设置进度条的信息
             * e. 显示进度条对话框
             * f. 开启一个线程, 下载apk文件
             */
            protected void showUpdateDialog() {
                //创建builder对象
                AlertDialog.Builder builder = new AlertDialog.Builder(this);
                //设置标题
                builder.setTitle(getString(R.string.update_dialog_tittle));
                //设置对话框信息
                builder.setMessage(updateInfo.getDescription());
                //设置不可回退
                builder.setCancelable(false);
                //设置确定按钮
                builder.setPositiveButton(getString(R.string.confirm), new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        //创建进度条对话框
                        mPb = new ProgressDialog(SplashActivity.this);
                        //设置进度条对话框不可回退
                        mPb.setCancelable(false);
                        //设置进度条对话框样式
                        mPb.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                        //设置进度条对话框的信息
                        mPb.setMessage(getString(R.string.update_dialog_messsage));
                        //显示进度条对话框
                        mPb.show();
                        //开启显示进度条对话框线程
                        new Thread(new DownloadApkTask()).start();
                    }
                });
                builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        loadMainUI();
                    }
                });
                //创建更新信息提示对话框
                mUpdateInfoDialog = builder.create();
                //显示更新信息提示对话框
                mUpdateInfoDialog.show();
            }  

            /**
             * 在这个线程中主要执行downloadApk方法, 这个方法传入apk路径和进度条对话框
             * 注意 : 下载的前提是sd卡的状态是挂载的
             */
            private final class DownloadApkTask implements Runnable{
                public void run() {
                    if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
                        try {
                            SystemClock.sleep(2000);
                            apkFile = downloadApk(SettingsFactory.readWebLoadUrl(getApplicationContext()) + UPDATE_FOLDER_DIRECTORY + updateInfo.url,mPb);
                            Message msg = new Message();
                            msg.what = SUCCESS_DOWNLOAD_APK;
                            mHandler.sendMessage(msg);
                        } catch (Exception e) {
                            e.printStackTrace();
                            Message msg = new Message();
                            msg.what = ERROR_DOWNLOAD_APK;
                            mHandler.sendMessage(msg);
                        }
                    }
                }
            }  

            /**
             * 下载apk更新文件
             *
             * a. 根据SD卡路径创建文件对象, 这个文件用来保存下载的文件
             * b. 创建URL对象
             * c. 创建HttpUrlConnection对象
             * d. 设置链接对象超时时间
             * e. 设置请求方式 get
             * f. 如果请求成功执行下面的操作
             *
             * g. 通过链接对象获取网络资源的大小
             * h. 将文件大小设置给进度条对话框
             * i. 获取输入流, 并且读取输入流信息
             * j. 根据读取到的字节数, 将已经读取的数据设置给进度条对话框
             */
            public File downloadApk(String path,ProgressDialog pb) throws Exception{
                //创建本地文件对象
                File file = new File(Environment.getExternalStorageDirectory(), getFileName(path));
                //创建HttpURL连接
                URL url = new URL(path);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setConnectTimeout(5000);
                conn.setRequestMethod("GET");
                if(conn.getResponseCode() == 200){
                    int max = conn.getContentLength();
                    //设置进度条对话框的最大值
                    pb.setMax(max);
                    int count = 0;
                    InputStream is = conn.getInputStream();
                    FileOutputStream fos = new FileOutputStream(file);
                    byte[] buffer = new byte[1024];
                    int len = 0;
                    while((len = is.read(buffer)) != -1){
                        fos.write(buffer, 0, len);
                        //设置进度条对话框进度
                        count = count + len;
                        pb.setProgress(count);
                    }
                    is.close();
                    fos.close();
                }
                return file;
            }  

            private String getFileName(String path){
                return path.substring(path.lastIndexOf("/") + 1);
            }  

            private String getVersion() {
                try {
                    pm = this.getPackageManager();
                    PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);
                    return packageInfo.versionName;
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;
            }  

            /**
             * 获取更新信息
             *      ① 根据字符串地址创建URL对象
             *      ② 根据URL对象创建HttpURLConnection链接对象
             *      ③ 设置链接对象5秒超时
             *      ④ 设置链接对象获取的方式为get方式
             *      ⑤ 如果成功连接, conn.getRequestCode值就是200, 此时就可以获取输入流
             *      ⑥ 解析输入流获取更新信息
             *
             */
            private UpdateInfo getUpdateInfo(String path){
                try {
                    URL url = new URL(path);    //创建URL对象
                    //创建连接对象
                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                    //设置链接超时
                    conn.setConnectTimeout(5000);
                    //设置获取方式
                    conn.setRequestMethod("GET");
                    //如果连接成功, 获取输入流
                    if(conn.getResponseCode() == 200){
                        InputStream is = conn.getInputStream();
                        //解析输入流中的数据, 返回更新信息
                        return parserUpdateInfo(is);
                    }
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                } catch (ProtocolException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return null;
            }  

            /**
             * 获取更新信息
             *      ① 创建pull解析器
             *      ② 为解析器设置编码格式
             *      ③ 获取解析事件
             *      ④ 遍历整个xml文件节点, 获取标签元素内容
             */
            private UpdateInfo parserUpdateInfo(InputStream is){
                try {
                    UpdateInfo updateInfo = null;
                    //1. 创建pull解析解析器
                    XmlPullParser parser = Xml.newPullParser();
                    //2. 设置解析编码
                    parser.setInput(is, "UTF-8");
                    //3. 获取解析器解事件, 如解析到文档开始 , 结尾, 标签等
                    int eventType = parser.getEventType();
                    //4. 在文档结束前一直解析
                    while (eventType != XmlPullParser.END_DOCUMENT) {
                        switch (eventType) {
                        //只解析标签
                        case XmlPullParser.START_TAG:
                            if ("updateInfo".equals(parser.getName())) {
                                //当解析到updateInfo标签的时候, 跟标签开始, 创建一个UpdateInfo对象
                                updateInfo = new UpdateInfo();
                            } else if ("version".equals(parser.getName())) {
                                //解析版本号标签
                                updateInfo.setVersion(parser.nextText());
                            } else if ("url".equals(parser.getName())) {
                                //解析url标签
                                updateInfo.setUrl(parser.nextText());
                            } else if ("description".equals(parser.getName())) {
                                //解析描述标签
                                updateInfo.setDescription(parser.nextText());
                            }
                            break;
                        default:
                            break;
                        }
                        //每解析完一个元素, 就将解析标志位下移
                        eventType = parser.next();
                    }
                    is.close();
                    return updateInfo;
                } catch (XmlPullParserException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return null;
            }  

            private void loadMainUI(){
                Intent intent = new Intent(this,HomeActivity.class);
                startActivity(intent);
                finish();
                overridePendingTransition(R.anim.main_in, R.anim.splash_out);
            }  

            public class UpdateInfo {
                private String version; //当前软件版本号
                private String url;     //获取到的软件地址
                private String description; //软件描述  

                public String getVersion() {
                    return version;
                }
                public void setVersion(String version) {
                    this.version = version;
                }
                public String getUrl() {
                    return url;
                }
                public void setUrl(String url) {
                    this.url = url;
                }
                public String getDescription() {
                    return description;
                }
                public void setDescription(String description) {
                    this.description = description;
                }
                @Override
                public String toString() {
                    return "UpdateInfo [version=" + version + ", url=" + url
                            + ", description=" + description + "]";
                }
            }
        }  

转载自:

http://www.cnblogs.com/android100/p/android-auto-update.html

时间: 2024-08-06 04:57:21

[转]Android应用的自动更新的相关文章

转载:Android应用的自动更新模块

软件的自动更新一般都与Splash界面绑定在一起, 由于需要维护的软件界面很复杂, 一个Activity中嵌入ViewPager, 并且逻辑比较复杂, 索性重新写一个Activity, 现在的软件都很流行使用Splash界面, 正好与自动更新配套在一起; 在这个自动更新Splash中, 使用到了 动画设置 ,SharedPerference ,pull解析 ,dialog对话框 ,http网络编程 ,handler 等. 注意一个错误 : 已安装具有该名称和不同签名的数据包 , 早上测试人员报告

使用crontab进行Android代码的自动更新和构建

引子 最近的工作是一个在Android平台上进行开发的项目,我个人基本是不改动Android部分的代码,但是我所在的项目需要使用到Android编译出来的很多目标文件.另一方面,我又不是开发apk等基于通用Android平台的项目,即Android部分的代码是有其他同事在进行维护.那么就会有这样的场景:我需要保持Android部分代码的更新和并构建出来. 编译过整个Android工程的人都知道编译一次的时间大概要30分钟以上(如果你是独占服务器且内存超大,那么请默默走开~),要是整个工程全部进行

Android实现APP自动更新功能

现在一般的android软件都是需要不断更新的,当你打开某个app的时候,如果有新的版本,它会提示你有新版本需要更新.该小程序实现的就是这个功能. 该小程序的特点是,当有更新时,会弹出一个提示框,点击确定,则在通知来创建一个进度条进行下载,点击取消,则取消更新. 以下是详细代码: 1.创建布局文件notification_item.xml,用于在通知栏生成一个进度条和下载图标. <?xml version="1.0" encoding="utf-8"?>

android 软件apk自动更新实现注意点!!

1,解析xml时的NetWorkOnMainThread问题 代码:这里要注意的点就是在访问服务器网络时,不能将InputStream直接返回,因为若直接返回给主线程操作,很可能子线程的InputStream还在获取字节流,这时候就会导致这个UI线程访问网络异常,所以不能直接返回这个InputStream,直接在子线程里 操作这个输入流,然后将操作的结果返回. private class AsyncTask_ConnVersion extends AsyncTask<String, Void,

Android应用自动更新功能的代码实现

由于Android项目开源所致,市面上出现了N多安卓软件市场.为了让我们开发的软件有更多的用户使用,我们需要向N多市场发布,软件升级后,我们也必须到安卓市场上进行更新,给我们增加了工作量.因此我们有必要给我们的Android应用增加自动更新的功能. 既然实现自动更新,我们首先必须让我们的应用知道是否存在新版本的软件,因此我们可以在自己的网站上放置配置文件,存放软件的版本信息: <update> <version>2</version> <name>baidu

Android 应用自动更新功能的代码

由于Android项目开源所致,市面上出现了N多安卓软件市场.为了让我们开发的软件有更多的用户使用,我们需要向N多市场发布,软件升级后,我们也必须到安卓市场上进行更新,给我们增加了工作量.因此我们有必要给我们的Android应用增加自动更新的功能. 既然实现自动更新,我们首先必须让我们的应用知道是否存在新版本的软件,因此我们可以在自己的网站上放置配置文件,存放软件的版本信息: <update> <version>2</version> <name>baidu

[转]Android应用自动更新功能的代码实现

本文转自:http://www.cnblogs.com/coolszy/archive/2012/04/27/2474279.html 由于Android项目开源所致,市面上出现了N多安卓软件市场.为了让我们开发的软件有更多的用户使用,我们需要向N多市场发布,软件升级后,我们也必须到安卓市场上进行更新,给我们增加了工作量.因此我们有必要给我们的Android应用增加自动更新的功能. 既然实现自动更新,我们首先必须让我们的应用知道是否存在新版本的软件,因此我们可以在自己的网站上放置配置文件,存放软

Android 自动更新 + IIS7 添加APK mime

如果APK文件放在IIS下面需要添加APK的mime,否则会出现下面错误 可以在IIS上添加mime映射 .apk application/vnd.android   下面内容转自:http://www.cnblogs.com/coolszy/archive/2012/04/27/2474279.html 由于Android项目开源所致,市面上出现了N多安卓软件市场.为了让我们开发的软件有更多的用户使用,我们需要向N多市场发布,软件升级后,我们也必须到安卓市场上进行更新,给我们增加了工作量.因此

android软件自动更新的实现步骤

本篇文章是直接下载最新的APK安装的方法,并不是增量下载该APk. 转载请注明出处:http://blog.csdn.net/harryweasley/article/details/44955719,谢谢 想要实现一个android应用,自动更新下载APK软件的方法,我采取的是以下几步方法: 1.每次进入主界面时,获取服务器的数据,看是否是最新版本,是,则无操作,否,则进行以下步骤: 2.弹出是否更新软件的对话框,点击下载后 3.弹出下载的进度条的对话框,开始下载,可以上随时点击按钮,停止下载