Android里面下载一般应该有两种方式,一种是多线程实现,一种是异步。而实现多线程在java里可以有两种方式,一种是多个线程实现同一个Runnable,另外就是将一个文件长度分为n个部分,然后让n个线程去分别下载1部分。
[1]我阅读了网上的一个方法2的实现代码,然后自己实现了一个,基本思路就是实现一个单个线程类,在这个单个线程类就完成3个操作,
1是从URL连接读取流
2是将读取的字节数组写入到本地文件中
3是将当前已经读取的长度返回
然后开启一个线程,这个线程的操作是创建一个线程数组,每个元素是刚刚的那个单个线程类,在这个线程中分配每个线程的下载起点和下载终点,然后根据各个线程的下载进度之和来更新进度条。
demo如下:

package com.study.daniel.downloadtest;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;

/**
 * Created by daniel on 15-8-1.
 */
public class DownloadSingleThread extends Thread{//单个下载线程
    /**文件下载URL*/
    private String downloadURL;
    /**文件保存路径*/
    private File file;
    /**单个线程下载文件大小*/
    private int partSize;
    /**当前已经下载的长度*/
    private int currentLength;
    /**线程id*/
    private int threadID;
    /**当前线程是否下载完成*/
    private boolean isCompleted=false;
    public DownloadSingleThread(String downloadURL,File file,int partSize,int threadID){
        this.partSize=partSize;
        this.downloadURL=downloadURL;
        this.file=file;
        this.threadID=threadID;
    }
    public void run(){
        BufferedInputStream bis=null;//读取下载文件流
        RandomAccessFile raf=null;//写入文件流
        try {
            //计算下载起点,终点
            int startPos = partSize * threadID;//开始位置
            int endPos = partSize * (threadID +1)-1;//结束位置

            //建立连接
            URL url=new URL(downloadURL);
            URLConnection connection=url.openConnection();//获得连接
            //设置本次连接的下载起点和终点
            connection.setRequestProperty("Range","bytes="+startPos+"-"+endPos);

            //创建输入流
            bis=new BufferedInputStream(connection.getInputStream());

           //创建文件写入流
            raf=new RandomAccessFile(file,"rwd");
            raf.seek(startPos);
            byte[] buffer=new byte[1024];//一次读取一k


            int len;
            while((len=bis.read(buffer,0,1024))!=-1){//本线程下载是否完成
                raf.write(buffer,0,len);//读取并写入
                currentLength+=len;
            }
            isCompleted=true;//当前线程下载完成
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {//检查并关闭IO流
            if(bis!=null)
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            if(raf!=null)
                try {
                    raf.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
    }
    public int getCurrentLength(){//返回当前下载长度
        return currentLength;
    }
    public boolean isCompleted(){
        return isCompleted;
    }

}
-------------------------------------------------------
package com.study.daniel.downloadtest;

import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;


public class MainActivity extends ActionBarActivity implements View.OnClickListener {
    private TextView tv_downloaded;
    private ProgressBar progressBar;
    Handler handler = new Handler() {
        public void handleMessage(Message msg) {

            //更新进度条
            progressBar.setProgress(msg.getData().getInt("size"));
            //算算当前进度比
            float temp = (float) progressBar.getProgress() / (float) progressBar.getMax();
            //显示进度百分比
            tv_downloaded.setText((int) (temp * 100) + "%");


            //下载完成
            if (progressBar.getProgress() == progressBar.getMax())
                Toast.makeText(MainActivity.this, "下载完成!", Toast.LENGTH_SHORT).show();
            msg.arg1 = 0;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv_downloaded = (TextView) findViewById(R.id.tv_downloaded);
        progressBar = (ProgressBar) findViewById(R.id.progressbar);
        findViewById(R.id.bt_download).setOnClickListener(this);
    }

    public void onClick(View view) {
        if (view.getId() == R.id.bt_download)
            download();
    }

    public void download() {
        //设置下载保存路径
        String path = Environment.getExternalStorageDirectory()
                + "/newdownload/";//这是下载文件保存目录

        File file = new File(path);
        if (!file.exists()) {//如果该保存目录不存在就创建
            file.mkdir();
        }
        //初始化进度条
        progressBar.setProgress(0);
        //设置下载URL
        String downloadURL = "http://gdown.baidu.com/data/wisegame/91319a5a1dfae322/baidu_16785426.apk";
        String fileName = "baidu_16785426.apk";
        int threadNum = 5;
        String filePath = path + fileName;
        System.out.println("download file  path:" + filePath);
        DownloadTask downloadTask = new DownloadTask(threadNum, downloadURL, filePath);
        downloadTask.start();

    }

    private class DownloadTask extends Thread {
        private int threadNum;//开启线程数
        private String downloadURL;//下载链接地址
        private String savePath;//文件保存路径
        private int partSize;//每一个线程的下载量,根据下载文件总长度计算得出

        public DownloadTask(int threadNum, String downloadURL, String savePath) {
            this.threadNum = threadNum;
            this.downloadURL = downloadURL;
            this.savePath = savePath;
        }

        public void run() {
            try {
                URL url = new URL(downloadURL);
                URLConnection connection = url.openConnection();
                int totalLength = connection.getContentLength();//总长度
                if (totalLength <= 0) {
                    System.out.println("读取文件失败");
                    return;
                }

                //根据下载文件长度设置进度条的最大值
                progressBar.setMax(totalLength);

                //计算单个线程下载长度
                partSize = (totalLength % threadNum == 0) ? totalLength / threadNum
                        : totalLength / threadNum + 1;

                DownloadSingleThread[] threads = new DownloadSingleThread[threadNum];
                File file = new File(savePath);
                for (int i = 0; i < threads.length; i++) {//创建并开启单个线程
                    DownloadSingleThread thread = new DownloadSingleThread(downloadURL, file, partSize, i);
                    threads[i] = thread;
                    threads[i].start();
                }
                boolean isfinished = false;
                int totalDownloadSize = 0;//5个线程当前下载总量
                while (!isfinished) {
                    isfinished = true;
                    totalDownloadSize = 0;
                    for (int i = 0; i < threads.length; i++) {
                        if (!threads[i].isCompleted())//只要有一个线程没有完成,就没有完成下载
                            isfinished = false;
                        totalDownloadSize += threads[i].getCurrentLength();//累加
                        System.out.println("totalDownloadSize----------" + totalDownloadSize);
                    }
                    //更新视图
                    Message msg = handler.obtainMessage(0x11);//发送当前下载总量去更新视图
                    msg.getData().putInt("size", totalDownloadSize);
                    msg.sendToTarget();
                    sleep(1000);//每隔1秒更新一次
                }
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }


        }
    }
    }

[2]方法1的代码是我参考方法2写的,自己实现。最后调很多遍不知为什么显示下载结束的Toast始终不消失,我以为是setRequestProperty()方法造成的下载没有结束引起,结果发现不是。后来终于发现是因为始终没有跳出while(!isfinished)循环,所以一直在更新handler,所以一直显示Toast,这个小问题搞了很久,逻辑上的,后来用了一点小技巧搞定。
demo如下:

package com.study.daniel.runnabletest;

import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;


public class MainActivity extends ActionBarActivity implements View.OnClickListener{

    /**本地保存目录*/
    private  String path= Environment.getExternalStorageDirectory()
            + "/RunnableDownload/";
    private File directory;
    /**下载连接URL*/
    private String downloadURL="http://gdown.baidu.com/data/wisegame/91319a5a1dfae322/baidu_16785426.apk";
    private String fileName="baidu_16785426.apk";

    /**下载文件总大小*/
    private int fileSize;

    /**输入流读取连接*/
    private BufferedInputStream bis;
    /**随机流保存文件*/
    private RandomAccessFile raf;
    /**每次读入保存的字节数组*/
    private byte[] buffer;
    /**下载文件*/
    private File file;

    /**线程数组*/
    private Thread[] threads;

    /**自定义线程类完成下载前的准备工作*/
    PrepareThread prepareThread;
    /**记录已下载文件距离文件开头的位置,初始为0*/
    private static int position=0;

    private boolean isfinished=false;
    private boolean runIsFinished=false;
    /**控件*/
    private TextView tv_downloaded;
    private ProgressBar progressBar;

    Handler handler=new Handler(){
        public void handleMessage(Message msg){
                     progressBar.setProgress(msg.getData().getInt("size"));
                     float temp=(float)progressBar.getProgress() /
                        (float)progressBar.getMax();
                     tv_downloaded.setText((int)(temp*100)+"%");
                    if(progressBar.getMax()==progressBar.getProgress())
                    {//下载完成
                           Toast.makeText(MainActivity.this, "下载完成", Toast.LENGTH_SHORT).show();
                    }
        }
    };

    private Runnable runnable=new Runnable() {

        @Override
        public void run() {
            try {
                URL url=new URL(downloadURL);
                URLConnection connection=url.openConnection();
                connection.setRequestProperty("Range","bytes="+0+"-"+fileSize);
                //输入流
                bis=new BufferedInputStream(connection.getInputStream());
                buffer=new byte[1024];
                //写入流
                raf=new RandomAccessFile(file,"rw");
                raf.seek(0);
                int len;
                while((len=bis.read(buffer,0,1024))!=-1){
                    raf.write(buffer,0,len);
                    position+=len;//为了更新进度条
                }
                runIsFinished=true;
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    };
    public void onClick(View view){
        if(view.getId()==R.id.bt_download)
        {
            //下载前的准备,文件及连接
            prepareThread=new PrepareThread();
            prepareThread.start();
        }

    }

    protected void onCreate(Bundle savedInstance){
        super.onCreate(savedInstance);
        setContentView(R.layout.activity_main);
        tv_downloaded=(TextView)findViewById(R.id.tv_downloaded);
        progressBar=(ProgressBar)findViewById(R.id.progressbar);
        findViewById(R.id.bt_download).setOnClickListener(this);

    }

    @Override
    protected void onStart() {
        super.onStart();
    }

    @Override
    protected void onResume() {
        super.onResume();
    }

    @Override
    protected void onDestroy() {//程序销毁时候销毁线程
        super.onDestroy();
    }

    /**
     * 自定义线程类,做初始化工作
     */
    private class PrepareThread extends Thread{
        int temp=0;
        public void run(){
            //设置本地下载文件保存目录
            directory=new File(path);
            if(!directory.exists()){//如果目录不存在则创建
                directory.mkdir();
            }
            file=new File(path+fileName);//将下载文件保存为file
            //连接
            try {
                URL url=new URL(downloadURL);
                URLConnection connection=url.openConnection();
                fileSize=connection.getContentLength();
                progressBar.setMax(fileSize);

                /**在这个线程里开启多线程,解决了执行先后的问题*/
                threads=new Thread[3];//3个线程下载
                for(int i=0;i<threads.length;i++){
                    Thread thread=new Thread(runnable);//所有线程都执行同一个任务
                    threads[i]=thread;
                    thread.start();
                }
                while(!isfinished){//更新视图
                    Message message=handler.obtainMessage(0x11);
                    message.getData().putInt("size",position);
                    message.sendToTarget();
                    sleep(1000);
                    isfinished=false;

                    if(temp==2 && runIsFinished){
                        isfinished=true;
                    }
                    if(runIsFinished ){
                        temp++;
                    }
                }
               System.out.println("----------------------out of while(!isfinished)");
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            finally {
                /**既然使用runnable,那么所有线程使用的都是一个流,那就不妨在这个线程里面,
                 * 等所有线程都结束再关闭流,就不存在问题了*/
                if(bis!=null)
                    try {
                        bis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                if(raf!=null)
                    try {
                        raf.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

            }
        }
    }
}

还遇到流的关闭的顺序问题,我原先把流的关闭放在Runnable的最后,但是造成流关闭多次的异常。我还没太搞清楚为什么。但是我将操作放到了那个准备线程中,就解决了。

运行结果如下,测试没有问题:

android 多线程下载一个文件 parcelFileDescriptor 安卓多线程下载_多线程


android 多线程下载一个文件 parcelFileDescriptor 安卓多线程下载_下载_02


我将两个demo工程上传到csdn,大家下载参考吧!我没有实现的是断点续传。实现之后更新博客,传代码。