Android中http多线程下载



参考了一篇博客,但是已改动了不少,主要是在进度条的计算上,工作完后博客地址已找不到,所以没办法贴上参考博客地址,见谅!贴上整个Activity代码吧:

package com.example.httpdownloadtest;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;

/**
 * @Class Name : DownLoadFileActivity
 * @Description:
 * @Author yuanlf
 * @Date 2014年6月6日 下午2:55:35
 */
public class DownLoadFileActivity extends Activity {

    private Context context;
    private ProgressBar downPrBar;
    private TextView loadProcessNumber;
    private String fileName;
    private String url;
    private final String tag = getClass().getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_down_file);
        context = this;
        initData();
        initUI();
    }

    private void initUI() {
        downPrBar = (ProgressBar) findViewById(R.id.downPrBar);
        loadProcessNumber = (TextView) findViewById(R.id.loadProcessNumber);
        showDownLoadPrompt();
    }

    private void initData() {
        fileName = "陌陌.apk";
        url = "http://gdown.baidu.com/data/wisegame/fbd69031ba7434e0/momo_206.apk";
    }

    private AlertDialog alertDialog;

    /**
     * @Description: 显示图书下载提示对话框
     */
    private void showDownLoadPrompt() {
        LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
        View view = LayoutInflater.from(this).inflate(R.layout.common_prompt_two_button_layout,
                null);
        alertDialog = new AlertDialog.Builder(this).create();
        alertDialog.setCanceledOnTouchOutside(false);
        alertDialog.show();
        alertDialog.setCancelable(false);
        alertDialog.setContentView(view, params);
        TextView titletip = (TextView) view.findViewById(R.id.titleTipTv);
        titletip.setText("下载");
        TextView content = (TextView) view.findViewById(R.id.contents);
        String message = fileName + "是否需要下载?";
        content.setText(message);

        Button confirmBtn = (Button) view.findViewById(R.id.confirmBtn);
        confirmBtn.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View arg0) {
                try {
                    alertDialog.dismiss();
                    downPrBar.setVisibility(View.VISIBLE);
                    new Thread() {
                        @Override
                        public void run() {
                            super.run();
                            try {
                                download(url, 3);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }.start();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

        Button cancelBtn = (Button) view.findViewById(R.id.cancelBtn);
        cancelBtn.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View arg0) {
                alertDialog.dismiss();
            }
        });
    }

    /**
     * 从路径中获取文件名称
     * 
     * @param path 下载路径
     * @return
     */
    public static String getFilename(String path) {
        return path.substring(path.lastIndexOf('/') + 1);
    }

    /**
     * 下载文件
     * 
     * @param path 下载路径
     * @param threadsize 线程数
     */
    public void download(String path, int threadsize) throws Exception {
        URL url = new URL(path);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        conn.setConnectTimeout(5 * 1000);
        sizeCount = conn.getContentLength();// 获取要下载的文件的长度
        Log.d(tag, "总大小:" + sizeCount);
        File saveFile = EnvironmentShare.getDownLoadFile(fileName);
        RandomAccessFile accessFile = new RandomAccessFile(saveFile, "rwd");
        accessFile.setLength(sizeCount);// 设置本地文件的长度和下载文件相同
        accessFile.close();
        // 计算每条线程下载的数据长度
        int block = sizeCount % threadsize == 0 ? sizeCount / threadsize : sizeCount / threadsize
                + 1;
        for (int threadid = 0; threadid < threadsize; threadid++) {
            new DownloadThread(url, saveFile, block, threadid).start();
        }
    }

    private final class DownloadThread extends Thread {
        private URL url;
        private File saveFile;
        private int block;// 每条线程下载的数据长度
        private int threadid;// 线程id

        public DownloadThread(URL url, File saveFile, int block, int threadid) {
            this.url = url;
            this.saveFile = saveFile;
            this.block = block;
            this.threadid = threadid;
        }

        @Override
        public void run() {
            // 计算开始位置公式:线程id*每条线程下载的数据长度= ?
            // 计算结束位置公式:(线程id +1)*每条线程下载的数据长度-1 =?
            int startposition = threadid * block;
            int endposition = (threadid + 1) * block - 1;
            try {
                RandomAccessFile accessFile = new RandomAccessFile(saveFile, "rwd");
                accessFile.seek(startposition);// 设置从什么位置开始写入数据
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");
                conn.setConnectTimeout(5 * 1000);
                conn.setRequestProperty("Range", "bytes=" + startposition + "-" + endposition);
                InputStream inStream = conn.getInputStream();
                byte[] buffer = new byte[1024];
                int len = 0;
                int count = 0;
                while ((len = inStream.read(buffer)) != -1) {
                    accessFile.write(buffer, 0, len);
                    count += len;
                    setDownSizes(len);
                    int downProcess = (int) (downedSizes * 100) / sizeCount;
                    if (downProcess > showProcess) {
                        showProcess = downProcess;
                        updateProgressBarHandler.sendEmptyMessage(Flag_UpadateProgressBar);
                    }
                }
                inStream.close();
                accessFile.close();
                Log.d(tag, "线程id:" + threadid + "下载完成,大小:" + count + "总大小" + downedSizes);
            } catch (Exception e) {
                e.printStackTrace();
                updateProgressBarHandler.sendEmptyMessage(Flag_DownloadError);
                Log.e(tag, "下载文件时异常", e);
            }
        }
    }

    /**
     * @Description: 同步增加已下载长度,避免多线程操作导致增加异常
     * @param len
     */
    private synchronized void setDownSizes(int len) {
        downedSizes += len;
    }

    /** 已下载长度 **/
    private int downedSizes = 0;
    /** 要下载文件总长度 **/
    private int sizeCount;
    /** 已显示长度 **/
    private int showProcess = 0;

    /** 更新进度条标识 **/
    private final int Flag_UpadateProgressBar = 69;
    /** 下载错误标识 **/
    private final int Flag_DownloadError = 96;

    private Handler updateProgressBarHandler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            switch (msg.what) {

                case Flag_UpadateProgressBar:
                    downPrBar.setProgress(showProcess);
                    loadProcessNumber.setText(fileName + "已下载:" + showProcess + "%");
                    if (showProcess == 100) {
                        finish();
                        Toast.makeText(context, "下载成功", Toast.LENGTH_SHORT).show();
                    }
                    break;

                case Flag_DownloadError:
                    Toast.makeText(context, "下载失败", Toast.LENGTH_SHORT).show();
                    finish();
                    break;

                default:
                    break;
            }
        };
    };

}


1. 预算每个线程下载的字节大小,能整除去商,不能整除则为商+1,这样最后一条线程以外的线程会下载的比平均值多1个字节,但是最后一个线程会下载剩余(小于平均值)的长度,详细见下段代码:

    

// 计算每条线程下载的数据长度
        int block = sizeCount % threadsize == 0 ? sizeCount / threadsize : sizeCount / threadsize + 1;


2. 获取每条线程实际下载的大小:

// 计算开始位置公式:线程id*每条线程下载的数据长度= ?
            // 计算结束位置公式:(线程id +1)*每条线程下载的数据长度-1 =?
            int startposition = threadid * block;
            int endposition = (threadid + 1) * block - 1;
            try {
                RandomAccessFile accessFile = new RandomAccessFile(saveFile, "rwd");
                accessFile.seek(startposition);// 设置从什么位置开始写入数据
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");
                conn.setConnectTimeout(5 * 1000);
                conn.setRequestProperty("Range", "bytes=" + startposition + "-" + endposition);


如果文件总大小和线程总数的商不能整除,那么上一步预算的block会比平均值大,

conn.setRequestProperty("Range", "bytes=" + startposition + "-" + endposition);
这段代码会返回该线程实际下载的大小,这样所有线程下载的总长度就和实际文件长度相等,保证文件的完整性。


3. 计算已下载的总长度:

/**
     * @Description: 同步增加已下载长度,避免多线程操作导致增加异常
     * @param len
     */
    private synchronized void setDownSizes(int len) {
        downedSizes += len;
    }

如果不用同步锁的话,因为多线程操作,已下载总长度增加时长度会丢失。


4. 下载进度大于已显示进度时再更新UI:

int downProcess = (int) (downedSizes * 100) / sizeCount;
                    if (downProcess > showProcess) {
                        showProcess = downProcess;
                        updateProgressBarHandler.sendEmptyMessage(Flag_UpadateProgressBar);
                    }

这样,避免了UI被多次无用刷新。



测试Project代码下载:http://download.csdn.net/detail/lufengdie/7487821



其他参考地址:http://blog.csdn.net/wanglong0537/article/details/6411565

                            http://gundumw100.iteye.com/blog/906072

                            http://www.cnblogs.com/hanyonglu/archive/2012/02/20/2358801.html



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值