Android使用多线程实现断点下载

多线程下载是加快下载速度的一种方式,通过开启多个线程去执行一个任务..可以使任务的执行速度变快..多线程的任务下载时常都会使用得到..比如说我们手机内部应用宝的下载机制..一定是通过使用了多线程创建的下载器..并且这个下载器可以实现断点下载..在任务被强行终止之后..下次可以通过触发按钮来完成断点下载...那么如何实现断点下载这就是一个问题了..

首先我们需要明确一点就是多线程下载器通过使用多个线程对同一个任务进行下载..但是这个多线程并不是线程的数目越多,下载的速度就越快..当线程增加的很多的时候,单个线程执行效率也会变慢..因此线程的数目需要有一个限度..经过楼主亲自测试..多线程下载同一个任务时,线程的数目5-8个算是比较高效的..当线程的数量超过10个之后,那么多线程的效率反而就变慢了(前提:网速大体相同的时候..)

那么在实现多线程下载同一个任务的时候我们需要明白其中的道理..下面先看一张附加图..

这个图其实就很简单的说明了其中的原理..我们将一个任务分成多个部分..然后开启多个线程去下载对应的部分就可以了..那么首先要解决的问题就是如何使各自的线程去下载各自对应的任务,不能越界..那么我们来看看具体的实现过程..首先我们使用普通Java代码去实现..最后将Java代码移植到Android就可以了..

public class Download { public static int threadCount = 5; //线程开启的数量.. public static void main(String[] args) { // TODO Auto-generated method stub String path = "http://192.168.199.172:8080/jdk.exe"; try { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(5000); int status = conn.getResponseCode(); if(status == 200){ int length = conn.getContentLength(); System.out.println(length); int blocksize = length/threadCount; //将文件长度进行平分.. for(int threadID=1; threadID<=threadCount;threadID++){ int startIndex = (threadID-1)*blocksize; //开始位置的求法.. int endIndex = threadID*blocksize -1; //结束位置的求法.. /** * 如果一个文件的长度无法整除线程数.. * 那么最后一个线程下载的结束位置需要设置文件末尾.. * */ if(threadID == threadCount){ endIndex = length; } System.out.println("线程下载位置:"+startIndex+"---"+endIndex); } } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }

这样只是实现了通过连接服务器来获取文件的长度..然后去设置每一个线程下载的开始位置和结束位置..这里只是完成了这些步骤..有了下载的开始位置和结束位置..我们就需要开启线程来完成下载了...因此我们需要自己定义下载的过程...

首先我们需要明确思路:既然是断点下载..那么如果一旦发生了断点情况..我们在下一次进行下载的时候需要从原来断掉的位置进行下载..已经下载过的位置我们就不需要进行下载了..因此我们需要记载每一次的下载记录..那么有了记录之后..一旦文件下载完成之后..这些记录就需要被清除掉...因此明确了这两个地方的思路就很容易书写了..

//下载线程的定义.. public static class DownLoadThread implements Runnable{ private int threadID; private int startIndex; private int endIndex; private String path; public DownLoadThread(int threadID,int startIndex,int endIndex,String path){ this.threadID = threadID; this.startIndex = startIndex; this.endIndex = endIndex; this.path = path; } @Override public void run() { // TODO Auto-generated method stub try { //判断上一次是否下载完毕..如果没有下载完毕需要继续进行下载..这个文件记录了上一次的下载位置.. File tempfile =new File(threadID+".txt"); if(tempfile.exists() && tempfile.length()>0){ FileInputStream fis = new FileInputStream(tempfile); byte buffer[] = new byte[1024]; int leng = fis.read(buffer); int downlength = Integer.parseInt(new String(buffer,0,leng));//从上次下载后的位置开始下载..重新拟定开始下载的位置.. startIndex = downlength; fis.close(); } URL url = new URL(path); HttpURLConnection conn =(HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(5000); conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex); int status = conn.getResponseCode(); //206也表示服务器响应成功.. if(status == 206){ //获取服务器返回的I/O流..然后将数据写入文件当中.. InputStream in = conn.getInputStream(); //文件写入开始..用来保存当前需要下载的文件.. RandomAccessFile raf = new RandomAccessFile("jdk.exe", "rwd"); raf.seek(startIndex); int len = 0; byte buf[] =new byte[1024]; //记录已经下载的长度.. int total = 0; while((len = in.read(buf))!=-1){ //用于记录当前下载的信息.. RandomAccessFile file =new RandomAccessFile(threadID+".txt", "rwd"); total += len; file.write((total+startIndex+"").getBytes()); file.close(); //将数据写入文件当中.. raf.write(buf, 0, len); } in.close(); raf.close(); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ //如果所有的线程全部下载完毕后..也就是任务完成..清除掉所有原来的记录文件.. runningThread -- ; if(runningThread==0){ for(int i=1;i<threadCount;i++){ File file = new File(i+".txt"); file.delete(); } } } } }

这样就完成了文件的数据信息的下载..经过测试..一个13M的文件在5个线程共同作用下下载的时间差不多是12秒左右(网速稳定在300k的情况下..带宽越宽..速度就会更快)单个线程下载的时间差不多是15秒左右..这里才缩短了两秒钟的时间..但是我们不要忘记..如果文件过大的话呢?因此楼主亲测了一下一个90M的文件在5个线程同时作用下时间差不多1分20秒左右..而使用一个线程进行下载差不多2分钟左右..这里还是缩短了大量的时间..

因此根据对比,还是使用多个线程来进行下载更加的好一些..虽然Android里的一般应用不会超过50M左右..但是游戏的话一般差不多能达到100-200M左右..因此使用多线程还是能够提高下载的进度和效率..同样我们可以通过使用线程池的方式去开启线程..最后这些线程交给线程池去管理就可以了..

在正常的Java项目中我们书写好了下载代码..就可以移植到我们的Android应用程序当中..但是还是有一些地方需要注意..因此在这里去强调一下..

package com.example.mutithread; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.app.Activity; import android.text.TextUtils; import android.view.Menu; import android.view.View; import android.widget.EditText; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends Activity { private EditText et; private ProgressBar pb; public static int threadCount = 5; public static int runningThread=5; public int currentProgress=0; //当前进度值.. private TextView tv; private Handler handler = new Handler(){ @Override public void handleMessage(Message msg){ switch (msg.what) { case 1: Toast.makeText(getApplicationContext(), msg.obj.toString(), 0).show(); break; case 2: break; case 3: tv.setText("当前进度:"+(pb.getProgress()*100)/pb.getMax()); default: break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); et = (EditText) findViewById(R.id.et); pb =(ProgressBar) findViewById(R.id.pg); tv= (TextView) findViewById(R.id.tv); } public void downLoad(View v){ final String path = et.getText().toString().trim(); if(TextUtils.isEmpty(path)){ Toast.makeText(this, "下载路径错误", Toast.LENGTH_LONG).show(); return ; } new Thread(){ // String path = "http://192.168.199.172:8080/jdk.exe"; public void run(){ try { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(5000); int status = conn.getResponseCode(); if(status == 200){ int length = conn.getContentLength(); System.out.println("文件总长度"+length); pb.setMax(length); RandomAccessFile raf = new RandomAccessFile("/sdcard/setup.exe","rwd"); raf.setLength(length); raf.close(); //开启5个线程来下载当前资源.. int blockSize = length/threadCount ; for(int threadID=1;threadID<=threadCount;threadID++){ int startIndex = (threadID-1)*blockSize; int endIndex = threadID*blockSize -1; if(threadID == threadCount){ endIndex = length; } System.out.println("线程"+threadID+"下载:---"+startIndex+"--->"+endIndex); new Thread(new DownLoadThread(threadID, startIndex, endIndex, path)).start() ; } } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } }; }.start(); } /** * 下载线程.. * */ public class DownLoadThread implements Runnable{ private int ThreadID; private int startIndex; private int endIndex; private String path; public DownLoadThread(int ThreadID,int startIndex,int endIndex,String path){ this.ThreadID = ThreadID; this.startIndex = startIndex; this.endIndex = endIndex; this.path = path; } @Override public void run() { // TODO Auto-generated method stub URL url; try { //检查是否存在还未下载完成的文件... File tempfile = new File("/sdcard/"+ThreadID+".txt"); if(tempfile.exists() && tempfile.length()>0){ FileInputStream fis = new FileInputStream(tempfile); byte temp[] =new byte[1024]; int leng = fis.read(temp); int downlength = Integer.parseInt(new String(temp,0,leng)); int alreadydown = downlength -startIndex; currentProgress += alreadydown;//发生断点之后记录下载的文件长度.. startIndex = downlength; fis.close(); } url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex); conn.setConnectTimeout(5000); //获取响应码.. int status =conn.getResponseCode(); if(status == 206){ InputStream in = conn.getInputStream(); RandomAccessFile raf =new RandomAccessFile("/sdcard/jdk.exe", "rwd"); //文件开始写入.. raf.seek(startIndex); int len =0; byte[] buffer =new byte[1024]; //已经下载的数据长度.. int total = 0; while((len = in.read(buffer))!=-1){ //记录当前数据下载的长度... RandomAccessFile file = new RandomAccessFile("/sdcard/"+ThreadID+".txt", "rwd"); raf.write(buffer, 0, len); total += len; System.out.println("线程"+ThreadID+"total:"+total); file.write((total+startIndex+"").getBytes()); file.close(); synchronized (MainActivity.this) { currentProgress += len; //获取当前总进度... //progressBar progressDialog可以直接在子线程内部更新UI..由于源码内部进行了特殊的处理.. pb.setProgress(currentProgress); //更改界面上的进度条进度.. Message msg =Message.obtain(); //复用以前的消息..避免多次new... msg.what = 3; handler.sendMessage(msg); } } raf.close(); in.close(); System.out.println("线程:"+ThreadID+"下载完毕"); }else{ System.out.println("线程:"+ThreadID+"下载失败"); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); Message msg = new Message(); msg.what = 1; msg.obj = e; handler.sendMessage(msg); }finally{ synchronized (MainActivity.this) { runningThread--; if(runningThread == 0){ for(int i=1;i<=threadCount;i++){ File file = new File("/sdcard/"+i+".txt"); file.delete(); } Message msg =new Message(); msg.what = 2; msg.obj ="下载完毕"; handler.sendMessage(msg); } } } } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }

源代码如上..优化的事情我就不做了..为了方便直接就贴上了..这里定义了一个ProgressBar进度条..一个TextView来同步进度条的下载进度..在Android中我们自然不能够在主线程中去调用耗时间的操作..因此这些耗时的操作我们就通过开启子线程的方式去使用..但是子线程是不能够更新UI界面的..因此我们需要使用到Handler Message机制来完成主界面UI更新的操作.

但是上面的代码当中我们会发现一个问题..在子线程内部居然更新了ProgressBar操作..其实ProgressBar和ProgressDialog是两个特例..我们可以在子线程内部去更新他们的属性..我们来看一下源代码的实现过程..

private synchronized void refreshProgress(int id, int progress, boolean fromUser) { if (mUiThreadId == Thread.currentThread().getId()) { //如果当前运行的线程和主线程相同..那么更新进度条.. doRefreshProgress(id, progress, fromUser, true); } else { //如果不满足上面说的情况.. if (mRefreshProgressRunnable == null) { mRefreshProgressRunnable = new RefreshProgressRunnable();//那么新建立一个线程..然后执行下面的过程.. } final RefreshData rd = RefreshData.obtain(id, progress, fromUser); //获取消息队列中的消息.. mRefreshData.add(rd); if (mAttached && !mRefreshIsPosted) { post(mRefreshProgressRunnable); //主要是这个地方..调用了post方法..将当前运行的线程发送到消息队列当中..那么这个线程就可以在UI中运行了..因此这一步是决定因素.. mRefreshIsPosted = true; } } }

正是由于源码内部调用了post方法..将当前的线程放入到消息队列当中..那么UI中的Looper线程就会对这个线程进行处理,那么就表示这个线程是可以被执行在UI当中的..也正是这个因素导致了我们可以在子线程内部更新ProgressBar..但是我们可以看到如果我们想要去更新TextView的时候..我们就需要调用Handler Message机制来完成UI界面的更新了..因此这一块需要我们去注意。
  移植之后代码其实并没有发生太大的变化,这样就可以完成一个在Android中的多线程断点下载器了。

以上就是本文的全部内容,希望对大家学习Android软件编程有所帮助。

时间: 2024-10-23 05:10:48

Android使用多线程实现断点下载的相关文章

Android使用多线程实现断点下载_Android

多线程下载是加快下载速度的一种方式,通过开启多个线程去执行一个任务..可以使任务的执行速度变快..多线程的任务下载时常都会使用得到..比如说我们手机内部应用宝的下载机制..一定是通过使用了多线程创建的下载器..并且这个下载器可以实现断点下载..在任务被强行终止之后..下次可以通过触发按钮来完成断点下载...那么如何实现断点下载这就是一个问题了..   首先我们需要明确一点就是多线程下载器通过使用多个线程对同一个任务进行下载..但是这个多线程并不是线程的数目越多,下载的速度就越快..当线程增加的很

Android原生实现多线程断点下载实例代码

各位父老乡亲,我单汉三又回来了,今天为大家带来一个用原生的安卓写的多线程断点下载Demo. 通过本文你可以学习到: SQLite的基本使用,数据库的增删改查. Handler的消息处理与更新UI. Service(主要用于下载)的进阶与使用. 原生的json文件解析(多层嵌套). RandomAccessFile的基本使用,可以将文件分段. 基于HttpURLConnection的大文件下载. 上面内容结合,实现多线程,断点下载. Demo是在TV上运行的,图片显示的问题不要纠结了. 文件下载的

java-多线程加上断点下载多个文件文件出现问题了

问题描述 多线程加上断点下载多个文件文件出现问题了 package cn.com.sinosoft.sfjy.util; 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 jav

Android实现多线程断点下载的方法

  本文实例讲述了Android实现多线程断点下载的方法.分享给大家供大家参考.具体实现方法如下: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69

我的Android进阶之旅------&amp;gt;Android基于HTTP协议的多线程断点下载器的实现

一.首先写这篇文章之前,要了解实现该Android多线程断点下载器的几个知识点  1.多线程下载的原理,如下图所示 注意:由于Android移动设备和PC机的处理器还是不能相比,所以开辟的子线程建议不要多于5条.当然现在某些高端机子的处理器能力比较强了,就可以多开辟几条子线程. 2.为了实现断点下载,采用数据库方式记录下载的进度,这样当你将该应用退出后,下次点击下载的时候,程序会去查看该下载链接是否存在下载记录,如果存在下载记录就会判断下载的进度,如何从上次下载的进度继续开始下载. 3.特别注意

Android入门:多线程断点下载详细介绍_Android

本案例在于实现文件的多线程断点下载,即文件在下载一部分中断后,可继续接着已有进度下载,并通过进度条显示进度.也就是说在文件开始下载的同时,自动创建每个线程的下载进度的本地文件,下载中断后,重新进入应用点击下载,程序检查有没有本地文件的存在,若存在,获取本地文件中的下载进度,继续进行下载.当下载完成后,自动删除本地文件. 一.多线程断点下载介绍 所谓的多线程断点下载就是利用多线程下载,并且可被中断,如果突然没电了,重启手机后可以继续下载,而不需要重新下载: 利用的技术有:SQLite存储各个线程的

Android多线程断点下载完整示例详解

MainActivity如下: package cc.activity; import java.io.File; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.view.

Android入门:多线程断点下载详细介绍

本案例在于实现文件的多线程断点下载,即文件在下载一部分中断后,可继续接着已有进度下载,并通过进度条显示进度.也就是说在文件开始下载的同时,自动创建每个线程的下载进度的本地文件,下载中断后,重新进入应用点击下载,程序检查有没有本地文件的存在,若存在,获取本地文件中的下载进度,继续进行下载.当下载完成后,自动删除本地文件. 一.多线程断点下载介绍 所谓的多线程断点下载就是利用多线程下载,并且可被中断,如果突然没电了,重启手机后可以继续下载,而不需要重新下载: 利用的技术有:SQLite存储各个线程的

多线程-android 多个文件,每个文件都使用断点下载,线程是否会太多

问题描述 android 多个文件,每个文件都使用断点下载,线程是否会太多 我现在想使用文件的断点下载功能,每个文件可以分成几断,使用线程下载,如果存在多个文件都是用线程下载的话,线程有点多,怎样才是一个合适的方法 解决方案 可以试试线程池,或者做一个自己控制的线程队列 解决方案二: 线程池,或者就用AsyncTask,里面用的就是线程池.