Android应用的自动更新模块

时间:2023-01-09 22:15:02

软件的自动更新一般都与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()方法之前调用;

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

b. 配置实现 :

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

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

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

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

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

(3) 屏幕方向设置

a. 配置文件实现

  1. //设置横屏
  2. <activity android:name="myAcitivty"  android:screenOrientation="landscape" />
  3. //设置竖屏
  4. <activity android:name="myAcitivty"  android:screenOrientation="portrait" />

b. 代码实现

  1. //设置横屏
  2. setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
  3. //设置竖屏
  4. setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

c. 获取屏幕方向

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

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

3. 设置动画

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

(1) 创建动画

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

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

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

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

(2) 动画常用方法

a. 普通设置

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

b. 设置监听器

  1. alphaAnimation.setAnimationListener(new AnimationListener() {
  2. //动画开始时回调
  3. @Override
  4. public void onAnimationStart(Animation animation) {
  5. }
  6. //动画重复执行时回调
  7. @Override
  8. public void onAnimationRepeat(Animation animation) {
  9. }
  10. //动画执行结束时回调
  11. @Override
  12. public void onAnimationEnd(Animation animation) {
  13. }
  14. });

4. SharedPerference使用

  1. //获取SharedPerference
  2. SharedPreferences sharedPreferences = getSharedPreferences("sp", Context.MODE_PRIVATE);
  3. Editor editor = sharedPreferences.edit();   //获取Editor对象
  4. editor.putBoolean("isUpdate", true);        //向sp中写入数据
  5. editor.commit();                            //提交
  6. sharedPreferences.getBoolean("isUpdate", true);//获取sp中的变量

5. onCreate()方法代码

  1. /**
  2. * 创建Activity时调用
  3. *
  4. * ① 设置全屏显示, 由于是Splash界面, 因此不能有标题
  5. * ② 设置布局, 版本号, 执行动画
  6. * ③ 设置当前时间
  7. * ④ 获取SharedPerference配置文件
  8. * ⑤ 开启检查版本号线程, 后续操作都在改线程中操作
  9. *
  10. */
  11. @Override
  12. public void onCreate(Bundle savedInstanceState) {
  13. super.onCreate(savedInstanceState);
  14. //隐藏标题栏
  15. requestWindowFeature(Window.FEATURE_NO_TITLE);
  16. //隐藏状态栏
  17. getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
  18. WindowManager.LayoutParams.FLAG_FULLSCREEN);
  19. //设置布局
  20. setContentView(R.layout.splash);
  21. /*
  22. *  显示当前软件的版本号
  23. *  获取布局中的TextView控件, 将版本号设置到这个TextView控件中
  24. */
  25. tv_version = (TextView) findViewById(R.id.tv_version);
  26. version =getString(R.string.current_version) + " " + getVersion();
  27. tv_version.setText(version);
  28. /*
  29. *  在界面设置一个动画, 用来表明正在运行
  30. *  a. 获取布局
  31. *  b. 创建一个动画对象
  32. *  c. 将动画设置到布局中
  33. */
  34. splash_rl = (RelativeLayout) findViewById(R.id.splash_rl);
  35. AlphaAnimation animation = new AlphaAnimation(0.0f, 1.0f);
  36. animation.setDuration(2000);
  37. splash_rl.setAnimation(animation);
  38. /*
  39. * 这个时间值是用来控制Splash界面显示时间的
  40. * 记录下这个值, 然后执行到下面, 如果时间差在3秒以内,
  41. * 就执行下面的操作, 如果时间差不足3秒, 就Thread.sleep时间差
  42. * 等够3秒在执行下面的操作
  43. */
  44. time = System.currentTimeMillis();
  45. //从SharedPreference中获取一些配置
  46. sp = getSharedPreferences("config", Context.MODE_PRIVATE);
  47. //开启检查版本号线程
  48. new Thread(new CheckVersionTask()).start();
  49. }

二. 检查版本号

1. 检查版本号线程

流程 :

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

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

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

源码 :

  1. private final class CheckVersionTask implements Runnable{
  2. public void run() {
  3. try {
  4. /*
  5. * 获取当前时间, 与onCreate方法中获取的时间进行比较
  6. * 如果不足3秒, 在等待够3秒之后在执行下面的操作
  7. */
  8. long temp = System.currentTimeMillis();
  9. if(temp - time < 3000){
  10. SystemClock.sleep(temp - time);
  11. }
  12. /*
  13. * 检查配置文件中的设置, 是否设置了自动更新;
  14. * 如果设置了自动更新, 就执行下面的操作,
  15. * 如果没有设置自动更新, 就直接进入主界面
  16. */
  17. boolean is_auto_update = sp.getBoolean("is_auto_update", true);
  18. if(!is_auto_update){
  19. loadMainUI();
  20. return;
  21. }
  22. /*
  23. * 获取更新信息
  24. * 如果信息不为null, 向handler发信息SUCESS_GET_UPDATEINOF, 执行后续操作
  25. * 如果信息为null, 向handler发信息ERROR_GET_UPDATEINOF, 执行后续操作
  26. * 如果出现异常, 向handler发信息ERROR_GET_UPDATEINOF, 执行后续操作
  27. */
  28. updateInfo = getUpdateInfo(SettingsFactory.readWebLoadUrl(getApplicationContext()) + UPDATE_FOLDER_DIRECTORY + XML_FILE_DIRECTORY);
  29. if(updateInfo != null){
  30. Message msg = new Message();
  31. msg.what = SUCESS_GET_UPDATEINOF;
  32. mHandler.sendMessage(msg);
  33. }else{
  34. Message msg = new Message();
  35. msg.what = ERROR_GET_UPDATEINOF;
  36. mHandler.sendMessage(msg);
  37. }
  38. } catch (Exception e) {
  39. e.printStackTrace();
  40. Message msg = new Message();
  41. msg.what = ERROR_GET_UPDATEINOF;
  42. mHandler.sendMessage(msg);
  43. }
  44. }
  45. }

2. 获取版本号方法

流程 :

a. 创URL建对象;

b. 创建HttpURLConnection对象;

c. 设置超时时间;

d. 设置获取方式;

e. 查看链接是否成功;

f. 解析输入流信息;

源码 :

  1. /**
  2. * 获取更新信息
  3. *      ① 根据字符串地址创建URL对象
  4. *      ② 根据URL对象创建HttpURLConnection链接对象
  5. *      ③ 设置链接对象5秒超时
  6. *      ④ 设置链接对象获取的方式为get方式
  7. *      ⑤ 如果成功连接, conn.getRequestCode值就是200, 此时就可以获取输入流
  8. *      ⑥ 解析输入流获取更新信息
  9. *
  10. */
  11. private UpdateInfo getUpdateInfo(String path){
  12. try {
  13. URL url = new URL(path);    //创建URL对象
  14. //创建连接对象
  15. HttpURLConnection conn = (HttpURLConnection) url.openConnection();
  16. //设置链接超时
  17. conn.setConnectTimeout(5000);
  18. //设置获取方式
  19. conn.setRequestMethod("GET");
  20. //如果连接成功, 获取输入流
  21. if(conn.getResponseCode() == 200){
  22. InputStream is = conn.getInputStream();
  23. //解析输入流中的数据, 返回更新信息
  24. return parserUpdateInfo(is);
  25. }
  26. } catch (MalformedURLException e) {
  27. e.printStackTrace();
  28. } catch (ProtocolException e) {
  29. e.printStackTrace();
  30. } catch (IOException e) {
  31. e.printStackTrace();
  32. }
  33. return null;
  34. }

3. 更新信息对象

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

  1. public class UpdateInfo {
  2. private String version; //当前软件版本号
  3. private String url;     //获取到的软件地址
  4. private String description; //软件描述
  5. public String getVersion() {
  6. return version;
  7. }
  8. public void setVersion(String version) {
  9. this.version = version;
  10. }
  11. public String getUrl() {
  12. return url;
  13. }
  14. public void setUrl(String url) {
  15. this.url = url;
  16. }
  17. public String getDescription() {
  18. return description;
  19. }
  20. public void setDescription(String description) {
  21. this.description = description;
  22. }
  23. @Override
  24. public String toString() {
  25. return "UpdateInfo [version=" + version + ", url=" + url
  26. + ", description=" + description + "]";
  27. }
  28. }

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文件

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

(3) 源码

  1. /**
  2. * 获取更新信息
  3. *      ① 创建pull解析器
  4. *      ② 为解析器设置编码格式
  5. *      ③ 获取解析事件
  6. *      ④ 遍历整个xml文件节点, 获取标签元素内容
  7. */
  8. private UpdateInfo parserUpdateInfo(InputStream is){
  9. try {
  10. UpdateInfo updateInfo = null;
  11. //1. 创建pull解析解析器
  12. XmlPullParser parser = Xml.newPullParser();
  13. //2. 设置解析编码
  14. parser.setInput(is, "UTF-8");
  15. //3. 获取解析器解事件, 如解析到文档开始 , 结尾, 标签等
  16. int eventType = parser.getEventType();
  17. //4. 在文档结束前一直解析
  18. while (eventType != XmlPullParser.END_DOCUMENT) {
  19. switch (eventType) {
  20. //只解析标签
  21. case XmlPullParser.START_TAG:
  22. if ("updateInfo".equals(parser.getName())) {
  23. //当解析到updateInfo标签的时候, 跟标签开始, 创建一个UpdateInfo对象
  24. updateInfo = new UpdateInfo();
  25. } else if ("version".equals(parser.getName())) {
  26. //解析版本号标签
  27. updateInfo.setVersion(parser.nextText());
  28. } else if ("url".equals(parser.getName())) {
  29. //解析url标签
  30. updateInfo.setUrl(parser.nextText());
  31. } else if ("description".equals(parser.getName())) {
  32. //解析描述标签
  33. updateInfo.setDescription(parser.nextText());
  34. }
  35. break;
  36. default:
  37. break;
  38. }
  39. //每解析完一个元素, 就将解析标志位下移
  40. eventType = parser.next();
  41. }
  42. is.close();
  43. return updateInfo;
  44. } catch (XmlPullParserException e) {
  45. e.printStackTrace();
  46. } catch (IOException e) {
  47. e.printStackTrace();
  48. }
  49. return null;
  50. }

三. Handler对象

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

  1. private Handler mHandler = new Handler(){
  2. public void handleMessage(android.os.Message msg) {
  3. switch (msg.what) {
  4. /*
  5. * 获取更新信息错误 , 在断网或者获取信息出现异常执行
  6. * 提示一下, 之后进入主界面
  7. */
  8. case ERROR_GET_UPDATEINOF:
  9. ToastHint.getInstance().showHint(R.string.fail_to_get_updateinfo);
  10. loadMainUI();
  11. break;
  12. /*
  13. * 成功获取更新信息, 一般在成功从网上获取xml文件并解析出来
  14. * 如果版本号相同, 说明不用更新, 直接进入主界面
  15. * 如果版本号不同, 需要弹出更新对话框
  16. */
  17. case SUCESS_GET_UPDATEINOF:
  18. if(updateInfo.getVersion().equals(version)){
  19. loadMainUI();
  20. }else{
  21. showUpdateDialog();
  22. }
  23. break;
  24. /*
  25. * 下载apk文件出现错误, 中途断网 出现异常等情况
  26. * 提示后进入主界面
  27. */
  28. case ERROR_DOWNLOAD_APK:
  29. mPb.dismiss();
  30. ToastHint.getInstance().showHint(R.string.fail_to_get_apk);
  31. loadMainUI();
  32. break;
  33. /*
  34. * 成功下载apk文件之后执行的操作
  35. * 取消进度条对话框, 之后安装apk文件
  36. */
  37. case SUCCESS_DOWNLOAD_APK:
  38. mPb.dismiss();
  39. installApk();
  40. break;
  41. default:
  42. break;
  43. }
  44. };
  45. };

四. 下载安装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) 源码

  1. /**
  2. * 弹出更新对话框
  3. *
  4. * a. 创建builder对象
  5. * b. 设置标题
  6. * c. 设置对话框显示信息
  7. * d. 设置该对话框不可回退, 如果回退的话就会卡在本界面
  8. * e. 设置确定按钮
  9. * f. 设置取消按钮
  10. * g. 创建对话框
  11. * h. 显示对话框
  12. *
  13. * 确定按钮按下显示进度条对话框
  14. * a. 创建一个进度条对话框
  15. * b. 设置该对话框不能回退
  16. * c. 设置进度条样式
  17. * d. 设置进度条的信息
  18. * e. 显示进度条对话框
  19. * f. 开启一个线程, 下载apk文件
  20. */
  21. protected void showUpdateDialog() {
  22. //创建builder对象
  23. AlertDialog.Builder builder = new AlertDialog.Builder(this);
  24. //设置标题
  25. builder.setTitle(getString(R.string.update_dialog_tittle));
  26. //设置对话框信息
  27. builder.setMessage(updateInfo.getDescription());
  28. //设置不可回退
  29. builder.setCancelable(false);
  30. //设置确定按钮
  31. builder.setPositiveButton(getString(R.string.confirm), new DialogInterface.OnClickListener() {
  32. public void onClick(DialogInterface dialog, int which) {
  33. //创建进度条对话框
  34. mPb = new ProgressDialog(SplashActivity.this);
  35. //设置进度条对话框不可回退
  36. mPb.setCancelable(false);
  37. //设置进度条对话框样式
  38. mPb.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
  39. //设置进度条对话框的信息
  40. mPb.setMessage(getString(R.string.update_dialog_messsage));
  41. //显示进度条对话框
  42. mPb.show();
  43. //开启显示进度条对话框线程
  44. new Thread(new DownloadApkTask()).start();
  45. }
  46. });
  47. builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
  48. public void onClick(DialogInterface dialog, int which) {
  49. loadMainUI();
  50. }
  51. });
  52. //创建更新信息提示对话框
  53. mUpdateInfoDialog = builder.create();
  54. //显示更新信息提示对话框
  55. mUpdateInfoDialog.show();
  56. }

2. 下载apk线程

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

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

  1. /**
  2. * 下载apk更新文件
  3. *
  4. * a. 根据SD卡路径创建文件对象, 这个文件用来保存下载的文件
  5. * b. 创建URL对象
  6. * c. 创建HttpUrlConnection对象
  7. * d. 设置链接对象超时时间
  8. * e. 设置请求方式 get
  9. * f. 如果请求成功执行下面的操作
  10. *
  11. * g. 通过链接对象获取网络资源的大小
  12. * h. 将文件大小设置给进度条对话框
  13. * i. 获取输入流, 并且读取输入流信息
  14. * j. 根据读取到的字节数, 将已经读取的数据设置给进度条对话框
  15. */
  16. public File downloadApk(String path,ProgressDialog pb) throws Exception{
  17. //创建本地文件对象
  18. File file = new File(Environment.getExternalStorageDirectory(), getFileName(path));
  19. //创建HttpURL连接
  20. URL url = new URL(path);
  21. HttpURLConnection conn = (HttpURLConnection) url.openConnection();
  22. conn.setConnectTimeout(5000);
  23. conn.setRequestMethod("GET");
  24. if(conn.getResponseCode() == 200){
  25. int max = conn.getContentLength();
  26. //设置进度条对话框的最大值
  27. pb.setMax(max);
  28. int count = 0;
  29. InputStream is = conn.getInputStream();
  30. FileOutputStream fos = new FileOutputStream(file);
  31. byte[] buffer = new byte[1024];
  32. int len = 0;
  33. while((len = is.read(buffer)) != -1){
  34. fos.write(buffer, 0, len);
  35. //设置进度条对话框进度
  36. count = count + len;
  37. pb.setProgress(count);
  38. }
  39. is.close();
  40. fos.close();
  41. }
  42. return file;
  43. }

4. 安装apk文件

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

五. 相关的源码

(1) 布局文件

splash.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:background="@drawable/ivt_splash"
  6. android:id="@+id/splash_rl">
  7. <ProgressBar android:id="@+id/pb"
  8. android:layout_width="wrap_content"
  9. android:layout_height="wrap_content"
  10. android:layout_centerHorizontal="true"
  11. android:layout_alignParentBottom="true"
  12. android:layout_marginBottom="30dip"/>
  13. <TextView android:id="@+id/tv_version"
  14. android:layout_width="wrap_content"
  15. android:layout_height="wrap_content"
  16. android:layout_centerHorizontal="true"
  17. android:layout_above="@id/pb"
  18. android:layout_marginBottom="60dip"
  19. android:textSize="30sp"
  20. android:textColor="#17A6E8"
  21. android:text="version"
  22. />
  23. </RelativeLayout>

(2) Activity页面切换动画

main_in.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <set xmlns:android="http://schemas.android.com/apk/res/android"
  3. >
  4. <translate
  5. android:fromXDelta="100%p"
  6. android:toXDelta="0"
  7. android:fromYDelta="0"
  8. android:toYDelta="0"
  9. android:duration="200"
  10. />
  11. </set>

splash_out.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <set xmlns:android="http://schemas.android.com/apk/res/android"
  3. >
  4. <translate
  5. android:fromXDelta="0"
  6. android:toXDelta="-100%p"
  7. android:fromYDelta="0"
  8. android:toYDelta="0"
  9. android:duration="200"
  10. />
  11. </set>

(3) SplashActivity源码

SplashActivity.java

    1. public class SplashActivity extends Activity {
    2. private static final String TAG = "SplashActivity";
    3. public static final int ERROR_GET_UPDATEINOF = 0;
    4. public static final int SUCESS_GET_UPDATEINOF = 1;
    5. public static final int ERROR_DOWNLOAD_APK = 2;
    6. public static final int SUCCESS_DOWNLOAD_APK = 3;
    7. private static final String XML_FILE_DIRECTORY = "updateinfo.xml";
    8. private static final String UPDATE_FOLDER_DIRECTORY = "/webupdate/";
    9. private TextView tv_version;
    10. private PackageManager pm;
    11. private String version;
    12. private UpdateInfo updateInfo;
    13. private Dialog mUpdateInfoDialog;
    14. private ProgressDialog mPb;
    15. private File apkFile;
    16. private RelativeLayout splash_rl;
    17. private long time;
    18. private SharedPreferences sp;
    19. private Handler mHandler = new Handler(){
    20. public void handleMessage(android.os.Message msg) {
    21. switch (msg.what) {
    22. /*
    23. * 获取更新信息错误 , 在断网或者获取信息出现异常执行
    24. * 提示一下, 之后进入主界面
    25. */
    26. case ERROR_GET_UPDATEINOF:
    27. ToastHint.getInstance().showHint(R.string.fail_to_get_updateinfo);
    28. loadMainUI();
    29. break;
    30. /*
    31. * 成功获取更新信息, 一般在成功从网上获取xml文件并解析出来
    32. * 如果版本号相同, 说明不用更新, 直接进入主界面
    33. * 如果版本号不同, 需要弹出更新对话框
    34. */
    35. case SUCESS_GET_UPDATEINOF:
    36. if(updateInfo.getVersion().equals(version)){
    37. loadMainUI();
    38. }else{
    39. showUpdateDialog();
    40. }
    41. break;
    42. /*
    43. * 下载apk文件出现错误, 中途断网 出现异常等情况
    44. * 提示后进入主界面
    45. */
    46. case ERROR_DOWNLOAD_APK:
    47. mPb.dismiss();
    48. ToastHint.getInstance().showHint(R.string.fail_to_get_apk);
    49. loadMainUI();
    50. break;
    51. /*
    52. * 成功下载apk文件之后执行的操作
    53. * 取消进度条对话框, 之后安装apk文件
    54. */
    55. case SUCCESS_DOWNLOAD_APK:
    56. mPb.dismiss();
    57. installApk();
    58. break;
    59. default:
    60. break;
    61. }
    62. };
    63. };
    64. /**
    65. * 创建Activity时调用
    66. *
    67. * ① 设置全屏显示, 由于是Splash界面, 因此不能有标题
    68. * ② 设置布局, 版本号, 执行动画
    69. * ③ 设置当前时间
    70. * ④ 获取SharedPerference配置文件
    71. * ⑤ 开启检查版本号线程, 后续操作都在改线程中操作
    72. *
    73. */
    74. @Override
    75. public void onCreate(Bundle savedInstanceState) {
    76. super.onCreate(savedInstanceState);
    77. //隐藏标题栏
    78. requestWindowFeature(Window.FEATURE_NO_TITLE);
    79. //隐藏状态栏
    80. getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
    81. WindowManager.LayoutParams.FLAG_FULLSCREEN);
    82. //设置布局
    83. setContentView(R.layout.splash);
    84. /*
    85. *  显示当前软件的版本号
    86. *  获取布局中的TextView控件, 将版本号设置到这个TextView控件中
    87. */
    88. tv_version = (TextView) findViewById(R.id.tv_version);
    89. version =getString(R.string.current_version) + " " + getVersion();
    90. tv_version.setText(version);
    91. /*
    92. *  在界面设置一个动画, 用来表明正在运行
    93. *  a. 获取布局
    94. *  b. 创建一个动画对象
    95. *  c. 将动画设置到布局中
    96. */
    97. splash_rl = (RelativeLayout) findViewById(R.id.splash_rl);
    98. AlphaAnimation alphaAnimation = new AlphaAnimation(0.0f, 1.0f);
    99. alphaAnimation.setDuration(2000);
    100. splash_rl.setAnimation(alphaAnimation);
    101. /*
    102. * 这个时间值是用来控制Splash界面显示时间的
    103. * 记录下这个值, 然后执行到下面, 如果时间差在3秒以内,
    104. * 就执行下面的操作, 如果时间差不足3秒, 就Thread.sleep时间差
    105. * 等够3秒在执行下面的操作
    106. */
    107. time = System.currentTimeMillis();
    108. //从SharedPreference中获取一些配置
    109. sp = getSharedPreferences("config", Context.MODE_PRIVATE);
    110. //开启检查版本号线程
    111. new Thread(new CheckVersionTask()).start();
    112. }
    113. private final class CheckVersionTask implements Runnable{
    114. public void run() {
    115. try {
    116. /*
    117. * 获取当前时间, 与onCreate方法中获取的时间进行比较
    118. * 如果不足3秒, 在等待够3秒之后在执行下面的操作
    119. */
    120. long temp = System.currentTimeMillis();
    121. if(temp - time < 3000){
    122. SystemClock.sleep(temp - time);
    123. }
    124. /*
    125. * 检查配置文件中的设置, 是否设置了自动更新;
    126. * 如果设置了自动更新, 就执行下面的操作,
    127. * 如果没有设置自动更新, 就直接进入主界面
    128. */
    129. boolean is_auto_update = sp.getBoolean("is_auto_update", true);
    130. if(!is_auto_update){
    131. loadMainUI();
    132. return;
    133. }
    134. /*
    135. * 获取更新信息
    136. * 如果信息不为null, 向handler发信息SUCESS_GET_UPDATEINOF, 执行后续操作
    137. * 如果信息为null, 向handler发信息ERROR_GET_UPDATEINOF, 执行后续操作
    138. * 如果出现异常, 向handler发信息ERROR_GET_UPDATEINOF, 执行后续操作
    139. */
    140. updateInfo = getUpdateInfo(SettingsFactory.readWebLoadUrl(getApplicationContext()) + UPDATE_FOLDER_DIRECTORY + XML_FILE_DIRECTORY);
    141. if(updateInfo != null){
    142. Message msg = new Message();
    143. msg.what = SUCESS_GET_UPDATEINOF;
    144. mHandler.sendMessage(msg);
    145. }else{
    146. Message msg = new Message();
    147. msg.what = ERROR_GET_UPDATEINOF;
    148. mHandler.sendMessage(msg);
    149. }
    150. } catch (Exception e) {
    151. e.printStackTrace();
    152. Message msg = new Message();
    153. msg.what = ERROR_GET_UPDATEINOF;
    154. mHandler.sendMessage(msg);
    155. }
    156. }
    157. }
    158. /**
    159. * 安装apk文件流程
    160. *
    161. * a. 设置Action : Intent.ACTION_VIEW.
    162. * b. 设置数据和类型 : 设置apk文件的uri 和 MIME类型
    163. * c. 开启安装文件的Activity.
    164. */
    165. protected void installApk() {
    166. Intent intent = new Intent();
    167. intent.setAction(Intent.ACTION_VIEW);
    168. intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
    169. startActivity(intent);
    170. }
    171. /**
    172. * 弹出更新对话框
    173. *
    174. * a. 创建builder对象
    175. * b. 设置标题
    176. * c. 设置对话框显示信息
    177. * d. 设置该对话框不可回退, 如果回退的话就会卡在本界面
    178. * e. 设置确定按钮
    179. * f. 设置取消按钮
    180. * g. 创建对话框
    181. * h. 显示对话框
    182. *
    183. * 确定按钮按下显示进度条对话框
    184. * a. 创建一个进度条对话框
    185. * b. 设置该对话框不能回退
    186. * c. 设置进度条样式
    187. * d. 设置进度条的信息
    188. * e. 显示进度条对话框
    189. * f. 开启一个线程, 下载apk文件
    190. */
    191. protected void showUpdateDialog() {
    192. //创建builder对象
    193. AlertDialog.Builder builder = new AlertDialog.Builder(this);
    194. //设置标题
    195. builder.setTitle(getString(R.string.update_dialog_tittle));
    196. //设置对话框信息
    197. builder.setMessage(updateInfo.getDescription());
    198. //设置不可回退
    199. builder.setCancelable(false);
    200. //设置确定按钮
    201. builder.setPositiveButton(getString(R.string.confirm), new DialogInterface.OnClickListener() {
    202. public void onClick(DialogInterface dialog, int which) {
    203. //创建进度条对话框
    204. mPb = new ProgressDialog(SplashActivity.this);
    205. //设置进度条对话框不可回退
    206. mPb.setCancelable(false);
    207. //设置进度条对话框样式
    208. mPb.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
    209. //设置进度条对话框的信息
    210. mPb.setMessage(getString(R.string.update_dialog_messsage));
    211. //显示进度条对话框
    212. mPb.show();
    213. //开启显示进度条对话框线程
    214. new Thread(new DownloadApkTask()).start();
    215. }
    216. });
    217. builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
    218. public void onClick(DialogInterface dialog, int which) {
    219. loadMainUI();
    220. }
    221. });
    222. //创建更新信息提示对话框
    223. mUpdateInfoDialog = builder.create();
    224. //显示更新信息提示对话框
    225. mUpdateInfoDialog.show();
    226. }
    227. /**
    228. * 在这个线程中主要执行downloadApk方法, 这个方法传入apk路径和进度条对话框
    229. * 注意 : 下载的前提是sd卡的状态是挂载的
    230. */
    231. private final class DownloadApkTask implements Runnable{
    232. public void run() {
    233. if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
    234. try {
    235. SystemClock.sleep(2000);
    236. apkFile = downloadApk(SettingsFactory.readWebLoadUrl(getApplicationContext()) + UPDATE_FOLDER_DIRECTORY + updateInfo.url,mPb);
    237. Message msg = new Message();
    238. msg.what = SUCCESS_DOWNLOAD_APK;
    239. mHandler.sendMessage(msg);
    240. } catch (Exception e) {
    241. e.printStackTrace();
    242. Message msg = new Message();
    243. msg.what = ERROR_DOWNLOAD_APK;
    244. mHandler.sendMessage(msg);
    245. }
    246. }
    247. }
    248. }
    249. /**
    250. * 下载apk更新文件
    251. *
    252. * a. 根据SD卡路径创建文件对象, 这个文件用来保存下载的文件
    253. * b. 创建URL对象
    254. * c. 创建HttpUrlConnection对象
    255. * d. 设置链接对象超时时间
    256. * e. 设置请求方式 get
    257. * f. 如果请求成功执行下面的操作
    258. *
    259. * g. 通过链接对象获取网络资源的大小
    260. * h. 将文件大小设置给进度条对话框
    261. * i. 获取输入流, 并且读取输入流信息
    262. * j. 根据读取到的字节数, 将已经读取的数据设置给进度条对话框
    263. */
    264. public File downloadApk(String path,ProgressDialog pb) throws Exception{
    265. //创建本地文件对象
    266. File file = new File(Environment.getExternalStorageDirectory(), getFileName(path));
    267. //创建HttpURL连接
    268. URL url = new URL(path);
    269. HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    270. conn.setConnectTimeout(5000);
    271. conn.setRequestMethod("GET");
    272. if(conn.getResponseCode() == 200){
    273. int max = conn.getContentLength();
    274. //设置进度条对话框的最大值
    275. pb.setMax(max);
    276. int count = 0;
    277. InputStream is = conn.getInputStream();
    278. FileOutputStream fos = new FileOutputStream(file);
    279. byte[] buffer = new byte[1024];
    280. int len = 0;
    281. while((len = is.read(buffer)) != -1){
    282. fos.write(buffer, 0, len);
    283. //设置进度条对话框进度
    284. count = count + len;
    285. pb.setProgress(count);
    286. }
    287. is.close();
    288. fos.close();
    289. }
    290. return file;
    291. }
    292. private String getFileName(String path){
    293. return path.substring(path.lastIndexOf("/") + 1);
    294. }
    295. private String getVersion() {
    296. try {
    297. pm = this.getPackageManager();
    298. PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0);
    299. return packageInfo.versionName;
    300. } catch (Exception e) {
    301. e.printStackTrace();
    302. }
    303. return null;
    304. }
    305. /**
    306. * 获取更新信息
    307. *      ① 根据字符串地址创建URL对象
    308. *      ② 根据URL对象创建HttpURLConnection链接对象
    309. *      ③ 设置链接对象5秒超时
    310. *      ④ 设置链接对象获取的方式为get方式
    311. *      ⑤ 如果成功连接, conn.getRequestCode值就是200, 此时就可以获取输入流
    312. *      ⑥ 解析输入流获取更新信息
    313. *
    314. */
    315. private UpdateInfo getUpdateInfo(String path){
    316. try {
    317. URL url = new URL(path);    //创建URL对象
    318. //创建连接对象
    319. HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    320. //设置链接超时
    321. conn.setConnectTimeout(5000);
    322. //设置获取方式
    323. conn.setRequestMethod("GET");
    324. //如果连接成功, 获取输入流
    325. if(conn.getResponseCode() == 200){
    326. InputStream is = conn.getInputStream();
    327. //解析输入流中的数据, 返回更新信息
    328. return parserUpdateInfo(is);
    329. }
    330. } catch (MalformedURLException e) {
    331. e.printStackTrace();
    332. } catch (ProtocolException e) {
    333. e.printStackTrace();
    334. } catch (IOException e) {
    335. e.printStackTrace();
    336. }
    337. return null;
    338. }
    339. /**
    340. * 获取更新信息
    341. *      ① 创建pull解析器
    342. *      ② 为解析器设置编码格式
    343. *      ③ 获取解析事件
    344. *      ④ 遍历整个xml文件节点, 获取标签元素内容
    345. */
    346. private UpdateInfo parserUpdateInfo(InputStream is){
    347. try {
    348. UpdateInfo updateInfo = null;
    349. //1. 创建pull解析解析器
    350. XmlPullParser parser = Xml.newPullParser();
    351. //2. 设置解析编码
    352. parser.setInput(is, "UTF-8");
    353. //3. 获取解析器解事件, 如解析到文档开始 , 结尾, 标签等
    354. int eventType = parser.getEventType();
    355. //4. 在文档结束前一直解析
    356. while (eventType != XmlPullParser.END_DOCUMENT) {
    357. switch (eventType) {
    358. //只解析标签
    359. case XmlPullParser.START_TAG:
    360. if ("updateInfo".equals(parser.getName())) {
    361. //当解析到updateInfo标签的时候, 跟标签开始, 创建一个UpdateInfo对象
    362. updateInfo = new UpdateInfo();
    363. } else if ("version".equals(parser.getName())) {
    364. //解析版本号标签
    365. updateInfo.setVersion(parser.nextText());
    366. } else if ("url".equals(parser.getName())) {
    367. //解析url标签
    368. updateInfo.setUrl(parser.nextText());
    369. } else if ("description".equals(parser.getName())) {
    370. //解析描述标签
    371. updateInfo.setDescription(parser.nextText());
    372. }
    373. break;
    374. default:
    375. break;
    376. }
    377. //每解析完一个元素, 就将解析标志位下移
    378. eventType = parser.next();
    379. }
    380. is.close();
    381. return updateInfo;
    382. } catch (XmlPullParserException e) {
    383. e.printStackTrace();
    384. } catch (IOException e) {
    385. e.printStackTrace();
    386. }
    387. return null;
    388. }
    389. private void loadMainUI(){
    390. Intent intent = new Intent(this,HomeActivity.class);
    391. startActivity(intent);
    392. finish();
    393. overridePendingTransition(R.anim.main_in, R.anim.splash_out);
    394. }
    395. public class UpdateInfo {
    396. private String version; //当前软件版本号
    397. private String url;     //获取到的软件地址
    398. private String description; //软件描述
    399. public String getVersion() {
    400. return version;
    401. }
    402. public void setVersion(String version) {
    403. this.version = version;
    404. }
    405. public String getUrl() {
    406. return url;
    407. }
    408. public void setUrl(String url) {
    409. this.url = url;
    410. }
    411. public String getDescription() {
    412. return description;
    413. }
    414. public void setDescription(String description) {
    415. this.description = description;
    416. }
    417. @Override
    418. public String toString() {
    419. return "UpdateInfo [version=" + version + ", url=" + url
    420. + ", description=" + description + "]";
    421. }
    422. }
    423. }