多线程断点续传下载
1、多线程:快
* 原理:抢占服务器资源
* 单线程下载:线程从第0个字节开始下,下到最后一个字节,在本地硬盘的临时文件中从第0个字节开始写,写到最后一个字节,下载完成时,临时文件也写完了,本地就创建了一个与服务器文件一模一样的文件
* 多线程下载:每条线程下载的开始位置和结束位置都是不一样的,每条线程下载的数据合在一起才是服务器的完整的文件

JAVA版的下载代码

public class Main {
static String path = "http://169.254.244.136:8080/QQPlayer.exe";
static int threadCount = 3;
static int finishedThread = 0;
public static void main(String[] args) {
//发送http请求,拿到目标文件长度
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(8000);
conn.setReadTimeout(8000);
if(conn.getResponseCode() == 200){
//获取长度
int length = conn.getContentLength();
//创建临时文件
File file = new File(getNameFromPath(path));         //rwd可读可写,不经过缓冲区,直接写入硬盘,系统崩溃,数据不会丢失
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
//设置临时文件大小与目标文件一致
                raf.setLength(length);
raf.close();
//计算每个线程下载区间
int size = length / threadCount;
for (int id = 0; id < threadCount; id++) {
//计算每个线程下载的开始位置和结束位置
int startIndex = id * size;
int endIndex = (id + 1) * size - 1;
if(id == threadCount - 1){
endIndex = length - 1;
}
System.out.println("线程" + id + "下载的区间:" + startIndex + " ~ " + endIndex);
new DownLoadThread(id, startIndex, endIndex).start();
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
            e.printStackTrace();
}
}
public static String getNameFromPath(String path){
int index = path.lastIndexOf("/");
return path.substring(index + 1);
}
}
class DownLoadThread extends Thread{
int threadId;
int startIndex;
int endIndex;
public DownLoadThread(int threadId, int startIndex, int endIndex) {
super();
this.threadId = threadId;
this.startIndex = startIndex;
this.endIndex = endIndex;
}
@Override
public void run() {
try {       //创建临时文件,保存下载进度
File fileProgress = new File(threadId + ".txt");
int lastProgress = 0;
if(fileProgress.exists()){
//读取进度临时文件中的内容,实现断点续传
FileInputStream fis = new FileInputStream(fileProgress);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
//得到上一次下载进度
lastProgress = Integer.parseInt(br.readLine());
//改变下载的开始位置,上一次下过的,这次就不请求了,lastProgress保存下载的总长度,不是索引
startIndex += lastProgress;
fis.close();
}
//发送http请求,请求要下载的数据
URL url = new URL(Main.path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(8000);
conn.setReadTimeout(8000);
//设置请求数据的区间
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
//请求部分数据,成功的响应码是206
if(conn.getResponseCode() == 206){
InputStream is = conn.getInputStream();
byte[] b = new byte[1024];
int len = 0;
//当前线程下载的总进度,total不能写为total=0
int total = lastProgress;
File file = new File(Main.getNameFromPath(Main.path));
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
//设置写入的开始位置
                raf.seek(startIndex);
while((len = is.read(b)) != -1){
raf.write(b, 0, len);
total += len;
System.out.println("线程" + threadId + "下载了:" + total);
//写入一个进度临时文件,保存下载进度
RandomAccessFile rafProgress = new RandomAccessFile(fileProgress, "rwd");
//每次下载1024个字节,就,就马上把1024写入进度临时文件
rafProgress.write((total + "").getBytes());
rafProgress.close();
}
raf.close();
System.out.println("线程" + threadId + "下载完毕------------------");
//3条线程全部下载完毕,才去删除进度临时文件,每个线程执行完加1
Main.finishedThread++;
         //synchronized看底部解释
synchronized (Main.path) {
if(Main.finishedThread == Main.threadCount){
for (int i = 0; i < Main.threadCount; i++) {
File f = new File(i + ".txt");
f.delete();
}
Main.finishedThread = 0;
}
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}

RandomAccessFile类是随机读取类,它是一个完全独立的类。
它适用于由大小已知的记录组成的文件,所以我们可以使用seek()将记录从一处转移到另一处,然后读取或者修改记录。

文件中记录的大小不一定都相同,只要能够确定哪些记录有多大以及它们在文件中的位置即可。

RandomAccessFile类可以实现对文件内容的读写操作,但是比较复杂。所以一般操作文件内容往往会使用字节流或字符流方式。

synchronized:当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码

一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。

三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。

四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。

五、以上规则对其它对象锁同样适用.

2、断点续传:
* 下载从上一次下载结束的位置开始
* 原理:每次下载把下载进度保存至一个文本临时文件中,下一次下载时从文本临时文件获取上一次下载的进度,从这个进度开始继续下载

手机端下载代码

package com.itheima.multithreaddownload;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.app.Activity;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
public class MainActivity extends Activity {
String path = "http://169.254.88.88:8080/LiebaoFreeWiFi5.1.exe";
int threadCount = 3;
int finishedThread = 0;
//所有线程下载总进度
int downloadProgress = 0;
private ProgressBar pb;
private TextView tv;
Handler handler = new Handler(){
public void handleMessage(android.os.Message msg) {
tv.setText((long)pb.getProgress() * 100 / pb.getMax() + "%");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
pb = (ProgressBar) findViewById(R.id.pb);
tv = (TextView) findViewById(R.id.tv);
}
public void click(View v){
Thread t = new Thread(){
@Override
public void run() {
//发送http请求,拿到目标文件长度
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(8000);
conn.setReadTimeout(8000);
if(conn.getResponseCode() == 200){
//获取长度
int length = conn.getContentLength();
//创建临时文件
File file = new File(Environment.getExternalStorageDirectory(), getNameFromPath(path));
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
//设置临时文件大小与目标文件一致
                        raf.setLength(length);
raf.close();
//设置进度条的最大值
                        pb.setMax(length);
//计算每个线程下载区间
int size = length / threadCount;
for (int id = 0; id < threadCount; id++) {
//计算每个线程下载的开始位置和结束位置
int startIndex = id * size;
int endIndex = (id + 1) * size - 1;
if(id == threadCount - 1){
endIndex = length - 1;
}
System.out.println("线程" + id + "下载的区间:" + startIndex + " ~ " + endIndex);
new DownLoadThread(id, startIndex, endIndex).start();
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
                    e.printStackTrace();
}
}
};
t.start();
}
public String getNameFromPath(String path){
int index = path.lastIndexOf("/");
return path.substring(index + 1);
}
class DownLoadThread extends Thread{
int threadId;
int startIndex;
int endIndex;
public DownLoadThread(int threadId, int startIndex, int endIndex) {
super();
this.threadId = threadId;
this.startIndex = startIndex;
this.endIndex = endIndex;
}
@Override
public void run() {
try {
File fileProgress = new File(Environment.getExternalStorageDirectory(), threadId + ".txt");
int lastProgress = 0;
if(fileProgress.exists()){
//读取进度临时文件中的内容
FileInputStream fis = new FileInputStream(fileProgress);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
//得到上一次下载进度
lastProgress = Integer.parseInt(br.readLine());
//改变下载的开始位置,上一次下过的,这次就不请求了
startIndex += lastProgress;
fis.close();
//把上一次下载进度加到进度条进度中
downloadProgress += lastProgress;
//发送消息,让文本进度条改变
handler.sendEmptyMessage(1);
}
//发送http请求,请求要下载的数据
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(8000);
conn.setReadTimeout(8000);
//设置请求数据的区间
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
//请求部分数据,成功的响应码是206
if(conn.getResponseCode() == 206){
InputStream is = conn.getInputStream();
byte[] b = new byte[1024];
int len = 0;
//当前线程下载的总进度
int total = lastProgress;
File file = new File(Environment.getExternalStorageDirectory(), getNameFromPath(path));
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
//设置写入的开始位置
                    raf.seek(startIndex);
while((len = is.read(b)) != -1){
raf.write(b, 0, len);
total += len;
System.out.println("线程" + threadId + "下载了:" + total);
//创建一个进度临时文件,保存下载进度
RandomAccessFile rafProgress = new RandomAccessFile(fileProgress, "rwd");
//每次下载1024个字节,就,就马上把1024写入进度临时文件
rafProgress.write((total + "").getBytes());
rafProgress.close();
//每次下载len个长度的字节,马上把len加到下载进度中,让进度条能反应这len个长度的下载进度
downloadProgress += len;
pb.setProgress(downloadProgress);
//发送消息,让文本进度条改变
handler.sendEmptyMessage(1);
}
raf.close();
System.out.println("线程" + threadId + "下载完毕------------------");
//3条线程全部下载完毕,才去删除进度临时文件
finishedThread++;
synchronized (path) {
if(finishedThread == threadCount){
for (int i = 0; i < threadCount; i++) {
File f = new File(Environment.getExternalStorageDirectory(), i + ".txt");
f.delete();
}
finishedThread = 0;
}
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
}

手机断点续传下载

3、进度条

* 计算下载百分比进度时要在long类型下计算

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity"
android:orientation="vertical"
>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下载"
android:onClick="click"
/>
<ProgressBar
style="@android:style/Widget.ProgressBar.Horizontal"
android:id="@+id/pb"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0%"
android:layout_gravity="right"
/>
</LinearLayout>

进度条XML

进度条控件是既可以在主线程又可以在子线程中运行。(刷新UI控件内部已处理)

------------------------------------------------------------------------------

-------------------------------------------------------------------------------

public class MainActivity extends Activity {

String path = "http://169.254.88.88:8080/LiebaoFreeWiFi5.1.exe";
int threadCount = 3;
int finishedThread = 0;
//所有线程下载总进度
int downloadProgress = 0;
private ProgressBar pb;
private TextView tv;

Handler handler = new Handler(){
public void handleMessage(android.os.Message msg) {
tv.setText((long)pb.getProgress() * 100 / pb.getMax() + "%");//不能用int,容易超出int类型范围
}
};

------------------------------------------------------------------------------

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

pb = (ProgressBar) findViewById(R.id.pb);
tv = (TextView) findViewById(R.id.tv);
}

-------------------------------------------------------------------------------------

//读取进度临时文件中的内容
FileInputStream fis = new FileInputStream(fileProgress);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
//得到上一次下载进度
lastProgress = Integer.parseInt(br.readLine());
//改变下载的开始位置,上一次下过的,这次就不请求了
startIndex += lastProgress;
fis.close();

//把上一次下载进度加到进度条进度中,让进度条支持断点续传
downloadProgress += lastProgress;
//发送消息,让文本进度条改变
handler.sendEmptyMessage(1);

---------------------------------------------------------------------------------------

//设置临时文件大小与目标文件一致
raf.setLength(length);
raf.close();

//设置进度条的最大值
pb.setMax(length);

//计算每个线程下载区间
int size = length / threadCount;

------------------------------------------------------------------------------------------------------------

//创建一个进度临时文件,保存下载进度
RandomAccessFile rafProgress = new RandomAccessFile(fileProgress, "rwd");
//每次下载1024个字节,就,就马上把1024写入进度临时文件
rafProgress.write((total + "").getBytes());
rafProgress.close();

//每次下载len个长度的字节,马上把len加到下载进度中,让进度条能反应这len个长度的下载进度
downloadProgress += len;
pb.setProgress(downloadProgress);

//发送消息,让文本进度条改变
handler.sendEmptyMessage(1);

4、XUtils(afinal)

https://github.com/search?utf8=✓&q=XUtils&type=Repositories&ref=searchresults

支持大文件上传,更全面的http请求协议支持(10种谓词),拥有更加灵活的ORM,更多的事件注解支持且不受混淆影响...

package com.itheima.xutils;
import java.io.File;
import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.exception.HttpException;
import com.lidroid.xutils.http.ResponseInfo;
import com.lidroid.xutils.http.callback.RequestCallBack;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
public class MainActivity extends Activity {
private ProgressBar pb;
private TextView tv_progress;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
pb = (ProgressBar) findViewById(R.id.pb);
tv_progress = (TextView) findViewById(R.id.tv_progress);
}
public void click(View v){
String url = "http://169.254.244.136:8080/QQPlayer.exe";
HttpUtils utils = new HttpUtils();
utils.download(url, //目标文件的网址
"sdcard/QQPlayer.exe", //指定存储的路径和文件名
true, //是否支持断点续传
true, //如果响应头中包含文件名,下载完成后自动重命名
new RequestCallBack<File>() {
//下载完成后调用
                    @Override
public void onSuccess(ResponseInfo<File> responseInfo) {
TextView tv_success = (TextView) findViewById(R.id.tv_success);
tv_success.setText(responseInfo.result.getPath());
}
//下载失败调用
                    @Override
public void onFailure(HttpException error, String msg) {
TextView tv_failure = (TextView) findViewById(R.id.tv_failure);
tv_failure.setText(msg);
}
//下载过程中不断调用
                    @Override
public void onLoading(long total, long current,
boolean isUploading) {
pb.setMax((int) total);
pb.setProgress((int) current);
tv_progress.setText(current * 100 / total + "%");
}
});
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity"
android:orientation="vertical"
>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下载"
android:onClick="click"
/>
<TextView
android:id="@+id/tv_success"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/tv_failure"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<ProgressBar
android:id="@+id/pb"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@android:style/Widget.ProgressBar.Horizontal"
/>
<TextView
android:id="@+id/tv_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:text="0%"
/>
</LinearLayout>

android 学习随笔十三(网络:多线程下载)相关推荐

  1. Android学习笔记---java实现多线程下载器,30_多线程下载原理介绍和使用

    2013-04-01 30_多线程下载原理 -------------------- a.文件下载原里:   使用http协议实现多线程下载 b.采用多线程下载,可以抢占服务器cpu的处理时间,实现快 ...

  2. Android 开发工具类 27_多线程下载大文件

    多线程下载大文件时序图 FileDownloader.java 1 package com.wangjialin.internet.service.downloader; 2 3 import jav ...

  3. 浅谈Android和java中的多线程下载

    为什么要使用多线程下载呢? 究其原因就一个字:"快",使用多线程下载的速度远比单线程的下载速度要快,说到下载速度,决定下载速度的因素一般有两个: 一个是客户端实际的网速,另一个则是 ...

  4. android 项目学习随笔十三(ListView实现ITEM点击事件,将已读状态持久化到本地)...

    1.因为给LISTVIEW增加了两个头布局,所以在点击事件ITEM索引会增加2,比如原来第一条数据的索引应该为0,增加两个头布局后,它的索引变为        2,为了使LISTVIEW的ITEM在点 ...

  5. android 学习随笔十二(网络:使用异步HttpClient框架)

    使用异步HttpClient框架发送get.post请求 在https://github.com/  搜索 asyn-http https://github.com/search?utf8=✓& ...

  6. Android学习系列(19)--App离线下载

    宜未雨而绸缪,毋临渴而掘井.----朱用纯<治家格言> 离线下载,在有网络的情况下下载服务器数据,以便无网络时也能阅读,就是离线阅读. 离线下载的功能点如下:       1.下载管理(开 ...

  7. Android学习笔记-Wifi网络操作

    Wifi网卡状态 WIFI_STATE_DISABLED Wifi网卡不可用 WIFI_STATE_DIABLING Wifi网卡正在关闭 WIFI_STATE_ENABLED Wifi网卡可用 WI ...

  8. android学习日记15--WebView(网络视图)

    一.WebView 1.简述 WebView(网络视图)内置WebKit引擎,能加载显示网页,还支持JS,并且能够在Android平台使用AJAX WebView可以在布局中声明,也可以在Activi ...

  9. Android学习羁绊之网络技术

    PC.手机.平板,还是电视,几乎都会具备上网的功能,在可预见的未来,手表.眼镜.汽车等设备也会逐个加入到这个行列,21世纪的确是互联网的时代.Android系统也提供了网络功能,对于开发者,如何使用这 ...

最新文章

  1. oracle 函数参数类型,ORACLE 11g中的表值函数? (参数化视图)
  2. NPOI随笔——图片在单元格等比缩放且居中显示
  3. java之Synchronize
  4. .NET Core Tools 1.0 版本
  5. Git——单人操作及多人协同操作
  6. 微信回应发原图泄露位置信息;华为员工索要离职补偿被起诉;Windows Terminal v0.7 发布 | 极客头条...
  7. aix升级openssh_AIX6.1上源码编译升级openssh6.6p1
  8. ISO14001环境管理体系认证好处
  9. Python 猜数字小游戏 (带闯关关卡)
  10. jmeter接口性能测试实例
  11. 车路协同场景身份认证及 V2X 通信安全保障
  12. linux怎么开启8080端口,Linux中如何开启8080端口供外界访问
  13. 语音搜索的基础-语音识别
  14. 程序员内功:八大排序算法
  15. SCLK时钟信号可以高电平有效也可以低电平有效
  16. Blue Coat:打击移动领域的坏人
  17. 计算机网络安全复习三——密钥分发与认证协议
  18. 【返老还童】大脑衰老可逆转,只需注入年轻脑脊液 From Nature
  19. Datawhale | 高级算法梳理第六期 Task3【XGB算法梳理】3天
  20. 一个10年C++程序员对技术和业务的感悟,献给还在迷茫中的你

热门文章

  1. 最新520表白网站制作HTML前端超好看 (HTML生日快乐代码)
  2. matlab关于bp神经网络,关于matlab的BP神经网络
  3. 大功率四轮电动车控制器代码, 原理图和Pcb
  4. excel 溢出 修复_参加Excel名称修复挑战
  5. 第二章 控制方程离散化之低阶格式(一)
  6. ab进行多个url压力测试
  7. wireshark抓包学习
  8. kubernetes 之 ingress-nginx 概述
  9. 维度模型数据仓库(十四) —— 杂项维度
  10. 网络可视化 | 虹科网络监控软件解决方案(二)-- 网络探针nProbe