转载: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 + "]";
}
}
}
*******************************************************************************************************************************
感谢原文作者:安卓吧
原文地址: http://www.cnblogs.com/android100/p/android-auto-update.html
*******************************************************************************************************************************
转载:Android应用的自动更新模块的更多相关文章
- 【Android 应用开发】Android应用的自动更新模块
. 作者 :万境绝尘 转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/18964835 . 软件的自动更新一般都与Splash界 ...
- 安卓程序代写 网上程序代写[原]Android应用的自动更新模块
软件的自动更新一般都与Splash界面绑定在一起, 由于需要维护的软件界面很复杂, 一个Activity中嵌入ViewPager, 并且逻辑比较复杂, 索性重新写一个Activity, 现在的软件都很 ...
- Android应用的自动更新模块
软件的自动更新一般都与Splash界面绑定在一起, 由于需要维护的软件界面很复杂, 一个Activity中嵌入ViewPager, 并且逻辑比较复杂, 索性重新写一个Activity, 现在的软件都很 ...
- Winform开发框架之通用自动更新模块(转)
在网络化的环境中,特别是基于互联网发布的Winform程序,程序的自动更新功能是比较重要的操作,这样可以避免挨个给使用者打电话.发信息通知或者发送软件等,要求其对应用程序进行升级.实现程序的自动更新, ...
- Windows程序通用自动更新模块(C#,.NET4.5以上)
本通用自动更新模块适合所有Windows桌面程序的自动更新,不论语言,无论Winform还是wpf. 一.工作流程:1. 主程序A调起升级程序B2. B从服务器获取更新程序列表,打印更新信息.3. B ...
- [转]Android应用的自动更新
软件的自动更新一般都与Splash界面绑定在一起, 由于需要维护的软件界面很复杂, 一个Activity中嵌入ViewPager, 并且逻辑比较复杂, 索性重新写一个Activity, 现在的软件都很 ...
- Android接入百度自动更新SDK
一:前言 公司的app,上传到百度应用市场,然后说必须要接入百度的自动更新sdk才能上架,于是从百度官网上去下载jar包,下载的时候必须要带上数据统计,如果使用自动的jar包,还需要带上广告联盟,坑爹 ...
- android之apk自动更新解析包失败问题
在apk自动更新(相关问题可以看我的博客http://blog.csdn.net/caicongyang) 从服务器下载完成后,点击notification提示安装时,每次都报解析包失败错误!首先我想 ...
- Android App版本自动更新
App在开发过程中,随着业务场景的不断增多,功能的不断完善,早期下载App的用户便无法体验最新的功能,为了能让用户更及时的体验App最新版本,在App开发过程加入App自动更新功能便显得尤为重要.更新 ...
随机推荐
- 路由器桥接(WIFI无线中继)设置及摆放位置图解
路由器桥接(WIFI无线中继)设置及摆放位置图解 WIFI实在好用,但它的波覆盖面小.穿透力很差.我们安装时要考虑波的衍射特点,装在衍射效果最佳的位置(居中,室外可绕,避开密封墙).确实无法兼顾的地方 ...
- python selenium cookie 登录
概要: 1.正常登录,使用selenium获取cookie: 2.保存cookie: 3.使用cookie登录. 4.python--2.7,selenium--3.4.1 步骤1 正常登录,使用se ...
- Pinpoint扩展插件实践笔记
为链路(spanEvent)添加tag 背景 我们可能需要想在代码中写入特定的信息到调用链中,并且希望对里面的特定key做检索 实现思路 创建一个特定的类,只需要一个方法,再对这个类的方法进行增强,这 ...
- PAT 天梯赛 L2-016. 愿天下有情人都是失散多年的兄妹 【BFS】
题目链接 https://www.patest.cn/contests/gplt/L2-016 思路 用BFS 每层 遍历当代 并且查找当代是否有重复 有重复就跳出 然后 POP 并且将他们的下一代 ...
- m3u8格式转MP4
公司直播平台使用的是七牛直播,今天有客户表示想将直播回放视频下载下来,数据妹子犯了愁,表示这个不会下载给客户,于是乎这个任务就落在了我的头上.熟练的打开视频,在 HTML 源代码播放地址为 http: ...
- echarts相关设置
1.显示隐藏工具栏 注释toolbox即可 /* toolbox: { show : true, feature : { dataView ...
- codevs1199 开车旅行
[问题描述]小 A 和小 B 决定利用假期外出旅行,他们将想去的城市从 1 到 N 编号,且编号较小的城市在编号较大的城市的西边,已知各个城市的海拔高度互不相同,记城市 i 的海拔高度为H i ,城市 ...
- Android GreenDao 中文表名,中文字段DAO生成乱码的问题
在gradle.properties 文件中加入编码类型 # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gr ...
- IE和FireFox关于CSS的兼容性
1. [代码][Java]代码 CSS对浏览器的兼容性有时让人很头疼,或许当你了解当中的技巧跟原理,就会觉得也不是难事,从网上收集了IE7,6与Fireofx的兼容性处理技巧并整理了一下.对于web2 ...
- Codeforces 402D Upgrading Array:贪心 + 数学
题目链接:http://codeforces.com/problemset/problem/402/D 题意: 给你一个长度为n的数列a[i],又给出了m个“坏质数”b[i]. 定义函数f(s),其中 ...