Android多线程文件下载器

Android多线程文件下载器

Android多线程文件下载器

Android多线程文件下载器

本应用实现的是输入文件的网络的地址,点击按钮开始下载,下载过程中有进度条和后面的文本提示进度,

下载过程中按钮不可点击,防止重复的下载,下载完毕后会进行Toast的提示显示,

并且回复按钮的可点击性,进度条也会清空,当然如果下载中途结束应用进程就会进行进度的保存,

下次下载同样的文件时就会从进度记录进行下载,节省流量和时间

应用需要的应用权限:

访问网络权限

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

外部储存的写入权限

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

布局文件代码

<LinearLayout 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"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <EditText
        android:id="@+id/et_path"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ems="10"
        android:hint="请输入下载文件的地址"
        android:singleLine="true"
        android:text="http://172.22.64.193:8080/test.exe" >
    </EditText>

    <TableLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="wrap_content" >

            <ProgressBar
                android:id="@+id/pb"
                style="?android:attr/progressBarStyleHorizontal"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_weight="8" />

            <TextView
                android:id="@+id/tv_progressNumber"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_weight="2"
                android:gravity="center"
                android:text="0%" />
        </TableRow>
    </TableLayout>

    <Button
        android:id="@+id/bt_startDownlode"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="startDownlode"
        android:text="开始下载" />

</LinearLayout>

核心代码

package com.examp.mutildownloader;

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.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

/**
 * Android下的多线程下载,断点下载
 * 
 * @author MartinDong
 * 
 */
public class MainActivity extends Activity {

	// sd卡的路径
	private static final File sd = Environment.getExternalStorageDirectory();
	private static final int DOWNLODE_ERROR = 1;
	private static final int DOWNLODE_SUCCESS = 2;
	private static final int DOWNLODE_DELETE_TEMP = 3;
	public static final int PROGRESS_NUMBER_CHANGE = 4;

	// 定义线程个数
	private static int threadCount = 3;
	// 定义当前存货的线程个数
	private static int runningThread = 3;
	// 组件的获取
	private EditText et_path;
	private ProgressBar pb;
	private Button bt_startDownlode;
	private TextView tv_progressNumber;
	// 定义存储的文件名称
	private String filename;
	// 设置进度条的进度
	// 进度条的数据
	public int progressTemp = 0;

	// 定义消息处理
	private Handler handler = new Handler() {
		@Override
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case DOWNLODE_ERROR:
				Toast.makeText(getApplicationContext(), "下载失败....",
						Toast.LENGTH_SHORT).show();
				break;
			case DOWNLODE_SUCCESS:
				// 设置按钮可用
				bt_startDownlode.setEnabled(true);
				// 清空进度
				progressTemp = 0;
				pb.setProgress(progressTemp);
				// 文本清0
				tv_progressNumber.setText("0%");
				Toast.makeText(getApplicationContext(), "下载成功....",
						Toast.LENGTH_SHORT).show();
				break;
			case DOWNLODE_DELETE_TEMP:
				Toast.makeText(getApplicationContext(), "删除进度文件....",
						Toast.LENGTH_SHORT).show();
				break;
			case PROGRESS_NUMBER_CHANGE:
				tv_progressNumber.setText(pb.getProgress() * 100 / pb.getMax()
						+ "%");
				break;
			}
		}

	};

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

		et_path = (EditText) findViewById(R.id.et_path);
		// 获取组件
		pb = (ProgressBar) findViewById(R.id.pb);
		bt_startDownlode = (Button) findViewById(R.id.bt_startDownlode);
		tv_progressNumber = (TextView) findViewById(R.id.tv_progressNumber);

	}

	public void startDownlode(View view) {
		// 设置按钮不可点击//防止重复提交
		bt_startDownlode.setEnabled(false);

		// 从控件中获取下载的路径
		final String path = et_path.getText().toString().trim();
		// 获取输入地址的最后一个"/"出现的位置
		int lastIndex = path.lastIndexOf("/");
		// 获取文件名称及格式
		filename = path.substring(lastIndex + 1, path.length());
		System.out.println("文件名称为===============" + filename);

		// 判断路径是否有效
		if (TextUtils.isEmpty(path)) {
			Toast.makeText(this, "请输入有效的下载路径......", Toast.LENGTH_SHORT).show();
			return;
		}
		// 为了避免与主线程冲突,开启子线程完成,避免anr问题
		new Thread() {
			public void run() {
				try {
					// 1,连接到服务器,获取一个文件,获取文件的大小跟服务器的文件一样的临时文件
					// String path = "http://172.22.64.193:8080/tomcat.css";
					URL url = new URL(path);
					HttpURLConnection conn = (HttpURLConnection) url
							.openConnection();
					// 设置超时
					conn.setConnectTimeout(5000);
					// 设置请求方式
					conn.setRequestMethod("GET");
					// 获取服务器的返回码
					int code = conn.getResponseCode();
					// 判断返回码
					if (code == 200) {
						// 获取返回的长度
						int length = conn.getContentLength();

						// 设置进度条的最大值
						pb.setMax(length);

						System.out.println("文件总长度:" + length);

						// 在客户端创建出一个跟服务器大小一致的临时文件
						RandomAccessFile raf = new RandomAccessFile(sd + "/"
								+ filename, "rwd");
						// 指定临时文件的大小
						raf.setLength(length);
						// 释放资源
						raf.close();

						// 平均每一个线程的文件大小
						int blockSize = length / threadCount;

						// 设置活跃的线程
						runningThread = 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 DownlodeThread(path, threadId, startIndex,
									endIndex).start();
						}
					}
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}

			};
		}.start();

	}

	/**
	 * 下载文件的子线程类,每一个线程下载对应位置文件数据
	 * 
	 * @author MartinDong
	 * 
	 */
	public class DownlodeThread extends Thread {

		private String path;
		private int threadId;
		private int startIndex;
		private int endIndex;

		/**
		 * 
		 * @param path
		 *            文件的下载路径
		 * @param threadId
		 *            线程id
		 * @param startIndex
		 *            线程开始的位置
		 * @param endIndex
		 *            线程结束的位置
		 */
		public DownlodeThread(String path, int threadId, int startIndex,
				int endIndex) {
			this.path = path;
			this.threadId = threadId;
			this.startIndex = startIndex;
			this.endIndex = endIndex;
		}

		@Override
		public void run() {
			try {
				// 检查是否存在下载历史的文件
				File tempFile = new File(sd + "/" + threadId + ".txt");// =========================断点记录操作===============================
				if (tempFile.exists() && tempFile.length() > 0) {
					// 文件输入流
					FileInputStream fis = new FileInputStream(tempFile);
					// 中间变量,缓存的作用
					byte[] tempBuffer = new byte[1024];
					// 获取进度文件的数据大小
					int length = fis.read(tempBuffer);
					// 获取进度文件的数据
					String historyData = new String(tempBuffer, 0, length);
					// 将进度数据装换为整型
					int historyDataInt = Integer.parseInt(historyData);
					// 如果是断点传送,初始化进度条的进度
					// 获取每个线程已经下载了的进度
					int temp = historyDataInt - startIndex;
					// 为进度重新赋值
					progressTemp += temp;

					// 修改真正的下载位置
					startIndex = historyDataInt;

					fis.close();
				}// =========================断点记录操作===============================

				// 将地址转换为URL
				URL url = new URL(path);
				// 获取http连接
				HttpURLConnection conn = (HttpURLConnection) url
						.openConnection();
				// 设置连接的请求方式
				conn.setRequestMethod("GET");
				// 重要:请求服务器下载部分的文件,指定文件的位置
				conn.setRequestProperty("Range", "bytes=" + startIndex + "-"
						+ endIndex);

				System.out
						.println("线程:" + threadId + "真实开始的下载进度:" + startIndex);
				// 设置超时时间
				conn.setReadTimeout(5000);
				// 得到服务器的状态码,200表示请求的全部资源得到响应=== ok,206请求的部分资源得到响应=== ok
				int code = conn.getResponseCode();
				System.out.println("code:" + code);

				if (code == 206) {
					// 返回的是指定位置的文件流
					InputStream is = conn.getInputStream();
					// 创建一个临时的文件
					RandomAccessFile raf = new RandomAccessFile(sd + "/"
							+ filename, "rwd");
					// 移动指针,到指定的文件位置,
					raf.seek(startIndex);

					// 创建中间缓冲字节数组
					byte[] buffer = new byte[1024];
					// 读取文件的大小
					int length = 0;

					// 定义已经下载的数据长度,用作断点下载的记录=========================断点记录操作===============================
					int downlodeTotal = 0;

					// 循环写入
					while ((length = is.read(buffer)) != -1) {
						// 定义一个记录线程的记录文件=========================断点记录操作===============================
						RandomAccessFile historyFile = new RandomAccessFile(sd
								+ "/" + threadId + ".txt", "rwd");
						// 向文件中写入数据
						raf.write(buffer, 0, length);
						// 记录已经下载的文件长度
						downlodeTotal += length;
						// 将已经下载的文件长度和开始的读取位置相加,得到已经读取的文件位置
						historyFile.write((downlodeTotal + startIndex + "")
								.getBytes());
						historyFile.close();// =========================断点记录操作===============================

						// 保持同步的更新
						synchronized (MainActivity.this) {
							// 将每个线程的读取文件的大小累加到下载进度上
							progressTemp += length;
							// 更新界面上的Progress的进度条的长度
							pb.setProgress(progressTemp);
							// 特殊情况ProgressBar ProgressDialog
							// 是可以直接在子线程中跟新ui的,内部代码有过特殊的处理
							// 使用Message.obtain();减少Message对象的创建
                                                        Message msg = Message.obtain();
							msg.what = PROGRESS_NUMBER_CHANGE;
							handler.sendMessage(msg);
						}

					}
					is.close();
					raf.close();
					System.out.println("线程:" + threadId + "下载完毕............");

				} else {
					System.out.println("线程:" + threadId
							+ "下载失败请重新下载............");
					// 向消息处理器发送信息
					Message msg = new Message();
					msg.what = DOWNLODE_ERROR;
					handler.sendMessage(msg);
				}
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} finally {
				synchronized (MainActivity.this) {
					// 进行线程数量的变化操作
					runningThread--;
					// 如果当前存活的线程为0,执行进度文件统一销毁的操作
					if (runningThread == 0) {
						// 如果下载完毕,清除进度文件
						for (int threadIndex = 1; threadIndex <= threadCount; threadIndex++) {
							// 这里创建的是与线程文件对应的文件,文件名可以自定义,方便起见是采用的是线程的ID表示
							File temp = new File(sd + "/" + threadIndex
									+ ".txt");
							// 执行文件删除的操作
							temp.delete();
						}
						System.out.println("下载完毕,删除进度文件.............");
						// 向消息处理器发送信息
						// 向消息处理器发送信息
						Message msg = new Message();
						msg.what = DOWNLODE_SUCCESS;
						handler.sendMessage(msg);
					}
				}

			}
		}
	}

}


Demo下载地址:

http://download.csdn.net/detail/u011936142/7428611




Android多线程文件下载器,布布扣,bubuko.com

Android多线程文件下载器

上一篇:Android SDK 更新和下载慢怎么办?


下一篇:Android清理设备内存详细完整示例(二)