[回忆篇]Android应用的自动更新

转自CSDN,5年前写的第一篇技术类的文章,当时还是个小菜鸟,一转眼5年过去了

----------------------------------------------------------------------说正事分割线------------------------------------------------------------------------------------

软件的自动更新一般都与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  

android:name="myAcitivty"    

android:theme="@android:style/Theme.NoTitleBar.Fullscreen" />  



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


//①设置窗体始终点亮  

getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);  



//②设置窗体始终点亮  

getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);  



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


//③AndroidManifest.xml添加权限  

  

//设置窗体背景模糊  

getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND,WindowManager.LayoutParams.FLAG_BLUR_BEHIND);  


(3) 屏幕方向设置


a.配置文件实现 

//设置横屏  

       


//设置竖屏  

   



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);    //创建动画  

animation.setDuration(2000);  //设置渐变  

splash_rl.setAnimation(animation);    //设置动画载体  

创建动画吧: 创建的这个动画是透明度渐变动画, 传入浮点型参数, 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文件 


  

3.2  

http://127.0.0.1:8080/web/mobilesafe.apk  

  客户端更新  




(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 


android:layout_width="match_parent"  

android:layout_height="match_parent"  

android:background="@drawable/ivt_splash"   

android:id="@+id/splash_rl">  


android:layout_width="wrap_content"  

android:layout_height="wrap_content"  

android:layout_centerHorizontal="true"  

android:layout_alignParentBottom="true"  

android:layout_marginBottom="30dip"/>  


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"  

        />  




(2) Activity页面切换动画


main_in.xml 


     >  


android:fromXDelta="100%p"  

android:toXDelta="0"  

android:fromYDelta="0"  

android:toYDelta="0"   

android:duration="200"  

        />  




splash_out.xml 


     >  


android:fromXDelta="0"  

android:toXDelta="-100%p"  

android:fromYDelta="0"  

android:toYDelta="0"   

android:duration="200"  

        />  




(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 + "]";  

        }  

    }  

}  

你可能感兴趣的:([回忆篇]Android应用的自动更新)