android 学习随笔十三(网络:多线程下载)
多线程断点续传下载
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 学习随笔十三(网络:多线程下载)的更多相关文章
- android 学习随笔六(网络要求及配置)
		android在4.0之后已经不允许在主线程执行http请求了. 主线程阻塞,应用会停止刷新界面,停止响应用户任何操作,耗时操作不要写在主线程 只有主线程才能修改UI ANR异常:Applicat ... 
- android 学习随笔七(网络:图片及文本传输及线程关系  )
		主线程.子线程.UI的关系 简单的HTTP请求 -------------------------------------------------------- public class MainAc ... 
- android 学习随笔十一(网络:HttpClient框架)
		1.使用HttpClient框架发送get.post请求 google收集apache提供的一个发送Http请求框架 public class Tools { public static String ... 
- android 学习随笔十(网络:get、post提交数据)
		1.get public class Tools { public static String getTextFromStream(InputStream is){ byte[] b = new by ... 
- android 学习随笔九(网络:简单新闻客户端实现)
		1.简单新闻客户端 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xm ... 
- python3.4学习笔记(十三) 网络爬虫实例代码,使用pyspider抓取多牛投资吧里面的文章信息,抓取政府网新闻内容
		python3.4学习笔记(十三) 网络爬虫实例代码,使用pyspider抓取多牛投资吧里面的文章信息PySpider:一个国人编写的强大的网络爬虫系统并带有强大的WebUI,采用Python语言编写 ... 
- Android 学习笔记之使用多线程实现断点下载...
		PS:莫名其妙的迷茫... 学习内容: 1.使用多线程实现文件下载... 多线程下载是加快下载速度的一种方式..通过开启多个线程去执行一个任务..可以使任务的执行速度变快..多线程的任务下载时常都 ... 
- Android 开发工具类 27_多线程下载大文件
		多线程下载大文件时序图 FileDownloader.java package com.wangjialin.internet.service.downloader; import java.io.F ... 
- Android学习随笔--ListView的分页功能
		第一次写博客,可能格式,排版什么的会非常不美观,不过我主要是为了记录自己的Android学习之路,为了以后能有些东西回顾.既然是为了学习,那我肯定会吸收各位大大们的知道经验,有不足的地方请指出. 通过 ... 
随机推荐
- undefined reference to `switch_dev_unregister'
			编译内核时,使用默认的配置进行编译.出现错误:undefined reference to switch_dev_unregister',undefined reference toswitch_se ... 
- 3. 如何封装查询条件与查询结果到map中
			public Map<String, Object> queryOrderStatus(String orderNo) { // 查询到的结果与查询的条件一一对应,封装到map中! Str ... 
- JavaScript:JavaScript事件的处理
			JavaScript事件处理 —————事件的处理流程: —————动态事件绑定: —————常用的事件处理. 1.事件的概念 在页面之中,会针对用户的每一个操作进行记录.在页面中的事件可以简单的理解 ... 
- MagicNotes:自我管理中的破窗效应
			MagicNotes,思绪随风飞扬,偶尔在这里停留. 在<程序员修炼之道——从小工到专家>这本书里,有这么一段描述: 在市区,有些建筑漂亮而整洁,而另一些却是破败不堪的“废弃船只”.为什么 ... 
- file operation note
			从HLE回来,大家拍了2499张照片,分放在N个文件夹下,下面的python将下层目录中文件移动到上层 import os,shutil dst=os.getcwd()+os.sep for path ... 
- Java基础之读文件——使用通道随机读取文件(RandomFileRead)
			import java.nio.file.*; import java.nio.channels.FileChannel; import java.io.IOException; import jav ... 
- 来自“Java中国”优秀的程序员不会觉得累成狗是一种荣耀
			分享下“https://java-china.org/topic/28“,也算是对自己的一种告诫吧. 原文:Sleep deprivation is not a badge of honor 先介绍一 ... 
- Spring 依赖注入,在Main方法中取得Spring控制的实例
			Spring依赖注入机制,在Main方法中通过读取配置文件,获取Spring注入的bean实例.这种应用在实训的时候,老师曾经说过这种方法,而且学Spring入门的时候都会先学会使用如何在普通的jav ... 
- 实验十四_访问CMOS RAM
			编程:以"年/月/日 时:分:秒"的格式,显示当前的日期,时间. 注意:CMOS RAM中存储着系统的配置信息,除了保存时间信息的单元外,不要向其他的单元写入内容,否则将引起一些系 ... 
- SQL server 表之间的关系生成图
			选择数据库名->数据库关系鼠标右键->新建数据库关系图 按着ctrl选择要添加的表 点击添加 
