01—android入门_多线程下载

 

01android入门_多线程下载

1、多线程下载的简介

线程可以理解为下载的通道,一个线程就是一个文件的下载通道,多线程也就是同时开起好几个下载通道.当服务器提供下载服务时,使用下载者是共享带宽的,在优先级相同的情况下,总服务器会对总下载线程进行平均分配。不难理解,如果你线程多的话,那下载的越快。现流行的下载软件都支持多线程。

注意:实现多线程的条件是服务器支持单一IP多线程下载,如果不支持的话,很有可能封IP或者是只有一个线程能连接成功,多余线程被屏蔽。部分软件提供"用代理下载"方式,这种方式不会封IP

 

2、多线程下载的原理

通常服务器同时与多个用户连接,用户之间共享带宽。如果N个用户的优先级都相同,那么每个用户连接到该服务器上的实际带宽就是服务器带宽的N分之一。可以想象,如果用户数目较多,则每个用户只能占有可怜的一点带宽,下载将会是个漫长的过程。

如果你通过多个线程同时与服务器连接,那么你就可以榨取到较高的带宽了。例如原来有10个用户都通过单一线程与服务器相连,服务器的总带宽假设为56Kbps,则每个用户(每个线程)分到的带宽是5.6Kbps,即0.7K字节/秒。如果你同时打开两个线程与服务器连接,那么共有11个线程与服务器连接,而你获得的带宽将是56/11*2=10.2Kbps,约1.27K字节/秒,将近原来的两倍。你同时打开的线程越多,你所获取的带宽就越大(原来是这样,以后每次我都通过1K个线程连接:P)。当然,这种情况下占用的机器资源也越多。有些号称“疯狂下载”的下载工具甚至可以同时打开100个线程连接服务器。

 

3、多线程下载的实现过程

1)得到服务器下载文件的大小,然后在本地设置一个临时文件,服务的文件大小一致

      fileSize=11;

2)开启线程的数量,每个线程下载的大小-开始位置与结束位置

      threadNum=3;

      threadSize=fileSize/threadNum=3;

      startIndex=(threadNum-1)*threadSize;

      endIndex=threadNum*threadSize-1;

3)怎么设置开始线程的位置

      raf.seek指定的位置去操作。

 

4、多线程下载的分析

4.1在客户端创建出来一个文件,这个文件的大小和服务器上文件大小完全相同

     注意:content-length代表的就是文件的大小

                 RandomAccessFile指定文件的大小

 

4.2开辟线程数量(每个线程下载的大小,每个线程下载的开始位置和结束位置)

     第一个线程: (1-1)*threadSize-1

     第二个线程:(2-1*threadSize-1

     依次:(n-1*threadSize-1

     注意:每个线程下载的数据需要放置到对应的文件块里面。

                randomaccessfile.seek()指定文件的写操作从那个位置开始。

 

5、多线程下载的实现

5.1搭建布局

在相对布局中实现,请仔细阅读:

<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:text="@string/tv_fileName" /> 
         
    <EditText android:id="@+id/et_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:inputType="textUri"
        android:text="@string/down_url"/>
    
    <ProgressBar
        android:id="@+id/pb_down"
        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/et_url" />
    
    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_below="@+id/pb_down"
        android:onClick="downLoadFile"
        android:text="@string/down_btn" />

    <TextView
        android:id="@+id/tv_pb"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/button1"
        android:layout_alignParentLeft="true"
        android:text="@string/tip_pb" />

</RelativeLayout>

 

5.2添加访问网络的权限

<uses-permissionandroid:name="android.permission.INTERNET"/>

<uses-permissionandroid:name="android.permission.MOUNT_FORMAT_FILESYSTEMS"/>

<uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

 

5.3根据Button控件中的onClick属性实现downLoadFile()方法,下载文件,判断文件的大小,请仔细阅读:

 

package www.youkuaiyun.com.activity;

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.youkuaiyun.com.utils.StreamTools;
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 DownloadActivity extends Activity {

	// 线程开启的数量
	private int threadNum = 3;
	private int threadRunning = 3;// 正在运行的线程

	private EditText et_url;
	private ProgressBar progressBar;
	private TextView tv_pb;

	private int currentProgress;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_download);
		et_url = (EditText) findViewById(R.id.et_url);
		progressBar = (ProgressBar) findViewById(R.id.pb_down);
		tv_pb = (TextView) findViewById(R.id.tv_pb);
		
		File sdDir=Environment.getExternalStorageDirectory();
		File pbFile=new File(sdDir,"pb.txt");
		InputStream is=null;
		try {
			if(pbFile.exists()){
				is = new FileInputStream(pbFile);
			}
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		if(is!=null){
			String value=StreamTools.streamToStr(is);
			String arr[]=value.split(";");
			progressBar.setMax(Integer.valueOf(arr[0]));//最大值
			currentProgress=Integer.valueOf(arr[1]);//当前值
			progressBar.setProgress(currentProgress);
			//String percent=arr[1];
			tv_pb.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() {
				public void run() {
					// 访问网络的地址
					// String spec =
					// "http://172.16.237.124:8080/video/3GTan.png";
					try {
						// 根据下载的地址,构建URL对象
						URL url = new URL(spec);
						// 通过URL对象的openConnection()方法打开链接,返回一个链接对象
						HttpURLConnection httpURLConnection = (HttpURLConnection) url
								.openConnection();
						// 设置请求的头
						httpURLConnection.setRequestMethod("GET");
						httpURLConnection.setConnectTimeout(5000);
						httpURLConnection.setReadTimeout(5000);
						// 判断是否响应成功
						if (httpURLConnection.getResponseCode() == 200) {
							int fileLength = httpURLConnection
									.getContentLength();
							// 设置进度条的最大值
							progressBar.setMax(fileLength);
							if (Environment.getExternalStorageState()
									.equals(Environment.MEDIA_MOUNTED)) {

								// System.out.println("文件的大小:" + fileLength);
								// 保存文件
								// String path = "D:\\android.avi";

								File sdFile = Environment
										.getExternalStorageDirectory();
								String fileName = spec.substring(spec
										.lastIndexOf("/") + 1);
								File file = new File(sdFile, fileName);

								// mode模型:r rw rws rwd
								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 - 1;//
									}
									System.out.println("当前线程---" + threadId
											+ "---开始位置:" + startIndex
											+ "---结束位置:" + endIndex
											+ "---每个线程的大小:" + threadSize);
									// 开启线程下载调用start()
									new DownLoadThread(threadId, startIndex,
											endIndex, spec).start();
								}
							} else {
								DownloadActivity.this
										.runOnUiThread(new Runnable() {

											@Override
											public void run() {
												Toast.makeText(
														DownloadActivity.this,
														"SDCard不可用",
														Toast.LENGTH_LONG)
														.show();
											}
										});
							}

						} else {
							DownloadActivity.this.runOnUiThread(new Runnable() {

								@Override
								public void run() {
									Toast.makeText(DownloadActivity.this,
											"服务器端返回错误", Toast.LENGTH_LONG)
											.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;

		// 构造器
		public DownLoadThread(int threadId, int startIndex, int endIndex,
				String path) {
			super();
			this.threadId = threadId;// 线程序号
			this.startIndex = startIndex;
			this.endIndex = endIndex;
			this.path = path;// 线程下载路径
		}

		@Override
		public void run() {
			// 可以通过每个线程去下载文件
			try {

				File sdFile = Environment.getExternalStorageDirectory();

				// 首先要从本地文件上读取已经下载的文件开始位置
				File recordFile = new File(sdFile, threadId + ".txt");
				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对象openConnection()打开链接对象,返回HttpURLConnection对象
				HttpURLConnection httpURLConnection = (HttpURLConnection) url
						.openConnection();
				// 设置请求的头
				httpURLConnection.setRequestMethod("GET");
				httpURLConnection.setConnectTimeout(5000);
				// 设置你下载文件的开始位置与结束位置
				httpURLConnection.setRequestProperty("Range", "bytes="
						+ startIndex + "-" + endIndex);
				// 获取的状态码
				int code = httpURLConnection.getResponseCode();
				// System.out.println(code);
				// 判断是否成功
				if (code == 206) {
					// 获取每个线程返回的流对象
					InputStream is = httpURLConnection.getInputStream();
					// 定义写入文件的路径
					// String path = "D:\\android.avi";
					String fileName = path.substring(path.lastIndexOf("/") + 1);
					// 根据路径创建文件
					File file = new File(sdFile, fileName);
					// 根据路径创建它的RandomAccessFile对象
					RandomAccessFile raf = new RandomAccessFile(file, "rwd");
					raf.seek(startIndex);
					// 定义读取的长度
					int length = 0;
					// 定义缓冲区
					byte buffer[] = new byte[1];
					int total = 0;
					// 循环读取
					while ((length = is.read(buffer)) != -1) {

						// System.out.println("当前线程--"+threadId+"---已经下载的大小是:"+total);
						// System.out.println("当前线程--"+threadId+"---当前下载的位置是:"+(startIndex+total));//断点下载?
						RandomAccessFile threadfile = new RandomAccessFile(
								new File(sdFile, threadId + ".txt"), "rwd");
						threadfile.writeBytes((startIndex + total) + "");
						threadfile.close();
						raf.write(buffer, 0, length);// raf.seek(startIndex);已经定位了
						total += length;// 已经下载的大小
						//解决同步的问题
						synchronized (DownloadActivity.this) {
							currentProgress += length;
							progressBar.setProgress(currentProgress);
							//100l中l为long
							final String percent=currentProgress * 100l/ progressBar.getMax()+"%";
							DownloadActivity.this.runOnUiThread(new Runnable() {

								@Override
								public void run() {
									tv_pb.setText("当前的进度是:" + percent);
								}
							});

							RandomAccessFile pbfile=new RandomAccessFile(new File(sdFile,"pb.txt"), "rwd");
							pbfile.writeBytes(progressBar.getMax()+";"+currentProgress+";"+percent);
							pbfile.close();
						}
						
					}
					raf.close();
					is.close();
					// System.out.println("当前线程---" + threadId + "---下载完毕");
					runOnUiThread(new Runnable() {

						@Override
						public void run() {
							Toast.makeText(DownloadActivity.this,
									"当前线程---" + threadId + "---下载完毕",
									Toast.LENGTH_LONG).show();
						}
					});
					deleteRecordFiles();
				} else {
					runOnUiThread(new Runnable() {

						@Override
						public void run() {
							Toast.makeText(DownloadActivity.this, "服务器端下载错误",
									Toast.LENGTH_LONG).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 = 0; i <= 3; i++) {
				File recordFile = new File(sdFile, i + ".txt");
				if (recordFile.exists()) {
					recordFile.delete();
				}
				//删除pb.txt文件
				File pbFile=new File(sdFile,"pb.txt");
				if(pbFile.exists()){
					pbFile.delete();
				}
			}
		}
	}

}


5.4最后的结果为:

 

 

6、容易出现的bug说明

1)进度条变化的太快,没有按照1%的递增进行

解决://定义缓冲区

  byte buffer[] =newbyte[1024*1024];

  可以把1024*1024改的小一些,如1024

2)进度条在下载过程中出现负的百分比

解决:String percent=currentProgress * 100/ progressBar.getMax()+"";

      可以把100改成100l,其中l表示long

3同步的问题

解决:

synchronized (DownloadActivity.this) {
							currentProgress += length;
							progressBar.setProgress(currentProgress);
							//100l中l为long
							final String percent=currentProgress * 100l/ progressBar.getMax()+"%";
							DownloadActivity.this.runOnUiThread(new Runnable() {

								@Override
								public void run() {
									tv_pb.setText("当前的进度是:" + percent);
								}
							});

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


4)中断下载之后能否继续下载文件的问题

解决:

File sdDir=Environment.getExternalStorageDirectory();
		File pbFile=new File(sdDir,"pb.txt");
		InputStream is=null;
		try {
			if(pbFile.exists()){
				is = new FileInputStream(pbFile);
			}
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		if(is!=null){
			String value=StreamTools.streamToStr(is);
			String arr[]=value.split(";");
			progressBar.setMax(Integer.valueOf(arr[0]));//最大值
			currentProgress=Integer.valueOf(arr[1]);//当前值
			progressBar.setProgress(currentProgress);
			//String percent=arr[1];
			tv_pb.setText("当前进度是:"+arr[2]);//显示进度条 百分比
		}


                 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值