需求说明

为了保证自己 APP 的新版本使用率,现在有很多已有的“软件更新”框架供各位使用,本文的主要内容是如何自己动手来实现软件的后台下载,更新。

下面详细说明下软件更新的逻辑,流程图如下:

每步详细代码

1. 检测当前的网络状态

    public static boolean isWifiConnected(Context context) {
if (context != null) {
// 获取手机所有连接管理对象
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
// 获取 NetworkInfo 对象
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
// 类型是否为WIFI
if (networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI)
return networkInfo.isAvailable();
}
return false;
}

上面的代码,传入一个 Context 对象,就可以返回当前的 Wifi 是否连接。

注意:需要添加获取网络状态权限

2. 检测和服务器版本是否一致

我们检查完网络状态后,就可以执行这一步骤。这一步骤,我们需要后端小伙伴的配合。

后端服务,需要提供一个 API,该接口的返回值类型包括如下主要内容:

{
"version_code":1,
"download_url":"你软件的下载地址"
}

其中,version_code 是服务器上软件的最新版本号,download_url 是 APK 的下载地址。本教程默认使用 HTTP 协议来下载 APK 文件。

我们在 Wifi 网络下,请求以上 API,将获得的版本号与当前的版本号进行比较,如果版本号不一致(或者说小于 API 返回的版本号),开始进入 APK 下载流程。

3. 多线程下载 APK

下面我们来完成多线程下载的代码,该代码要实现以下功能:

  1. 多线程下载,目的是不造成用户在主线程的操作卡顿。
  2. 任务完成后,启动安装程序开始安装 APK。
  3. 任务完成后的标志,该功能主要解决用户下载一半就(强制)退出程序的问题,防止文件坏损,保证安装的文件都是完整的文件。

下面是多线程下载的程序:

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL; /**
* 用来下载文件的,多线程,主要用来下载 APK 文件,用来更新
* <p>
* https://github.com/Jere3y/utils/blob/master/xz.java
* Created by Tianyu Xin (tianyurui@gmail.com) on 2017/8/2.
*/ public abstract class Xz {
private static final String TAG = "Xz"; private static final int BUFFER_SIZE = 1024 * 500;
private static final int THREAD_COUNT = 2;
private static volatile int completeCount = 0; public void start(String url) {
start(url, "");
} public void start(String urlStr, String path) {
Log.i(TAG, "开始下载任务,地址是:" + urlStr);
Log.i(TAG, "保存到:" + path);
HttpURLConnection connection;
URL url;
try {
url = new URL(urlStr);
Log.i(TAG, "开始建立连接...");
connection = (HttpURLConnection) url.openConnection();
Log.i(TAG, "连接建立成功....");
int code = connection.getResponseCode();
Log.i(TAG, "code ---->" + code);
if (code == 200) {
Log.i(TAG, "文件获取成功,开始下载...");
final int length = connection.getContentLength();
connection.disconnect();
Log.i(TAG, "文件大小获取成功,大小为:" + length);
String[] split = urlStr.split("/");
String defFileName = split[split.length - 1];
Log.i(TAG, "开始创建,文件名为:" + defFileName);
File file = new File(path);
if (!file.exists()) {
file.mkdir();
}
String p = path + defFileName;
RandomAccessFile raf = new RandomAccessFile(p, "rw");
Log.i(TAG, "文件创建成功");
raf.setLength(length);
raf.close();
int partSize = length / THREAD_COUNT;
for (int i = 0; i < THREAD_COUNT; i++) {
int startPosition, endPosition;
startPosition = i * partSize;
endPosition = (i + 1) * partSize - 1;
if (i == THREAD_COUNT - 1) {
endPosition = length - 1;
}
new DownloadThread(url, p, startPosition, endPosition, i).start();
}
}
} catch (MalformedURLException e) {
Log.i(TAG, "地址解析失败,请检查后重新尝试!");
e.printStackTrace();
} catch (Exception e) {
Log.i(TAG, "地址链接失败,请检查地址是否正确!");
e.printStackTrace();
} } /**
* 同步方法,增加完成的线程数量 +1
*/
private synchronized void incCompletedCount() {
completeCount += 1;
} /**
* 这个必须实现
*
* @param path 这个是下载完成后回调的方法,参数是下载完成后改名的文件路径
*/
public abstract void onDownloadCompleted(String path); /**
* 是否能成功改名
*
* @param path 改名前的文件的路径
* @return 改名是否成功
*/
private boolean canRenameToCompleted(String path) {
File file = new File(path);
String replace = markingTaskCompleted(path);
if (file.exists()) {
return file.renameTo(new File(replace));
}
return false;
} /**
* 把下载完成后的文件改名,能区分是否下载完成
*
* @param path 改名前的路径
* @return 改名后的路径
*/
public static String markingTaskCompleted(String path) {
return path.replace(".apk", "-complete.apk");
} class DownloadThread extends Thread {
URL url;
String defFileName;
int startPosition;
int endPosition;
int id; DownloadThread(URL url, String defFileName, int startPosition, int endPosition, int id) {
super();
this.url = url;
this.defFileName = defFileName;
this.startPosition = startPosition;
this.endPosition = endPosition;
this.id = id;
} @Override
public void run() {
Long startTime = System.currentTimeMillis();
Log.i(TAG, "线程开始id:" + id);
HttpURLConnection connection;
InputStream inputStream = null;
try { connection = (HttpURLConnection) url.openConnection();
connection.setRequestProperty("Range", "bytes=" + startPosition + "-" + endPosition); int code = connection.getResponseCode(); if (206 == code) {
inputStream = connection.getInputStream();
RandomAccessFile raf = new RandomAccessFile(defFileName, "rwd");
raf.seek(startPosition);
Log.i(TAG, "线程" + id + "创建成功,开始下载...");
int hasRead = -1;
byte[] buffer = new byte[BUFFER_SIZE];
while ((hasRead = inputStream.read(buffer)) != -1) {
raf.write(buffer, 0, hasRead);
}
raf.close();
}
Long endTime = System.currentTimeMillis();
Log.i(TAG, "线程" + id + "下载完毕,耗时:" + (endTime - startTime) / 1000 + "秒");
incCompletedCount();
// 完成后并且成功改名,回调方法
if (completeCount == THREAD_COUNT && canRenameToCompleted(defFileName)) {
onDownloadCompleted(markingTaskCompleted(defFileName));
}
} catch (MalformedURLException e) {
Log.i(TAG, "线程" + id + "地址解析失败,请检查后重新尝试!");
e.printStackTrace();
} catch (IOException e) {
Log.i(TAG, "线程" + id + "地址链接失败,请检查地址是否正确!");
e.printStackTrace();
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) { e.printStackTrace();
}
}
}
}
}

上面的代码使用多线程完成了 HTTP 协议的下载,并且在成功下载后,会将文件改名,用来标志文件的成功下载。

这样我们只需要判断改名后的文件是否存在,就可以知道文件是否完整的下载完成。

如果完整的下载完成,那么执行安装。

如果没有完整的下载完成(就是,改名后的文件不存在),那么重新执行下载。

上面的代码在下载完成后会回调方法 onDownloadCompleted(String defFile)

其中,参数 defFile 是下载完成后保存的文件路径,我们可以直接使用这个参数来调用 Android 的安装代码。

4. 执行安装

关于执行安装的代码,可以参照上一篇文章,调用系统的包管理安装 APK:

http://www.cnblogs.com/newjeremy/p/7294519.html

最新文章

  1. Linux中SysRq的使用(魔术键)
  2. Python基于websocket实时通信的实现—GoEasy
  3. 对Delphi控件作用的新理解(控件本身的源代码就是一个很强的工业级源码)
  4. 尚未在 Web 服务器上注册 ASP.NET 4.0” 的解决办法
  5. flash player over linux
  6. CentOS 6.x 下Postfix和dovecot邮件服务安装和基本配置
  7. NTOPNG修改密码
  8. ASP.NET页面事件顺序
  9. WebSocket部署服务器外网无法连接解决方案
  10. canvas动画气球
  11. 「LibreOJ Round #6」花火
  12. Python生成PASCAL VOC格式的xml标注文件
  13. bootstrapTable 参数说明
  14. 初探nginx负载均衡配置
  15. 应用内直接跳转到Appstore
  16. 分析jvm线程堆栈
  17. JavaScript-this理解
  18. JVM之JIT
  19. robotframework-ride多次运行,有时候不显示日志信息
  20. repcached配置与简单測试

热门文章

  1. [学习笔记]overthewire bandit 通关秘籍
  2. H5的localStorage简单存储删除
  3. mysql13---索引使用注意
  4. 【Android进度条】三种方式实现自定义圆形进度条ProgressBar
  5. SpringMVC数据绑定三(JSON 、XML))
  6. android:layout_gravity 和 android:gravity 的区别(转载)
  7. [App Store Connect帮助]八、维护您的 App(2)将 App 从 App Store 中移除
  8. springboot(六)自动配置原理和@Conditional
  9. Luogu P1396 营救【最小生成树/二分答案/最短路】 By celur925
  10. centos 7添加快捷键