您的位置:首页 > 移动开发 > Android开发

Android应用的自动更新模块

2015-10-11 22:56 591 查看


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


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.FLAG_KEEP_SCREEN_ON);

//②设置窗体始终点亮

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_BLUR_BEHIND);

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

}

}

}

分类: android
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android 自动更新