HTTP多线程断点下载
原理:获取目标文件的大小,在本地创建一个相同大小的文件,并计算每个线程需要下载的起始位置及大小,然后分配至每个线程独立下载,全部下载完毕则自动合并.
实现步骤
-
查看并计算目标文件的大小
URL url = new URL(mPath); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); int code = connection.getResponseCode(); if (code == 200) { int length = connection.getContentLength(); System.out.println("文件总长度为:" + length);
-
设置目标文件在本地的映射
RandomAccessFile raf = new RandomAccessFile( Environment.getExternalStorageDirectory().getAbsolutePath() +"/"+getDownlaodName(mPath), "rw"); raf.setLength(length);
-
开启子线程并分配下载任务
int blokeSize = length / mTotalCount; System.out.println("每一块大小为:" + blokeSize); runningThreadCount = mTotalCount; for (int threadid = 0; threadid < mTotalCount; threadid++) { int startPosition = threadid * blokeSize; int endPosition = (threadid + 1) * blokeSize - 1; //最后一个线程应该下载至末尾 if (threadid == mTotalCount - 1) { endPosition = length - 1; } System.out.println("线程编号:" + threadid + "" + ",下载范围:" + startPosition + "~~" + endPosition); //开启下载子线程 new DownloadThread(threadid, startPosition, endPosition).start();
-
子线程中的具体逻辑
public void run() { System.out.println("线程"+threadid+"开始运行了"); try{ //读存有历史下载进度的文件,判断是否已经下载过 File finfo=new File(Environment.getExternalStorageDirectory().getAbsolutePath() +"/"+mTotalCount + getDownlaodName(mPath)+threadid+".txt"); if (finfo.exists()&&finfo.length()>0) { //获取文件输入流 FileInputStream fis=new FileInputStream(finfo); //读取缓冲 BufferedReader br=new BufferedReader(new InputStreamReader(fis)); //得到文件内容 String lasrposition=br.readLine(); //转化为int值 int intlastposition=Integer.parseInt(lasrposition); lastLoadSize=intlastposition-startPosition; startPosition=intlastposition; fis.close(); } URL url=new URL(mPath); HttpURLConnection conn=(HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); System.out.println("线程实际下载:"+threadid+",范围:"+startPosition +"~~"+endPosition); //设置http请求头部参数 conn.setRequestProperty("Range", "bytes="+startPosition+"-"+endPosition); int code=conn.getResponseCode(); //206代表请求部分数据成功 if (code==206) { //拿到链接返回的输入流 InputStream is=conn.getInputStream(); //指向要写的文件(abc.exe) RandomAccessFile raf=new RandomAccessFile( Environment.getExternalStorageDirectory().getAbsolutePath() +"/"+getDownlaodName(mPath), "rw"); //指定文件开始写的位置 raf.seek(startPosition); //下载缓冲区,越大下载越快,对硬盘损耗越小,但是越容易丢失数据 byte[] buffer=new byte[1024*1024]; int len=-1; int total=0;//当前线程本次下载数据总量 while ((len=is.read(buffer))!=-1) { raf.write(buffer,0,len); total+=len; //将每次更新的数据同步到底层硬盘 RandomAccessFile inforaf=new RandomAccessFile( Environment.getExternalStorageDirectory().getAbsolutePath() +"/"+mTotalCount +getDownlaodName(mPath)+threadid+".txt","rwd"); //保存当前线程下载到什么位置 inforaf.write(String.valueOf(startPosition+total).getBytes()); inforaf.close(); } is.close(); raf.close(); System.out.println("线程"+threadid+"下载完毕"); } }catch(Exception e){ e.printStackTrace(); }finally{ //同步代码块,保证同时间仅有一个线程执行此区块代码 synchronized (MainActivity.class) { runningThreadCount--; if (runningThreadCount<=0) { System.out.println("多线程下载完毕"); for (int i = 0; i <mTotalCount ; i++) { File f=new File(Environment.getExternalStorageDirectory().getAbsolutePath() +"/"+mTotalCount +getDownlaodName(mPath)+i+".txt"); System.out.println(f.delete()); endTime=System.currentTimeMillis(); } System.out.println("结束时间:"+endTime); System.out.println("总共花费:"+(endTime-startTime)/1000+"秒"); runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(getApplicationContext(), "总共花费:" +(endTime-startTime)/1000+"秒",Toast.LENGTH_SHORT).show(); } }); } } } }
备注:如果不使用断点下载,只需要将判断和存储历史下载信息的逻辑删除即可。