android简单的多线程下载

1、首先实现界面的实现

关于进度条的设置

设置进度条的样式

style="?android:attr/progressBarStyleHorizontal"//设置为线性进度条

页面具体实现代码如下

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="${packageName}.${activityClass}" >

    <TextView
        android:id="@+id/tv_url"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:layout_marginTop="14dp"
        android:text="@string/tv_fileName"
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <ProgressBar
        android:id="@+id/pb"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentRight="true"
        android:layout_below="@+id/ed_url"
        />

    <EditText
        android:id="@+id/ed_url"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentRight="true"
        android:layout_below="@+id/tv_url"
        android:ems="10"
        android:inputType="textUri"
        android:text="@string/down_url" />

    <Button
        android:id="@+id/btn_download"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_below="@+id/pb"
        android:onClick="downLoadFile"
        android:text="@string/down_btn" />

    <TextView
        android:id="@+id/tv_pd"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignTop="@+id/btn_download"
        android:layout_marginTop="20dp"
        android:text="@string/tv_pd" />

</RelativeLayout>
2、得到服务器文件大小, 然后在本地设置一个临时文件()服务的文件大小一致

String path="D:\\sun.avi"; 
File file = new File(path);
RandomAccessFile accessFile = new RandomAccessFile(file, "rwd");

3、开启线程的数量(每个线程下载的大小(开始与结束位置))
//开启线程数量
 threadNum=3

   
//每个线程长度
threadSize = fileSize/threadNum =3
     
//开始位置
startIndex = (threadNum-1)*3
     
//结束位置
endIndex =threadNum*threadSize-1         
 
   
以fileSize=11, threadNum=3为例
threadNum=1 startIndex = 0 endIndex = 2
      threadNum=2 startIndex = 3 endIndex = 5
    threadNum=3 startIndex = 6 endIndex = 10

  int threadSize = fileLength/threadNum;
      for (int threadId=1;threadId <=3;threadId++) {
           int startIndex=(threadId-1)*threadSize;//开始位置
           int endIndex = threadId*threadSize-1;
           if(threadId==threadNum){//最后一个线程
               endIndex=fileLength;
           }
      System.out.println("当前线程--"+threadId+"开始位置---"+startIndex+"结束位置---"+endIndex+"线程大小---");
      //开启线程下载
      new DownLoadThread(threadId, startIndex, endIndex, spec).start();
}
4、 使用Http的Range头字段指定每条线程从文件的什么位置开始下载

   URL url = new URL(path);//通过path路径构建URL对象
   //通过URL对象的打开连接,返回对象
   HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
   //设置请求头
   httpURLConnection.setRequestMethod("GET");
   httpURLConnection.setConnectTimeout(5000);
   //设置下载文件的开始位置和结束位置
   httpURLConnection.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex);
   //获取的状态吗
   int code = httpURLConnection.getResponseCode();

5、保存文件,使用RandomAccessFile类指定每条线程从本地文件的什么位置开始写入数据
while((len=inputStream.read(buffer))!=-1){
      System.out.println("当前线程--"+threadId+"--已经下载了"+(startIndex+total));
      @SuppressWarnings("resource")
      RandomAccessFile threadfile = new RandomAccessFile(new File("D:\\"+threadId+".txt"),"rwd");
      threadfile.writeBytes((startIndex+total)+"");
      raf.write(buffer, 0, len);
      total+=len;
}

基本实现下载功能后需要进一步改进,实现断点续传的功能

首先得记录下下载断点的位置
判断是否存在sd卡
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){}
使用txt文本记录断点位置,并在进入程序的时候判断是否存在断点文件
File sdDir = Environment.getExternalStorageDirectory();
File pdfile = new File(sdDir, "pd.txt");
FileInputStream is=null;
try {
	if (pdfile.exists()) {
	//首先判断文件是否存在
	is = new FileInputStream(pdfile);
	}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
	e.printStackTrace();
}
if (is!=null) {
	String value = StreamTools.streamTostr(is);
	String arr[] = value.split(";");
	pBar.setMax(Integer.valueOf(arr[0]));//最大值
	currentProgress = Integer.valueOf(arr[1]);//当前值
	pBar.setProgress(currentProgress);//显示文本
	//String percent = arr[1];
	tv_pd.setText(arr[2]);
}

实现代码如下
package www.csdn.net.download;

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

import www.csdn.net.tools.StreamTools;
import android.R.string;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.text.TextUtils;
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 int threadRunning = 3;
	private int threadNum = 3;

	private EditText et_url;
	private TextView tv_pd;

	private ProgressBar pBar;
	private int currentProgress;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		et_url = (EditText) findViewById(R.id.ed_url);
		tv_pd = (TextView) findViewById(R.id.tv_pd);

		pBar = (ProgressBar) findViewById(R.id.pb);

		File sdDir = Environment.getExternalStorageDirectory();
		File pdfile = new File(sdDir, "pd.txt");
		FileInputStream is=null;
		try {
			if (pdfile.exists()) {
				//首先判断文件是否存在
				is = new FileInputStream(pdfile);
			}
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		if (is!=null) {
			String value = StreamTools.streamTostr(is);
			String arr[] = value.split(";");
			pBar.setMax(Integer.valueOf(arr[0]));//最大值
			currentProgress = Integer.valueOf(arr[1]);//当前值
			pBar.setProgress(currentProgress);//显示文本
			//String percent = arr[1];
			tv_pd.setText(arr[2]);
		}
	}

	public void downLoadFile(View v) {

		// 获取下载路径
		final String spec = et_url.getText().toString();
		if (TextUtils.isEmpty(spec)) {
			Toast.makeText(this, "下载地址不为空", Toast.LENGTH_LONG).show();
		} else {
			new Thread() {
				@Override
				public void run() {
					// TODO Auto-generated method stub
					try {
						URL url = new URL(spec);
						HttpURLConnection httpURLConnection = (HttpURLConnection) url
								.openConnection();
						// 设置请求的头文件信息还有时间
						httpURLConnection.setRequestMethod("GET");
						httpURLConnection.setConnectTimeout(5000);
						httpURLConnection.setReadTimeout(5000);
						if (httpURLConnection.getResponseCode() == 200) {
							int fileLength = httpURLConnection
									.getContentLength();
							// 设置进度条最大值
							pBar.setMax(fileLength);

							// 判断是否存在外部存储设备
							if (Environment.getExternalStorageState().equals(
									Environment.MEDIA_MOUNTED)) {
								File sdFile = Environment
										.getExternalStorageDirectory();
								// 获取文件名称
								String fileName = spec.substring(spec
										.lastIndexOf("/") + 1);
								// 创建保存文件
								File file = new File(sdFile, fileName);
								// 穿件随机可以访问的文件对象
								RandomAccessFile accessFile = new RandomAccessFile(
										file, "rwd");
								// 设置文件大小
								accessFile.setLength(fileLength);

								accessFile.close();

								// 首先计算出每个线程下载的大小 开始位置 结束位置
								int threadSize = fileLength / threadNum;

								for (int threadId = 1; threadId <= 3; threadId++) {
									int startIndex = (threadId - 1)
											* threadSize;// 开始位置
									int endIndex = threadId * threadSize - 1;
									if (threadId == threadNum) {// 最后一个线程
										endIndex = fileLength;
									}
									System.out.println("当前线程--" + threadId
											+ "开始位置---" + startIndex
											+ "结束位置---" + endIndex + "线程大小---");
									// 开启线程下载
									new DownLoadThread(threadId, startIndex,
											endIndex, spec, fileName).start();
								}
							} else {
								MainActivity.this.runOnUiThread(new Runnable() {

									@Override
									public void run() {
										// TODO Auto-generated method stub
										Toast.makeText(MainActivity.this,
												"sd卡不可用", 1).show();
									}
								});
							}

						} else {
							// 在线程中运行
							MainActivity.this.runOnUiThread(new Runnable() {
								public void run() {
									Toast.makeText(MainActivity.this,
											"服务器返回错误", 1).show();
									;
								}
							});
						}
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}.start();

		}
	}

	class DownLoadThread extends Thread {
		private int threadId;
		private int startIndex;
		private int endIndex;
		private String path;
		private String fileName;

		public DownLoadThread(int threadId, int startIndex, int endIndex,
				String path, String fileName) {
			super();
			this.threadId = threadId;
			this.startIndex = startIndex;
			this.endIndex = endIndex;
			this.path = path;
			this.fileName = fileName;
		}

		@Override
		public void run() {

			File sdFile = Environment.getExternalStorageDirectory();

			// 获取每个线程下载的记录文件
			File recordFile = new File(sdFile, threadId + ".txt");

			// 可以通过每个线程去下载文件了。
			try {

				// 首先从本地文件上读取已经下载文件的开始位置
				if (recordFile.exists()) {
					// 读取文件的内容
					InputStream is = new FileInputStream(recordFile);
					// 利用工具类转换
					String value = StreamTools.streamTostr(is);
					// 获取记录位置
					int recordIndex = Integer.parseInt(value);
					startIndex = recordIndex;// 记录的位置复制给开始的位置即可
				}
				URL url = new URL(path);// 通过path路径构建URL对象
				// 通过URL对象的打开连接,返回对象
				HttpURLConnection httpURLConnection = (HttpURLConnection) url
						.openConnection();
				// 设置请求头
				httpURLConnection.setRequestMethod("GET");
				httpURLConnection.setConnectTimeout(5000);
				// 设置下载文件的开始位置和结束位置
				httpURLConnection.setRequestProperty("Range", "bytes="
						+ startIndex + "-" + endIndex);
				// 获取的状态吗
				int code = httpURLConnection.getResponseCode();
				if (code == 206) {
					// 获取每个线程返回的流对象:
					InputStream inputStream = httpURLConnection
							.getInputStream();
					// 定一写入文件的路径
					// 根据路径创建文件
					File file = new File(sdFile, fileName);
					// 根据文件创建她RandomAccessFile对象
					RandomAccessFile raf = new RandomAccessFile(file, "rwd");
					raf.seek(startIndex);
					// 定义读取的长度
					int len = 0;
					byte buffer[] = new byte[1024 * 1024 * 10];
					int total = 0;
					// 循环读取
					while ((len = inputStream.read(buffer)) != -1) {
						System.out.println("当前线程--" + threadId + "--已经下载了"
								+ (startIndex + total));
						@SuppressWarnings("resource")
						RandomAccessFile threadfile = new RandomAccessFile(
								new File(sdFile, threadId + ".txt"), "rwd");
						threadfile.writeBytes((startIndex + total) + "");
						threadfile.close();
						raf.write(buffer, 0, len);
						total += len;

						//解决同步问题
						synchronized (MainActivity.this) {
							currentProgress += len;
							pBar.setProgress(currentProgress);
							final String percent = currentProgress*100L/pBar.getMax()+"%";
							MainActivity.this.runOnUiThread(new Runnable() {

								@Override
								public void run() {
									// TODO Auto-generated method stub
									tv_pd.setText("当前进度是:" + percent);
								}
							});

							RandomAccessFile pbfile = new RandomAccessFile(new File(sdFile, "pd.txt"),"rwd");
							pbfile.writeBytes(pBar.getMax()+";"+currentProgress+";"+percent);
							pbfile.close();
						}
						

					}
					raf.close();
					inputStream.close();

					MainActivity.this.runOnUiThread(new Runnable() {

						@Override
						public void run() {
							// TODO Auto-generated method stub
							Toast.makeText(MainActivity.this,
									"当前线程----" + threadId + "下载完毕", 1).show();
						}
					});

					deleteRecordFiles();
				} else {
					runOnUiThread(new Runnable() {

						@Override
						public void run() {
							// TODO Auto-generated method stub
							Toast.makeText(MainActivity.this, "服务器端下载错误", 1)
									.show();
						}
					});
				}
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	public synchronized void deleteRecordFiles() {
		File sdFile = Environment.getExternalStorageDirectory();
		threadRunning--;
		if (threadRunning == 0) {
			for (int i = 1; i < 3; i++) {
				File recordFile = new File(sdFile, i + ".txt");
				if (recordFile.exists()) {
					recordFile.delete();// 删除掉文件
				}
				File pdFile = new File(sdFile,"pd.txt");
				if (pdFile.exists()) {
					pdFile.delete();
				}
			}
		}
	}


}

对于多线程下载理解还不是很透彻,在测试的过程中出现了不少bug,比如断点保存时会超出100%,进入下载后退出再进入后悔停止运行,再重新进入后才能下载。望大神们指教。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值