最近想写一个关于在线更新的文章。在线更新基本逻辑就是将当前的app的版本号发送给服务器校验,若服务器上有新版本就会返回一个url给客户端。客户端就要去这个url下载最新的apk包。这个功能的核心以及难点就是下载文件,然而!!然而!!就在今天在极客学院的网站上看到一个视频,讲的是关于多线程断点下载的。这可比我的单线程,不支持暂停的下载要高大上太多了。于是我就学习了一下,将它总结一下:视频地址极客学院-多线程断点下载。app运行效果如下:
基本逻辑:
1.通过HttpURLConnection获取url中相关资源
2.通过HttpURLConnection的setRequestProperty方法,截取资源的bytes,实现多线程下载
3.通过RandomAccessFile的seek方法可以设置从哪个位置开始写入,实现断点下载(恢复下载)
下面贴上代码,具体的讲解请看注释:
MainActivity.java
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.Toast;
import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private int mDownloadedLength = 0;//已经下载的大小
private int mTotalLength = 0;//已经下载的大小
private boolean isDownloading = false;//标记是否下载中,用于断电续传
private File mDownloadFile;
private URL mUrl;
private List> mThreadList = new ArrayList<>();
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 0x1023) {
int progressRate = (int) msg.obj;
mDownloadPB.setProgress(progressRate);
if (progressRate >= mTotalLength) {
Toast.makeText(MainActivity.this, "下载完成", Toast.LENGTH_SHORT).show();
}
}
}
};
private EditText mUrlET;
private Button mDownloadBtn;
private ProgressBar mDownloadPB;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mUrlET = (EditText) findViewById(R.id.et_url);
mDownloadBtn = (Button) findViewById(R.id.btn_download);
mDownloadPB = (ProgressBar) findViewById(R.id.pb_download);
mDownloadBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isDownloading) {
isDownloading = false;
((Button) v).setText("Download");
return;
} else {
isDownloading = true;
((Button) v).setText("Pause");
}
if (mThreadList.size() == 0) {
//开启新下载
String url = mUrlET.getText().toString();
executeDownload(url);
} else {
//恢复下载
for (int i = 0; i < mThreadList.size(); i++) {
HashMap map = mThreadList.get(i);
int begin = map.get("begin");
int end = map.get("end");
int finished = map.get("finished");
//修改下载文件的起始点
Thread t = new Thread(new DownloadRunnable(i, begin + finished,
end, mDownloadFile, mUrl));
t.start();
}
}
}
});
}
/**
* 执行下载
* @param url 需要下载的资源的url
*/
private void executeDownload(final String url) {
if (!TextUtils.isEmpty(url)) {
//
new Thread(new Runnable() {
@Override
public void run() {
try {
mUrl = new URL(url);
HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
//设置为get请求
connection.setRequestMethod("GET");
//设置请求超时的时间
connection.setConnectTimeout(5 * 1000);
//模拟浏览器请求
connection.setRequestProperty("User-Agent",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36");
int length = connection.getContentLength();
if (length < 0) {
System.out.println("url错误");
return;
}
mTotalLength = length;
mDownloadPB.setMax(length);
mDownloadPB.setProgress(0);
//创建一个文件用于保存下载的资源
int urlIndex = url.lastIndexOf(".");
//获取文件后缀
String fileSuffix = url.substring(urlIndex);
mDownloadFile = new File(Environment.getExternalStorageDirectory(),
String.valueOf(System.currentTimeMillis()) + fileSuffix);
RandomAccessFile randomAccessFile = new RandomAccessFile(mDownloadFile, "rw");
randomAccessFile.setLength(length);
//线程数量,用于开启多线程下载
int threadCount = 3;
//每个线程下载的文件的大小
int eachFileSize = length / threadCount;
for (int i = 0; i < threadCount; i++) {
int begin = i * eachFileSize;
int end = (i + 1) * eachFileSize;
if (i == threadCount - 1) {
end = length;
}
//记录该线程的下载信息
HashMap map = new HashMap();
map.put("begin", begin);
map.put("end", end);
map.put("finished", 0);
mThreadList.add(map);
//创建线程来下载文件
Thread thread = new Thread(new DownloadRunnable(i, begin, end, mDownloadFile, mUrl));
thread.start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
/**
* 执行下载Runnable
*/
class DownloadRunnable implements Runnable {
private int begin;
private int end;
private File file;
private URL url;
private int id;
public DownloadRunnable(int id, int begin, int end, File file, URL url) {
this.id = id;
this.begin = begin;
this.end = end;
this.file = file;
this.url = url;
}
@Override
public void run() {
try {
if (begin > end) {
//下载结束
return;
}
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
//设置为get请求
connection.setRequestMethod("GET");
//设置请求超时的时间
connection.setConnectTimeout(5 * 1000);
//模拟浏览器请求
connection.setRequestProperty("User-Agent",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36");
//多线程断点下载的重点
connection.setRequestProperty("Range", "bytes=" + begin + "-" + end);
InputStream is = connection.getInputStream();
byte[] buf = new byte[1024 * 1024];
//randomAccessFile对象不能在多个线程中共享
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
randomAccessFile.seek(begin);
int len = 0;
HashMap map = mThreadList.get(id);
while ((len = is.read(buf)) != -1 && isDownloading) {
randomAccessFile.write(buf, 0, len);
updateProgress(len);
//修改该线程已经下载的文件大小
map.put("finished", map.get("finished") + len);
System.out.println("mDownloadedLength:" + mDownloadedLength);
}
is.close();
randomAccessFile.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 更新下载进度
* @param addLength 增加的进度
*/
synchronized private void updateProgress(int addLength) {
mDownloadedLength += addLength;
mHandler.obtainMessage(0x1023, mDownloadedLength).sendToTarget();
}
}
xml文件
到这里就结束了吗?no!虽然例子里面下载的资源不是app文件,但是在线更新的话需要下载的肯定是一个apk格式的文件。我们不能下载下来后不做任何操作,让用户自己去file explorer中去找自己刚刚下载的apk文件,并且手动点击安装。这样的体验太差了,所以我们还需要一步,安装apk文件,代码如下:
File apkfile = new File(apkFilePath);
if (!apkfile.exists()) {
return;
}
Intent i = new Intent(Intent.ACTION_VIEW);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.setDataAndType(Uri.parse("file://" + apkfile.toString()), "application/vnd.android.package-archive");
mContext.startActivity(i);
apkFile就是刚刚下载的文件了