文件下载之多线程断点续传技术底层实现

 通过HttpURLConnection连接

 断点续传核心步骤:      

  1.UI设计

  2.获取服务器文件的大小,通过连接的一个方法getContentLength()来得到。

  3.在客户端创建一个和将要下载的文件的同样大小的同名文件。

  4.计算每个线程的起始位置和结束位置

  5.开启多个线程,采用RandomAccessFile类,根据计算得到的起始位置和结束位置来随机的读取服务

   器资源的输出流,并且实时的采用RandomAccessFile类保存线程读取到的字节位置。

  6.判断线程结束和文件上传成功。

  断点续传具体实现:  

  1.UI设计

    下载主界面设计

   wKioL1WuU0_xbFmEAADX87DPYAY439.jpg

     进度条单独存放在一个布局文件中,注意进度条要采用以下的样式的进度条。

     wKiom1WuUirQJRgdAAAW5ywmjtw245.jpg    

  2.代码逻辑

     多线程断点续传的技术可以说是目前学Android课程以来最难、代码最长的一个案例,那么如何

   有章有序的将代码写出来呢?

     思路整理

    1)网络访问权限和SD卡访问权限的添加

     2)初始化动作及下载点击事件的书写wKiom1WuXfvTjkZPAASURy1t-gc755.jpg 

     3)随机读取文件线程类的定义

      wKiom1WuZonBOlzlAAQ2INrID8c765.jpg  

     具体代码:   

	// 注册点击事件
	public void click(View v) {
		path = et_path.getText().toString().trim();
		String threadcount_str = et_threadcount.getText().toString().trim();
		final int threadcount = Integer.parseInt(threadcount_str);

		
		if (TextUtils.isEmpty(path) || threadcount < 0) {
			Toast.makeText(getApplicationContext(), "输入错误", 0).show();
			return;
		}

		list_pbBars = new ArrayList<ProgressBar>();
		// 先清空进度条布局
		ll_layout.removeAllViews();
		// 添加进度条
		for (int i = 0; i < threadcount; i++) {
			//得到进度条的View对象
			View item_pb_View = View.inflate(getApplicationContext(),R.layout.item_progress, null);
			//通过View再得到进度条
			ProgressBar pb_bar = (ProgressBar) item_pb_View.findViewById(R.id.item_pb);
			list_pbBars.add(pb_bar);
			ll_layout.addView(item_pb_View);
		}
		try {
			//将服务器文件路径用File包装一下,以便获得它的文件名。
			sourceFile = new File(path);
			
			//访问URL得到文件的大小 ,通过Content-Length得到。
			new Thread(){
				public void run()
				{
					System.out.println("xxx"+Thread.currentThread().getName());
					try {
						//创建HttpURLConnection
						URL url = new URL(path);
						
						HttpURLConnection conn = (HttpURLConnection) url.openConnection();
						
						conn.setRequestMethod("GET");
						//获取状态码
						int responseCode = conn.getResponseCode();
						if(responseCode == 200)
						{
							//获取资源文件的长度
							sourceFileLength = conn.getContentLength();
							
							
							sourceFileName = sourceFile.getName();
							clientFile = new RandomAccessFile(Environment.getExternalStorageDirectory() + "/"
											+ sourceFileName, "rw");
							clientFile.setLength(sourceFileLength);
							
							
							//正在运行的线程
							runningThreadCount = threadcount;
							// 第3步: 计算每个线程读取文件的起始和结束位置
							int blockSize = sourceFileLength / threadcount;
							for (int i = 0; i < threadcount; i++) {
								//得到每个线程读取的起始和结束位置
								int start_index = i * blockSize;
								int end_index = (i + 1) * blockSize - 1;
								int last_index = -1;
								if(i == threadcount - 1)
								{
									end_index = sourceFileLength -1;
								}
								//------如果采用了断点续传,线程的起始位置就是上次记录位置。
								
								File recordIndexFile = new File(Environment.getExternalStorageDirectory().getPath() + "/" +i + ".txt");
								
								//如果有断点文件,就得到上次记录的读取位置。
								if(recordIndexFile.exists() && recordIndexFile.length() > 0)
								{
									BufferedReader bufr = new BufferedReader(new FileReader(recordIndexFile));
									last_index = Integer.parseInt(bufr.readLine());
//									last_index++;
									bufr.close();
								}
								else  //否则上次读取位置就是开始位置。
								{
									last_index = start_index;
								}
								//设置进度条的总长度
								list_pbBars.get(i).setMax((int)(end_index - start_index));
								
								System.out.println("线程"+i +"::"+ last_index + "----" + end_index);
								// 第4步:启动线程
								new DownloadThread(i, start_index, end_index,last_index).start();
							}
							
							
							
						}
						else
						{
							showToast("请求不到资源 ");
							return;
						}
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}.start();
			
			
			
			
				
			// 第5步:判断线程结束

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	// 定义下载的线程类
	class DownloadThread extends Thread {
		private int threadID;
		private int  start_index, end_index,last_index;
		private int total = 0;		//记录已经读写的总的字节数
		public DownloadThread(int threadID, int start_index, int end_index,int last_index) {
			this.threadID = threadID;
			this.start_index = start_index;
			this.end_index = end_index;
			this.last_index = last_index;
			this.setName("线程" + threadID);
		}

		public void run() {
			try {
				//创建HttpURLConnection
				URL url = new URL(path);
				
				HttpURLConnection conn = (HttpURLConnection) url.openConnection();
				
				conn.setRequestMethod("GET");
//				conn.setReadTimeout(5);
				
				//设置一个头信息Range,表示要读取的流的字节范围。
				conn.setRequestProperty("Range", "bytes=" + last_index + "-" + end_index);
				
				
				
				//获取状态码
				int responseCode = conn.getResponseCode();
				//注意多线程下载状态得用206来判断
				if(responseCode == 206)
				{
					//得到服务器的输出流
					InputStream serverIn = conn.getInputStream();
					//得到客户端的文件输出流
					RandomAccessFile myClientFile = new RandomAccessFile(
							Environment.getExternalStorageDirectory() + "/"
									+ sourceFileName, "rwd");
					myClientFile.seek(last_index);
					//读取服务器文件写入到客户端文件中
					//断点续传文件
					
					int len = 0;
					byte[] buf = new byte[1024*1024];
					while((len = serverIn.read(buf))!= -1)
					{
						myClientFile.write(buf,0,len);
						total += len;
						//实时存储当前读取到的文件位置。
						 int current_index = last_index + total;
						//因为此处实时的存储读取文件的位置,将缓冲区buf稍微弄大点。
						
						// 改
						/***注意读取文件位置的流一定要用RandomAccessFile,因为它可以直接同步到缓存(磁盘上)。***/
						RandomAccessFile raffAccessFile = new RandomAccessFile(Environment.getExternalStorageDirectory().getPath()+"/"+threadID+".txt", "rwd");
						raffAccessFile.write(String.valueOf(current_index).getBytes());
						raffAccessFile.close();
						//设置进度条的当前进度
						list_pbBars.get(threadID).setProgress((int)(total + last_index - start_index));
					}
					myClientFile.close();
					serverIn.close();
					
					
					//操作共享资源,注意加锁。
					synchronized (DownloadThread.class) {
						runningThreadCount--;
						if(runningThreadCount == 0)
						{
							Toast.makeText(getApplicationContext(), "文件下载完毕", 0).show();
						}
					}
					
				}
				else 
				{
					showToast("服务器忙");
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
			
		}
	}