需求说明

为了保证自己 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

Android 更新方案实现的更多相关文章

  1. fir.im Weekly - iOS / Android 动态化更新方案盘点

    动态化更新是 App 开发必然面对的问题.在 iOS 环境下,Apple 开发者们像是" 带着手铐脚镣跳舞" ,相比之下 Android 开发者会轻松一点,有很多相关的开源框架帮助 ...

  2. 移动端热更新方案(iOS+Android)

    PPT资源包含iOS+Android 各种方案分析:https://github.com/qiyer/Share/blob/master/%E7%83%AD%E6%9B%B4%E6%96%B0%E5% ...

  3. 基于Quick-cocos2d-x的资源更新方案 二

    写在前面 又是12点半了,对于一个程序员来说,这是一个黄金时间,精力旺盛,我想,是最适合整理和分享一些思路的时候了. 自从上次写了 基于Quick-cocos2d-x的资源更新方案 同样可见quick ...

  4. 基于Quick-cocos2d-x的资源更新方案 一

    图片来自网络 思绪何来 昨天写了一篇关于更新方案的理论 游戏开发:通过路径搜索优先级来进行补丁升级(从端游到手游) 今天继续细化一下 由于新项目采用的是Quick-cocos2d-x,那我就直接给出我 ...

  5. 【腾讯Bugly干货分享】手游热更新方案xLua开源:Unity3D下Lua编程解决方案

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:http://mp.weixin.qq.com/s/2bY7A6ihK9IMcA0bOFyB-Q 导语 xL ...

  6. Unity3D 热更新方案(集合各位专家的汇总)

    http://blog.csdn.net/guofeng526/article/details/52662994 热更新”这个词,在Unity3D的应用下,是有些语义错误的,但是作为大家都熟知的一项技 ...

  7. 腾讯开源手游热更新方案,Unity3D下的Lua编程

    原文:http://www.sohu.com/a/123334175_355140 作者|车雄生 编辑|木环 腾讯最近在开源方面的动作不断:先是微信跨平台基础组件Mars宣布开源,腾讯手游又于近期开源 ...

  8. Unity官方发布热更新方案性能对照

    孙广东  2016.3.11 Unity应用的iOS热更新 作者:丁治宇 Unity TechnologiesChina Agenda •  什么是热更新 •  为何要热更新 •  怎样在iOS 上对 ...

  9. unity热更新方案对比

    Unity应用的iOS热更新 •  什么是热更新 •  为何要热更新 •  怎样在iOS 上对Unity 应用进行热更新 •  支持Unity iOS 热更新的各种Lua 插件的对照 什么是热更新 • ...

随机推荐

  1. java读取Excel表格中的数据

    1.需求 用java代码读取hello.xls表格中的数据 2.hello.xls表格 3.java代码 package com.test; import java.io.File; import j ...

  2. 嵌入式开发之davinci--- 8148/8168/8127 中的图像处理vpss link dei、sclr、swms、Mosaic’s

    vpss 中的link (1)dei dei 主要做数据交错处理,带缩放 dei control data flow: (2)sclr 8168中支持缩放按比例的分子和分母,只支持缩小,貌似不支持放大 ...

  3. Flask采用Virtualenv+Supervisor+Nginx部署应用

    Flask采用Virtualenv+Supervisor+Nginx部署应用 -- 首先是概念解释 WSGI服务器,负责我们的app与服务器的交互,常用的有Gunicorn Web服务器,是个HTTP ...

  4. 【iOS系列】-程序开启后台运行

    [iOS系列]-程序开启后台运行 iOS程序是伪后台的运行,可是有时候我们需要让其在后台也要进行一些操作,我们可以让其伪装成音乐的APP,这样就可以让程序后台进行相关操作了,具体做法如下: 1:在Ap ...

  5. js控制页面显示

    两个菜单切换显示页面内容: js控制代码, /** JS初始化 **/ $(document).ready(function() { $('#email_btn').click(function(){ ...

  6. HDU1269 迷宫城堡 —— 强连通分量

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1269 迷宫城堡 Time Limit: 2000/1000 MS (Java/Others)    M ...

  7. 网站图片增强JS插件2.0(兼容IE&FF)

    网站图片增强JS插件2.0简单介绍:插件可以增强网站互动能力与外链建设,用户在欣赏图片的同时,把看好的图片直接制作成自己喜欢的样式后通过QQ等传播,增强外链建设,通过用户互动创造外链.(支持:放大缩小 ...

  8. mac系统下设置eclipse的补全快捷键方法

    eclispe Word Completion 的默认快捷键是Alt+/eclipse Content Assist 的默认快捷键是Ctrl+Space在使用中发现Word Completion经常导 ...

  9. 并不对劲的Loj6031:「雅礼集训 2017 Day1」字符串

    题目传送门:-> 看到题目的第一反应当然是暴力:对于串s建后缀自动机,每次询问中,求w对应的子串在s的SAM中的right集合.O(qmk)听上去显然过不了. 数据范围有个∑w<=1e5, ...

  10. bzoj 1017: [JSOI2008]魔兽地图DotR【树形dp+背包】

    bzoj上是一个森林啊--? dp还是太弱了 设f[i][j][k]为到点i,合成j个i并且花费k金币能获得的最大力量值,a[i]为数量上限,b[i]为价格,p[i]为装备力量值 其实这个状态设计出来 ...