Android开发--多线程下载加断点续传

标签: android 开发 多线程 | 发表时间:2014-12-30 19:34 | 作者:smbroe
出处:http://blog.csdn.net

        文件下载在App应用中也用到很多,一般版本更新时多要用的文件下载来进行处理,以前也有看过很多大神有过该方面的博客,今天我也自己来实践一下,写的一般,还请大家多提意见,共同进步。主要思路:

        1.多线程下载:

               首先通过下载总线程数来划分文件的下载区域:利用int range = fileSize / threadCount;得到每一段下载量;每一段的位置是i * range到(i + 1) * rang  - 1,注意最后一段的位置是到filesize - 1;

               通过Http协议的Range字段实现下载文件的分段;

               通过Java类RandomAccessFile可以实现文件的随机访问,利用seek方法定位的文件的指定位置;

               由HttpUrlConnection获取流来进行流的读写,实现文件的存储;

               在下载过程中利用Handler来向外传递下载的信息。

         2.断点续传:

               对于每一个线程利用一个DownloadInfo类来保存下载的信息,每次在下载过程中向数据库更新信息(我也有想过只在下载暂停时进行更新,但那样的话我们的进程被杀掉时信息就无法保存下来)。在进行下载之前去访问数据库是否有记录存在,如果没有执行第一次下载的初始化,如果存在记录但下载文件不存在时,删掉数据库中的记录之后进行第一次下载的初始化,如果有记录且文件存在,则从数据库中取出信息。

         实现的效果如图,自己封装的类提供了开始,暂停,删除,以及重新下载的方法。还没来得及将工程穿上CSDN,给大家一个百度云盘的下载地址:http://pan.baidu.com/s/1dD1Xo8T 

                                                                                                             

             主要类的结构有:

                     1)DownloadHttpTool:利用Http协议进行网络下载类

                     2)DownlaodSqlTool, DownLoadHelper:数据库相关操作类

                     3)DownloadInfo:下载信息保存类

                     4)DownloadUtil:封装下载方法并提供对外接口的类

 贴一下代码:DownloadInfo类

package com.example.test;

public class DownloadInfo {
	/**
	 * 保存每个下载线程下载信息类
	 * 
	 */
	
	private int threadId;// 下载器id
	private int startPos;// 开始点
	private int endPos;// 结束点
	private int compeleteSize;// 完成度
	private String url;// 下载文件的URL地址
	
	public DownloadInfo(int threadId, int startPos, int endPos,
			int compeleteSize, String url) {
		this.threadId = threadId;
		this.startPos = startPos;
		this.endPos = endPos;
		this.compeleteSize = compeleteSize;
		this.url = url;
	}

	public DownloadInfo() {
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public int getThreadId() {
		return threadId;
	}

	public void setThreadId(int threadId) {
		this.threadId = threadId;
	}

	public int getStartPos() {
		return startPos;
	}

	public void setStartPos(int startPos) {
		this.startPos = startPos;
	}

	public int getEndPos() {
		return endPos;
	}

	public void setEndPos(int endPos) {
		this.endPos = endPos;
	}

	public int getCompeleteSize() {
		return compeleteSize;
	}

	public void setCompeleteSize(int compeleteSize) {
		this.compeleteSize = compeleteSize;
	}

	@Override
	public String toString() {
		return "DownloadInfo [threadId=" + threadId + ", startPos=" + startPos
				+ ", endPos=" + endPos + ", compeleteSize=" + compeleteSize
				+ "]";
	}
}
DownLoadHelper类,创建我们的数据库文件
package com.example.test;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

/**
 * 利用数据库来记录下载信息
 * @author acer
 */
public class DownLoadHelper extends SQLiteOpenHelper{

	private static final String SQL_NAME = "download.db";
	private static final int DOWNLOAD_VERSION=1;
	
	public DownLoadHelper(Context context) {
		super(context, SQL_NAME, null, DOWNLOAD_VERSION);
		// TODO Auto-generated constructor stub
	}
	
	 /**
     * 在download.db数据库下创建一个download_info表存储下载信息
     */
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("create table download_info(_id integer PRIMARY KEY AUTOINCREMENT, thread_id integer, "
                + "start_pos integer, end_pos integer, compelete_size integer,url char)");
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

}
DownlaodSqlTool进行数据的插入更新删除等操作
package com.example.test;

import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

/**
 * 
 * 数据库操作工具类
 */
public class DownlaodSqlTool {
    private DownLoadHelper dbHelper;

    public DownlaodSqlTool(Context context) {
        dbHelper = new DownLoadHelper(context);
    }

    /**
     * 创建下载的具体信息
     */
    public void insertInfos(List<DownloadInfo> infos) {
        SQLiteDatabase database = dbHelper.getWritableDatabase();
        for (DownloadInfo info : infos) {
            String sql = "insert into download_info(thread_id,start_pos, end_pos,compelete_size,url) values (?,?,?,?,?)";
            Object[] bindArgs = { info.getThreadId(), info.getStartPos(),
                    info.getEndPos(), info.getCompeleteSize(), info.getUrl() };
            database.execSQL(sql, bindArgs);
        }
    }

    /**
     * 得到下载具体信息
     */
    public List<DownloadInfo> getInfos(String urlstr) {
        List<DownloadInfo> list = new ArrayList<DownloadInfo>();
        SQLiteDatabase database = dbHelper.getWritableDatabase();
        String sql = "select thread_id, start_pos, end_pos,compelete_size,url from download_info where url=?";
        Cursor cursor = database.rawQuery(sql, new String[] { urlstr });
        while (cursor.moveToNext()) {
            DownloadInfo info = new DownloadInfo(cursor.getInt(0),
                    cursor.getInt(1), cursor.getInt(2), cursor.getInt(3),
                    cursor.getString(4));
            list.add(info);
        }
        return list;
    }

    /**
     * 更新数据库中的下载信息
     */
    public void updataInfos(int threadId, int compeleteSize, String urlstr) {
        SQLiteDatabase database = dbHelper.getWritableDatabase();
        String sql = "update download_info set compelete_size=? where thread_id=? and url=?";
        Object[] bindArgs = { compeleteSize, threadId, urlstr };
        database.execSQL(sql, bindArgs);
    }
    /**
     * 关闭数据库
     */
    public void closeDb() {
        dbHelper.close();
    }

    /**
     * 下载完成后删除数据库中的数据
     */
    public void delete(String url) {
        SQLiteDatabase database = dbHelper.getWritableDatabase();
        database.delete("download_info", "url=?", new String[] { url });
    }
}
DownloadHttpTool进行网络下载的类
package com.example.test;

import java.io.File;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

public class DownloadHttpTool {
	/**
	 * 
	 * 利用Http协议进行多线程下载具体实践类
	 */

	private static final String TAG = DownloadHttpTool.class.getSimpleName();

	private int threadCount;//线程数量
	private String urlstr;//URL地址
	private Context mContext;
	private Handler mHandler;
	private List<DownloadInfo> downloadInfos;//保存下载信息的类

	private String localPath;//目录
	private String fileName;//文件名
	private int fileSize;
	private DownlaodSqlTool sqlTool;//文件信息保存的数据库操作类

	private enum Download_State {
		Downloading, Pause, Ready;//利用枚举表示下载的三种状态
	}

	private Download_State state = Download_State.Ready;//当前下载状态

	private int globalCompelete = 0;//所有线程下载的总数

	public DownloadHttpTool(int threadCount, String urlString,
			String localPath, String fileName, Context context, Handler handler) {
		super();
		this.threadCount = threadCount;
		this.urlstr = urlString;
		this.localPath = localPath;
		this.mContext = context;
		this.mHandler = handler;
		this.fileName = fileName;
		sqlTool = new DownlaodSqlTool(mContext);
	}

	//在开始下载之前需要调用ready方法进行配置
	public void ready() {
		Log.w(TAG, "ready");
		globalCompelete = 0;
		downloadInfos = sqlTool.getInfos(urlstr);
		if (downloadInfos.size() == 0) {
			initFirst();
		} else {
			File file = new File(localPath + "/" + fileName);
			if (!file.exists()) {
				sqlTool.delete(urlstr);
				initFirst();
			} else {
				fileSize = downloadInfos.get(downloadInfos.size() - 1)
						.getEndPos();
				for (DownloadInfo info : downloadInfos) {
					globalCompelete += info.getCompeleteSize();
				}
				Log.w(TAG, "globalCompelete:::" + globalCompelete);
			}
		}
	}

	public void start() {
		Log.w(TAG, "start");
		if (downloadInfos != null) {
			if (state == Download_State.Downloading) {
				return;
			}
			state = Download_State.Downloading;
			for (DownloadInfo info : downloadInfos) {
				Log.v(TAG, "startThread");
				new DownloadThread(info.getThreadId(), info.getStartPos(),
						info.getEndPos(), info.getCompeleteSize(),
						info.getUrl()).start();
			}
		}
	}

	public void pause() {
		state = Download_State.Pause;
		sqlTool.closeDb();
	}
	
	public void delete(){
		compelete();
		File file = new File(localPath + "/" + fileName);
		file.delete();
	}
	
	public void compelete() {
		sqlTool.delete(urlstr);
		sqlTool.closeDb();
	}

	public int getFileSize() {
		return fileSize;
	}

	public int getCompeleteSize() {
		return globalCompelete;
	}

	//第一次下载初始化
	private void initFirst() {
		Log.w(TAG, "initFirst");
		try {
			URL url = new URL(urlstr);
			HttpURLConnection connection = (HttpURLConnection) url
					.openConnection();
			connection.setConnectTimeout(5000);
			connection.setRequestMethod("GET");
			fileSize = connection.getContentLength();
			Log.w(TAG, "fileSize::" + fileSize);
			File fileParent = new File(localPath);
			if (!fileParent.exists()) {
				fileParent.mkdir();
			}
			File file = new File(fileParent, fileName);
			if (!file.exists()) {
				file.createNewFile();
			}
			// 本地访问文件
			RandomAccessFile accessFile = new RandomAccessFile(file, "rwd");
			accessFile.setLength(fileSize);
			accessFile.close();
			connection.disconnect();
		} catch (Exception e) {
			e.printStackTrace();
		}
		int range = fileSize / threadCount;
		downloadInfos = new ArrayList<DownloadInfo>();
		for (int i = 0; i < threadCount - 1; i++) {
			DownloadInfo info = new DownloadInfo(i, i * range, (i + 1) * range
					- 1, 0, urlstr);
			downloadInfos.add(info);
		}
		DownloadInfo info = new DownloadInfo(threadCount - 1, (threadCount - 1)
				* range, fileSize - 1, 0, urlstr);
		downloadInfos.add(info);
		sqlTool.insertInfos(downloadInfos);
	}

	//自定义下载线程
	private class DownloadThread extends Thread {

		private int threadId;
		private int startPos;
		private int endPos;
		private int compeleteSize;
		private String urlstr;
		private int totalThreadSize;

		public DownloadThread(int threadId, int startPos, int endPos,
				int compeleteSize, String urlstr) {
			this.threadId = threadId;
			this.startPos = startPos;
			this.endPos = endPos;
			totalThreadSize = endPos - startPos + 1;
			this.urlstr = urlstr;
			this.compeleteSize = compeleteSize;
		}

		@Override
		public void run() {
			HttpURLConnection connection = null;
			RandomAccessFile randomAccessFile = null;
			InputStream is = null;
			try {
				randomAccessFile = new RandomAccessFile(localPath + "/"
						+ fileName, "rwd");
				randomAccessFile.seek(startPos + compeleteSize);
				URL url = new URL(urlstr);
				connection = (HttpURLConnection) url.openConnection();
				connection.setConnectTimeout(5000);
				connection.setRequestMethod("GET");
				connection.setRequestProperty("Range", "bytes="
						+ (startPos + compeleteSize) + "-" + endPos);
				is = connection.getInputStream();
				byte[] buffer = new byte[1024];
				int length = -1;
				while ((length = is.read(buffer)) != -1) {
					randomAccessFile.write(buffer, 0, length);
					compeleteSize += length;
					Message message = Message.obtain();
					message.what = threadId;
					message.obj = urlstr;
					message.arg1 = length;
					mHandler.sendMessage(message);
					sqlTool.updataInfos(threadId, compeleteSize, urlstr);
					Log.w(TAG, "Threadid::" + threadId + "    compelete::"
							+ compeleteSize + "    total::" + totalThreadSize);
					if (compeleteSize >= totalThreadSize) {
						break;
					}
					if (state != Download_State.Downloading) {
						break;
					}
				}

			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				try {
					if (is != null) {
						is.close();
					}
					randomAccessFile.close();
					connection.disconnect();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}
}
DownloadUtils提供下载向外接口方法类:
package com.example.test;

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

/**
 * 将下载方法封装在此类
 * 提供下载,暂停,删除,以及重置的方法
 */
public class DownloadUtil {

	private DownloadHttpTool mDownloadHttpTool;
	private OnDownloadListener onDownloadListener;

	private int fileSize;
	private int downloadedSize = 0;

	@SuppressLint("HandlerLeak")
	private Handler mHandler = new Handler() {

		@Override
		public void handleMessage(Message msg) {
			// TODO Auto-generated method stub
			super.handleMessage(msg);
			int length = msg.arg1;
			synchronized (this) {//加锁保证已下载的正确性
				downloadedSize += length;
			}
			if (onDownloadListener != null) {
				onDownloadListener.downloadProgress(downloadedSize);
			}
			if (downloadedSize >= fileSize) {
				mDownloadHttpTool.compelete();
				if (onDownloadListener != null) {
					onDownloadListener.downloadEnd();
				}
			}
		}

	};

	public DownloadUtil(int threadCount, String filePath, String filename,
			String urlString, Context context) {

		mDownloadHttpTool = new DownloadHttpTool(threadCount, urlString,
				filePath, filename, context, mHandler);
	}

	//下载之前首先异步线程调用ready方法获得文件大小信息,之后调用开始方法
	public void start() {
		new AsyncTask<Void, Void, Void>() {

			@Override
			protected Void doInBackground(Void... arg0) {
				// TODO Auto-generated method stub
				mDownloadHttpTool.ready();
				return null;
			}

			@Override
			protected void onPostExecute(Void result) {
				// TODO Auto-generated method stub
				super.onPostExecute(result);
				fileSize = mDownloadHttpTool.getFileSize();
				downloadedSize = mDownloadHttpTool.getCompeleteSize();
				Log.w("Tag", "downloadedSize::" + downloadedSize);
				if (onDownloadListener != null) {
					onDownloadListener.downloadStart(fileSize);
				}
				mDownloadHttpTool.start();
			}
		}.execute();
	}

	public void pause() {
		mDownloadHttpTool.pause();
	}
	
	public void delete(){
		mDownloadHttpTool.delete();
	}

	public void reset(){
		mDownloadHttpTool.delete();
		start();
	}
	
	public void setOnDownloadListener(OnDownloadListener onDownloadListener) {
		this.onDownloadListener = onDownloadListener;
	}

	//下载回调接口
	public interface OnDownloadListener {
		public void downloadStart(int fileSize);

		public void downloadProgress(int downloadedSize);//记录当前所有线程下总和

		public void downloadEnd();
	}
}
在MainActivity
package com.example.test;

import android.os.Bundle;
import android.os.Environment;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.example.test.DownloadUtil.OnDownloadListener;

public class MainActivity extends FragmentActivity {

	private static final String TAG = MainActivity.class.getSimpleName();

	private ProgressBar mProgressBar;
	private Button start;
	private Button pause;
	private Button delete;
	private Button reset;
	private TextView total;

	private int max;

	private DownloadUtil mDownloadUtil;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mProgressBar = (ProgressBar) findViewById(R.id.progressBar1);
		start = (Button) findViewById(R.id.button_start);
		pause = (Button) findViewById(R.id.button_pause);
		delete = (Button) findViewById(R.id.button_delete);
		reset = (Button) findViewById(R.id.button_reset);
		total = (TextView) findViewById(R.id.textView_total);
		String urlString = "http://bbra.cn/Uploadfiles/imgs/20110303/fengjin/013.jpg";
		String localPath = Environment.getExternalStorageDirectory()
				.getAbsolutePath() + "/local";
		mDownloadUtil = new DownloadUtil(2, localPath, "abc.jpg", urlString,
				this);
		mDownloadUtil.setOnDownloadListener(new OnDownloadListener() {

			@Override
			public void downloadStart(int fileSize) {
				// TODO Auto-generated method stub
				Log.w(TAG, "fileSize::" + fileSize);
				max = fileSize;
				mProgressBar.setMax(fileSize);
			}

			@Override
			public void downloadProgress(int downloadedSize) {
				// TODO Auto-generated method stub
				Log.w(TAG, "Compelete::" + downloadedSize);
				mProgressBar.setProgress(downloadedSize);
				total.setText((int) downloadedSize * 100 / max + "%");
			}

			@Override
			public void downloadEnd() {
				// TODO Auto-generated method stub
				Log.w(TAG, "ENd");
			}
		});
		start.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View arg0) {
				// TODO Auto-generated method stub
				mDownloadUtil.start();
			}
		});
		pause.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View arg0) {
				// TODO Auto-generated method stub
				mDownloadUtil.pause();
			}
		});
		delete.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View arg0) {
				// TODO Auto-generated method stub
				mDownloadUtil.delete();
			}
		});
		reset.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View arg0) {
				// TODO Auto-generated method stub
				mDownloadUtil.reset();
			}
		});
	}

}






作者:smbroe 发表于2014-12-30 19:34:04 原文链接
阅读:0 评论:0 查看评论

相关 [android 开发 多线程] 推荐:

Android开发--多线程下载加断点续传

- - CSDN博客推荐文章
        文件下载在App应用中也用到很多,一般版本更新时多要用的文件下载来进行处理,以前也有看过很多大神有过该方面的博客,今天我也自己来实践一下,写的一般,还请大家多提意见,共同进步.         1.多线程下载:.                首先通过下载总线程数来划分文件的下载区域:利用int range = fileSize / threadCount;得到每一段下载量;每一段的位置是i * range到(i + 1) * rang  - 1,注意最后一段的位置是到filesize - 1;.

Android实战技巧:多线程AsyncTask

- - CSDN博客推荐文章
AsyncTask是Android 1.5 Cubake加入的用于实现异步操作的一个类,在此之前只能用Java SE库中的Thread来实现多线程异步,AsyncTask是Android平台自己的异步工具,融入了Android平台的特性,让异步操作更加的安全,方便和实用. 实质上它也是对Java SE库中Thread的一个封装,加上了平台相关的特性,所以对于所有的多线程异步都强烈推荐使用AsyncTask,因为它考虑,也融入了Android平台的特性,更加的安全和高效.

Android多任务多线程下载

- - 移动开发 - ITeye博客
关注微信号:javalearns   随时随地学Java. 打算实现一个下载功能,当然理想的功能要支持多任务下载、多线程下载、断点续传的功能,我想一步一步来,首先困难摆在了多任务这里. 开始的思路是在一个Service中启动下载的流操作,然后通过Service中声明一个Activity中的Handler更新UI(比如进度条.

Android开发Tips

- - CSDN博客推荐文章
欢迎Follow我的 GitHub, 关注我的 CSDN.. 介绍一些, 在Android开发中, 会经常使用的小知识点.. submodule与git可以保持实时同步. 导入, 路径多于一个, 前面不添加冒号(:).. 使用PackageManager.. // 检查App是否安装 private boolean appInstalledOrNot(String uri) {.

Android通过HTTP协议实现多线程下载

- - 移动开发 - ITeye博客
     * 从路径中获取文件名称 .      * @param path 下载路径 .      * 下载文件 .      * @param path 下载路径 .      * @param threadsize 线程数 .         int filelength = conn.getContentLength();//获取要下载的文件的长度  .

Android 开发者调查

- - 爱范儿 · Beats of Bits
Startup 是为 Android 开发者提供盈利模式的一个公司. Android 开发者只要在应用上推广 Startup 服务,并且为网站带去流量,就可以得到网站给予的补贴. 今年 3 月的时候,Startup 网站对 Android 开发者进行了一次调查. 现在,他们将调查的结果制成了信息图,并 发布在网站之上.

Android敏捷开发指南

- - 互联网的那点事
本文紧密结合移动开发方法与技术,围绕Android平台的开发探讨提供更高质量移动产品的解决方案. 作者中分析了移动开发中常见的问题,从两方面阐述了ThoughtWorks使用的测试开发方案和相应的架构方法与常用工具应用,并进一步阐述了为移动开发流程所提供的持续发布方案. 随着云计算、移动互联等一系列新技术概念的崛起,新一轮的IT经济正在不断扩大发展.

Android应用开发资源

- - InfoQ cn
Android应用设计和开发人员现在可以参考由Android用户体验(UX)团队官方发布的 Android设计指南. 该指南提供了开发者应该遵循的基本原则,并列出了很多细节指导,涉及 设备与显示、 主题、 触控交互、 度量与栅格、 排版、 色彩、 图标设计,以及如何 编写用户交互界面的提示语.

Android 开发视频推荐

- - 大猫の意淫筆記
作为 Android 死忠粉,总是想自己来两手,无奈没摸过后台开发,面向的唯一对象就是马总. 花了个把星期入个门,发现还是挺好玩的. 市面上的 Android 入门书有2个主要问题,一个是都要求有 Java 基础,另一个是跟不上 Android 版本更新.所以并不推荐买书学习. 斯坦福大学公开课:编程方法学.

Android 开发者在 Android Market 上兜售应用

- SotongDJ - 谷安——谷奥Android专题站
无论你是 Android 用户还是开发者,我们相信你会同意我们所说的一个事实:Android Market 还远未完善,我们不是在谈论它的用户界面或者是如何“开放”,而是另外一个话题. CatNinjaFly,或者是 SuperNinjaCat,又或者是 NinjaCatFly. 叫什么都不要紧,要紧的是我们关心的这个问题,要紧的是它们都是来自同一开发者的完全是相同的游戏,很显然,开发者(商)NANJAANDROID 为了让他们的应用能够在 Android Market 多一些曝光率于是用不同的名称来在 Android Market 中兜售.