Android 多线程下载与断点续传

可以突破服务器段对单个线程的速度限制。

不可能超过带宽。

服务端支持con.setRequestProPerty("Range","bytes="+startIndex+"-"+endIndex);


客户端RandomAccessFile 支持随意读写文件的任意位置

模式为rws 表示每次写入相应文件,都直接更新文件,而不是像一般的File,每次写入数据之前,还一定先把数据放在缓存里,然后才把缓存更新到硬盘上。

seek(long pos) ;  这个对象中有一个文件指针,可以用来控制读写文件的位置。

读的时候,若是服务器成功返回数据,相应码是206(正好是我宿舍号),不是200

public class RangeTest {

	/**
	 * @param args
	 */
	private static String path="http://127.0.0.1:8080/test.avi";
	private static int threadcount=3;
	public static void main(String[] args) throws Exception {
		URL url=new URL(path);
		HttpURLConnection connection=(HttpURLConnection) url.openConnection();
		connection.setRequestMethod("GET");
		connection.setConnectTimeout(10000);
		int code=connection.getResponseCode();
		if(code==200)
		{
			int leng=connection.getContentLength();
			RandomAccessFile file=new RandomAccessFile("test.avi", "rw");
			file.setLength(leng);
			for(int i=0;i<threadcount;++i)
			{
				int start=leng/threadcount*i;
				int end=leng/threadcount*(i+1)-1;
				if(i==threadcount-1){
					end=leng-1;
				}
				new DownLoadPart(start, end, i+1).start();
				
			}
		}
		
	}
	private static class DownLoadPart extends Thread{
		private int start;
		private int end;
		private int threadid;
		public DownLoadPart(int start, int end, int threadid) {
			super();
			this.start = start;
			this.end = end;
			this.threadid = threadid;
		}
		@Override
		public void run() {
			URL url;
			try {
				url = new URL(path);
				HttpURLConnection conn=(HttpURLConnection) url.openConnection();
				conn.setRequestMethod("GET");
				conn.setReadTimeout(10000);
				//设置请求头,表示开始下载的起始位置和终止位置。
				conn.setRequestProperty("Range", "bytes="+start+"-"+end);
				if(conn.getResponseCode()==206){
					System.out.println("线程"+threadid+"开始下载");
					InputStream is=conn.getInputStream();
					byte[]buf=new byte[1024];
					RandomAccessFile file=new RandomAccessFile("test.avi", "rw");
					file.seek(start);
					int len;
					while((len=is.read(buf))>0){
						file.write(buf,0,len);
					}
					is.close();
					System.out.println("线程"+threadid+"下载完毕");
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
			
		}

	}

}
断点续传只做了几处修改:

1.新建几个日志文件,每个日志文件对应一个线程。

2.确定下载区间范围前,先考虑是否存在相应的日志文件,若存在,则读取文件并转为int(long更好点),作为起始区间。

3.下载文件时,每进行一次读写操作,则修改本线程的日志文件,记录当前读到的位置。

完善的:

public class RangeTest {

	/**
	 * @param args
	 */
	private static String path="http://127.0.0.1:8080/test.avi";
	private static int threadcount=3;
	public static void main(String[] args) throws Exception {
		URL url=new URL(path);
		HttpURLConnection connection=(HttpURLConnection) url.openConnection();
		connection.setRequestMethod("GET");
		connection.setConnectTimeout(10000);
		int code=connection.getResponseCode();
		if(code==200)
		{
			int leng=connection.getContentLength();
			RandomAccessFile file=new RandomAccessFile("test.avi", "rw");
			file.setLength(leng);
			for(int i=0;i<threadcount;++i)
			{
				int start=leng/threadcount*i;
				int end=leng/threadcount*(i+1)-1;
				File log=new File("test"+(i+1)+".log");
				if(log!=null&&log.length()>0){
					FileInputStream fis=new FileInputStream(log);
					BufferedReader br=new BufferedReader(new InputStreamReader(fis));
					String st=br.readLine();
					start=Integer.parseInt(st);
				}
				
				if(i==threadcount-1){
					end=leng-1;
				}
				new DownLoadPart(start, end, i+1).start();				
			}
		}
		
	}
	private static class DownLoadPart extends Thread{
		private int start;
		private int end;
		private int threadid;
		public DownLoadPart(int start, int end, int threadid) {
			super();
			this.start = start;
			this.end = end;
			this.threadid = threadid;
		}
		@Override
		public void run() {
			URL url;
			try {
				url = new URL(path);
				HttpURLConnection conn=(HttpURLConnection) url.openConnection();
				conn.setRequestMethod("GET");
				conn.setReadTimeout(10000);
				//设置请求头,表示开始下载的起始位置和终止位置。
				conn.setRequestProperty("Range", "bytes="+start+"-"+end);
				if(conn.getResponseCode()==206){
					System.out.println("线程"+threadid+"开始下载"+start+"-"+end);
					InputStream is=conn.getInputStream();
					byte[]buf=new byte[1024];
					RandomAccessFile file=new RandomAccessFile("test.avi", "rw");

					file.seek(start);
					int len;
					int coun=0;
					while((len=is.read(buf))>0){
						file.write(buf,0,len);
						coun+=len;
						int position=coun+start;
						RandomAccessFile temp=new RandomAccessFile("test"+threadid+".log", "rwd");
						temp.write(String.valueOf(position).getBytes());
					}
					is.close();
					System.out.println("线程"+threadid+"下载完毕");
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
			
		}
	}
}

最后将其应用到Android上:

注意:与进度相关的控件都可以在子线程,直接更新,因为他们已经封装好了,ProgressBar,SeekBar,ProgressDialog(进度条对话框)

ProgressBar  

setMax设置最大值

setProgress设置当前进度

ViewGroup(LinearLayout,RelativeLayout ,FrameLayout)

View.inflate(context,R.layout.**,viewgroup);

如果最后一个参数传入了ViewGroup,则从xml转化来的view对象就会作为这个viewgroup的子view,添加到这个viewgroup中,

ViewGroup.removeAllViews();删除所有子控件

getChildAt(index); 可以根据索引找到子控件。

Toast与View.inflate等都会用到,注意:getApplicationContext() 获取应用的上下文,Application这个类生命周期最长。与ServletContext类似。

若是使用MainActivity.this  有时候不是很安全,比如说Toast.makeText(MainActivity.this, "XXX",Toast.LONG).show(); 展示的过程中,你点击返回,垃圾回收机制应当回收MainActivity,但MainActivity还在被Toast引用,所以无法回收,时间久了便会造成内存泄漏。

只要 程序还在跑getApplicationContext()一定是存在的,因此这个资源大部分情况下都会被回收,所以getApplication()在绝大部分情况下,都是安全的,只有当其在操作对话框时,不太安全,需要使用MainActivity。

核心代码:

public class MainActivity extends Activity implements OnClickListener{
	private EditText et_url;
	private EditText et_count;
	private Button bt_download;
	private LinearLayout ll_progressbar;
	private String path="http://10.0.2.2:8080/test.avi";
	private int threadcount;
	private String savePath;
	private int partSize;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		et_url=(EditText) findViewById(R.id.et_url);
		et_count=(EditText) findViewById(R.id.et_count);
		bt_download=(Button) findViewById(R.id.bt_download);
		ll_progressbar=(LinearLayout) findViewById(R.id.ll_progresses);
		bt_download.setOnClickListener(this);		
		savePath = this.getCacheDir().getAbsolutePath()+"/test.avi";
	}
	public void onClick(View v) {
		ll_progressbar.removeAllViews();
		String num=et_count.getText().toString().trim();
		threadcount=Integer.parseInt(num);
		for(int i=0;i<threadcount;++i){
			View.inflate(MainActivity.this, R.layout.progress, ll_progressbar);
		}
		new Thread(){
			@Override
			public void run() {
				try{
					URL url=new URL(path);
					HttpURLConnection connection=(HttpURLConnection) url.openConnection();
					connection.setRequestMethod("GET");
					connection.setConnectTimeout(10000);
					int code=connection.getResponseCode();
					if(code==200)
					{
						int leng=connection.getContentLength();
						RandomAccessFile file=new RandomAccessFile(savePath, "rw");
						file.setLength(leng);
						partSize=leng/threadcount;
						for(int i=0;i<threadcount;++i)
						{
							int start=leng/threadcount*i;
							int end=leng/threadcount*(i+1)-1;
							if(i==threadcount-1){
								end=leng-1;
							}
							new DownLoadPart(start, end, i+1).start();				
						}
					}
				}catch(Exception e){
					e.printStackTrace();
				}
			}
		}.start();
	}
	
	private  class DownLoadPart extends Thread{
		private int start;
		private int end;
		private int threadid;
		private ProgressBar progressbar;
		public DownLoadPart(int start, int end, int threadid) {
			super();
			this.start = start;
			this.end = end;
			this.threadid = threadid;
			//参数不是threadid,往往有at或Index等字样表明下标从0开始
			progressbar=(ProgressBar) ll_progressbar.getChildAt(threadid-1);
			//这里的start表示这一个线程的最开始下载的位置,而不是将要下载的位置。
			progressbar.setMax(end-start);
		}
		@Override
		public void run() {
			try {
				File log=new File(MainActivity.this.getCacheDir().getAbsolutePath()+"/test"+threadid+".log");
				if(log!=null&&log.length()>0){
					FileInputStream fis=new FileInputStream(log);
					BufferedReader br=new BufferedReader(new InputStreamReader(fis));
					String st=br.readLine();
					//这里的start表示将要下载的位置。
					start=Integer.parseInt(st);
				}			
				URL url;
				url = new URL(path);
				HttpURLConnection conn=(HttpURLConnection) url.openConnection();
				conn.setRequestMethod("GET");
				conn.setReadTimeout(10000);
				//设置请求头,表示开始下载的起始位置和终止位置。
				conn.setRequestProperty("Range", "bytes="+start+"-"+end);
				if(conn.getResponseCode()==206){
					System.out.println("线程"+threadid+"开始下载"+start+"-"+end);
					InputStream is=conn.getInputStream();
					byte[]buf=new byte[1024];
					RandomAccessFile file=new RandomAccessFile(savePath, "rw");

					file.seek(start);
					int len;
					int coun=0;
					while((len=is.read(buf))>0){
						file.write(buf,0,len);
						coun+=len;
						int position=coun+start;
						RandomAccessFile temp=new RandomAccessFile(MainActivity.this.getCacheDir().getAbsolutePath()+"/test"+threadid+".log", "rwd");
						temp.write(String.valueOf(position).getBytes());
						//这里不可以写coun,coun表示这一次运行开始到现在的下载量
						//这里表示当前下载的位置减最开始下载的位置
						progressbar.setProgress(position-partSize*(threadid-1));
					}
					log.delete();
					is.close();
					System.out.println("线程"+threadid+"下载完毕");
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}


使用开源项目:

这是最常用的方法

导入 xUtils-2.6.14.jar 

public class MainActivity extends Activity implements OnClickListener{
	private EditText et_url;
	private ProgressBar bar;
	private Button bt_download;
	private String path="http://10.0.2.2:8080/test.avi";
	private String savePath;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		et_url=(EditText) findViewById(R.id.et_url);
		bt_download=(Button) findViewById(R.id.bt_download);
		bar=(ProgressBar) findViewById(R.id.pb_progress);
		bt_download.setOnClickListener(this);		
		savePath = this.getCacheDir().getAbsolutePath()+"/test.avi";
	}
	public void onClick(View v) {
		HttpUtils utils =new HttpUtils();
		utils.download(path, savePath, true, new RequestCallBack<File>() {
			@Override
			public void onLoading(long total, long current, boolean isUploading) {
				bar.setMax((int) total);
				bar.setProgress((int) current);
			}
			//表示下载完成
			@Override
			public void onSuccess(ResponseInfo<File> arg0) {
			}
			
			@Override
			public void onFailure(HttpException arg0, String arg1) {
			}
		});
	}
}
download方法 第一个参数表示url路径,第二个参数表示保存路径,第三个参数表示是否支持断点续传,第四个参数表示是否下载完成重命名







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值