- 浏览: 20427 次
最新评论
android中提供多线程下载思路
2010年12月13日
单线程下载HTTP文件对我们来说是一件非常简单的事。那么,多线程断点下载的难点在哪里?1.多线程下载,2.支持断点。
多线程下载:
如何才能从文件的指定位置处开始下载文件?(比如从50MB开始)这一点我们可以通过HTTP请求信息头来设置,还记得HTTP请求信息头的“Range”属性吗?
断点:
首要问题(多线程下载)已经被我们解决了,支持断点下载想必大家也已经想到了。就是将下载的进度保存到文件中,但在Android中却不能这么做。在Android平台中,我们需要向文件中写出下载的文件数据,还需要向另一个文件中写出下载进度,这样会出错。这样会导致有一个文件的内容没有被写出。所以我们就不能以文件的方式来保存下载进度,但可以通过数据库的方式保存下载进度。这两大问题我们已经有了解决思路,那么就开始动手编写吧!(以下源码仅供学习)
1.创建Android工程
Project name:MulThreadDownloader
BuildTarget:Android2.1
Application name:多线程断点下载
Package name:com.changcheng.download
Create Activity:MulThreadDownloader
Min SDK Version:7
2.AndroidManifest.xml
3.strings.xml
Hello World, DownloadActivity!
多线程断点下载
下载路径
下载
SDCard不存在或者写保护
4.main.xml
5.MulThreadDownloader
package com.changcheng.download;
import java.io.File;
import com.changcheng.net.download.DownloadProgressListener;
import com.changcheng.net.download.FileDownloader;
import com.changcheng.download.R;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
public class MulThreadDownloader extends Activity {
private EditText pathText;
private ProgressBar progressBar;
private TextView resultView;
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
if(!Thread.currentThread().isInterrupted()){
switch (msg.what) {
case 1:
// 获取当前文件下载的进度
int size = msg.getData().getInt("size");
progressBar.setProgress(size);
int result = (int)(((float)size/(float)progressBar.getMax()) * 100);
resultView.setText(result+ "%");
if(progressBar.getMax() == size){
Toast.makeText(MulThreadDownloader.this, "文件下载完成", 1).show();
}
break;
case -1:
String error = msg.getData().getString("error");
Toast.makeText(MulThreadDownloader.this, error, 1).show();
break;
}
}
super.handleMessage(msg);
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
pathText = (EditText)this.findViewById(R.id.path);
progressBar = (ProgressBar)this.findViewById(R.id.downloadbar);
resultView = (TextView)this.findViewById(R.id.resultView);
Button button = (Button)this.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String path = pathText.getText().toString();
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
//下载文件需要很长的时间,主线程是不能够长时间被阻塞,如果主线程被长时间阻塞, 那么Android被回收应用
download(path, Environment.getExternalStorageDirectory());
}else{
Toast.makeText(MulThreadDownloader.this, R.string.sdcarderror, 1).show();
}
}
});
}
/**
* 下载文件
* @param path 下载路径
* @param saveDir 文件保存目录
*/
//对于Android的UI控件,只能由主线程负责显示界面的更新,其他线程不能直接更新UI控件的显示
public void download(final String path, final File saveDir){
new Thread(new Runnable() {
@Override
public void run() {
FileDownloader downer = new FileDownloader(MulThreadDownloader.this, path, saveDir, 3);
progressBar.setMax(downer.getFileSize());//设置进度条的最大刻度
try {
downer.download(new DownloadProgressListener(){
@Override
public void onDownloadSize(int size) {
Message msg = new Message();
msg.what = 1;
msg.getData().putInt("size", size);
handler.sendMessage(msg);//发送消息
}});
} catch (Exception e) {
Message msg = new Message();
msg.what = -1;
msg.getData().putString("error", "下载失败");
handler.sendMessage(msg);
}
}
}).start();
}
}
6.FileDownload
package com.changcheng.net.download;
import java.io.File;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.changcheng.download.service.FileService;
import android.content.Context;
import android.util.Log;
/**
* 文件下载器
* @author lihuoming@sohu.com
*
*/
public class FileDownloader {
private Context context;
private FileService fileService;
private static final String TAG = "FileDownloader";
/* 已下载文件大小 */
private int downloadSize = 0;
/* 原始文件大小 */
private int fileSize = 0;
/* 线程数 */
private DownloadThread[] threads;
/* 下载路径 */
private URL url;
/* 本地保存文件 */
private File saveFile;
/* 下载记录文件 */
private File logFile;
/* 缓存各线程最后下载的位置*/
private Map data = new ConcurrentHashMap();
/* 每条线程下载的大小 */
private int block;
private String downloadUrl;//下载路径
/**
* 获取线程数
*/
public int getThreadSize() {
return threads.length;
}
/**
* 获取文件大小
* @return
*/
public int getFileSize() {
return fileSize;
}
/**
* 累计已下载大小
* @param size
*/
protected synchronized void append(int size) {
downloadSize += size;
}
/**
* 更新指定线程最后下载的位置
* @param threadId 线程id
* @param pos 最后下载的位置
*/
protected void update(int threadId, int pos) {
this.data.put(threadId, pos);
}
/**
* 保存记录文件
*/
protected synchronized void saveLogFile() {
this.fileService.update(this.downloadUrl, this.data);
}
/**
* 构建文件下载器
* @param downloadUrl 下载路径
* @param fileSaveDir 文件保存目录
* @param threadNum 下载线程数
*/
public FileDownloader(Context context, String downloadUrl, File fileSaveDir, int threadNum) {
try {
this.context = context;
this.downloadUrl = downloadUrl;
fileService = new FileService(context);
this.url = new URL(downloadUrl);
if(!fileSaveDir.exists()) fileSaveDir.mkdirs();
this.threads = new DownloadThread[threadNum];
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(6*1000);
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
conn.setRequestProperty("Accept-Language", "zh-CN");
conn.setRequestProperty("Referer", downloadUrl);
conn.setRequestProperty("Charset", "UTF-8");
conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.connect();
printResponseHeader(conn);
if (conn.getResponseCode()==200) {
this.fileSize = conn.getContentLength();//根据响应获取文件大小
if (this.fileSize logdata = fileService.getData(downloadUrl);
if(logdata.size()>0){
data.putAll(logdata);
}
this.block = this.fileSize / this.threads.length + 1;
if(this.data.size()==this.threads.length){
for (int i = 0; i 0) randOut.setLength(this.fileSize);
randOut.seek(this.data.get(i+1));
this.threads = new DownloadThread(this, this.url, randOut, this.block, this.data.get(i+1), i+1);
this.threads.setPriority(7);
this.threads.start();
}else{
this.threads = null;
}
}
this.fileService.save(this.downloadUrl, this.data);
boolean notFinish = true;//下载未完成
while (notFinish) {// 循环判断是否下载完毕
Thread.sleep(900);
notFinish = false;//假定下载完成
for (int i = 0; i getHttpResponseHeader(HttpURLConnection http) {
Map header = new LinkedHashMap();
for (int i = 0;; i++) {
String mine = http.getHeaderField(i);
if (mine == null) break;
header.put(http.getHeaderFieldKey(i), mine);
}
return header;
}
/**
* 打印Http头字段
* @param http
*/
public static void printResponseHeader(HttpURLConnection http){
Map header = getHttpResponseHeader(http);
for(Map.Entry entry : header.entrySet()){
String key = entry.getKey()!=null ? entry.getKey()+ ":" : "";
print(key+ entry.getValue());
}
}
private static void print(String msg){
Log.i(TAG, msg);
}
}
7.DownloadProgressListener
package com.changcheng.net.download;
public interface DownloadProgressListener {
public void onDownloadSize(int size);
}
8.FileService
package com.changcheng.download.service;
import java.util.HashMap;
import java.util.Map;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
/**
* 业务bean
*
*/
public class FileService {
private DBOpenHelper openHelper;
public FileService(Context context) {
openHelper = new DBOpenHelper(context);
}
/**
* 获取线程最后下载位置
* @param path
* @return
*/
public Map getData(String path){
SQLiteDatabase db = openHelper.getReadableDatabase();
Cursor cursor = db.rawQuery("select threadid, position from filedown where downpath=?", new String[]{path});
Map data = new HashMap();
while(cursor.moveToNext()){
data.put(cursor.getInt(0), cursor.getInt(1));
}
cursor.close();
db.close();
return data;
}
/**
* 保存下载线程初始位置
* @param path
* @param map
*/
public void save(String path, Map map){//int threadid, int position
SQLiteDatabase db = openHelper.getWritableDatabase();
db.beginTransaction();
try{
for(Map.Entry entry : map.entrySet()){
db.execSQL("insert into filedown(downpath, threadid, position) values(?,?,?)",
new Object[]{path, entry.getKey(), entry.getValue()});
}
db.setTransactionSuccessful();
}finally{
db.endTransaction();
}
db.close();
}
/**
* 实时更新线程的最后下载位置
* @param path
* @param map
*/
public void update(String path, Map map){
SQLiteDatabase db = openHelper.getWritableDatabase();
db.beginTransaction();
try{
for(Map.Entry entry : map.entrySet()){
db.execSQL("update filedown set position=? where downpath=? and threadid=?",
new Object[]{entry.getValue(), path, entry.getKey()});
}
db.setTransactionSuccessful();
}finally{
db.endTransaction();
}
db.close();
}
/**
* 当文件下载完成后,清掉该文件对应的下载记录
* @param path
*/
public void delete(String path){
SQLiteDatabase db = openHelper.getWritableDatabase();
db.execSQL("delete from filedown where downpath=?", new Object[]{path});
db.close();
}
}
9.DownloadThread
package com.changcheng.net.download;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import android.util.Log;
public class DownloadThread extends Thread {
private static final String TAG = "DownloadThread";
private RandomAccessFile saveFile;
private URL downUrl;
private int block;
/* 下载开始位置 */
private int threadId = -1;
private int startPos;
private int downLength;
private boolean finish = false;
private FileDownloader downloader;
public DownloadThread(FileDownloader downloader, URL downUrl, RandomAccessFile saveFile, int block, int startPos, int threadId) {
this.downUrl = downUrl;
this.saveFile = saveFile;
this.block = block;
this.startPos = startPos;
this.downloader = downloader;
this.threadId = threadId;
this.downLength = startPos - (block * (threadId - 1));
}
@Override
public void run() {
if(downLength Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
http.setRequestProperty("Connection", "Keep-Alive");
InputStream inStream = http.getInputStream();
int max = block>1024 ? 1024 : (block>10 ? 10 : 1);
byte[] buffer = new byte[max];
int offset = 0;
print("线程 " + this.threadId + "从位置"+ this.startPos+ "开始下载 ");
while (downLength < block && (offset = inStream.read(buffer, 0, max)) != -1) {
saveFile.write(buffer, 0, offset);
downLength += offset;
downloader.update(this.threadId, block * (threadId - 1) + downLength);
downloader.saveLogFile();
downloader.append(offset);
int spare = block-downLength;//求剩下的字节数
if(spare < max) max = (int) spare;
}
saveFile.close();
inStream.close();
print("线程 " + this.threadId + "完成下载 ");
this.finish = true;
this.interrupt();
} catch (Exception e) {
this.downLength = -1;
print("线程"+ this.threadId+ ":"+ e);
}
}
}
private static void print(String msg){
Log.i(TAG, msg);
}
/**
* 下载是否完成
* @return
*/
public boolean isFinish() {
return finish;
}
/**
* 已经下载的内容大小
* @return 如果返回值为-1,代表下载失败
*/
public long getDownLength() {
return downLength;
}
}
11.DBOpenHelper
package com.changcheng.download.service;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class DBOpenHelper extends SQLiteOpenHelper {
private static final String DBNAME = "download.db";
private static final int VERSION = 2;
public DBOpenHelper(Context context) {
super(context, DBNAME, null, VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS filedown (id integer primary key autoincrement, downpath varchar(100), threadid INTEGER, position INTEGER)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS filedown");
onCreate(db);
}
}
发表评论
-
内核启动过程分析
2012-01-20 02:11 1111内核启动过程分析 2011 ... -
操作系统学习笔记1
2012-01-20 02:11 753操作系统学习笔记1 2010 ... -
深入探究 Win32 PE 文件格式
2012-01-20 02:11 799深入探究 Win32 PE 文件格 ... -
堆栈的认知!
2012-01-20 02:10 559堆栈的认知! 2010年11月2 ... -
uboot讲解
2012-01-20 02:10 1461uboot讲解 2010年09月15日 实验:p167 ... -
会说话的女人最出色
2012-01-19 10:01 758会说话的女人最出色 2011年07月06日 魅力这个词语 ... -
收集赞美人的词语
2012-01-19 10:01 1356收集赞美人的词语 2011 ... -
形容女孩子的词语
2012-01-19 10:01 1240形容女孩子的词语 2011年04月23日 阿娇金屋 闭月 ... -
2011-7-19
2012-01-19 10:00 6322011-7-19 2011年07月19日 ... -
会说话的女人最出色!
2012-01-19 10:00 776会说话的女人最出色! 2 ... -
有关手机内程序系统平台的介绍
2012-01-17 02:42 663有关手机内程序系统平 ... -
Charles Petzold
2012-01-17 02:42 675Charles Petzold 2011年10月28 ... -
多线程opengl 创建位图失败之解决方案
2012-01-17 02:41 2958多线程opengl 创建位图失败之解决方案 2010年07月 ... -
基于Windows CE的激光切割机开放式数控系统的研究
2012-01-17 02:41 753基于Windows CE的激光切割 ... -
老产品新扩展
2012-01-15 22:15 613老产品新扩展 2012年01月08日 TLCHAT V5 ... -
基于FMS+FME的直播系统的实现
2012-01-15 22:15 1958基于FMS+FME的直播系统的 ... -
java视频监控
2012-01-15 22:15 1122java视频监控 2010年05月28日 呵呵!临近毕业 ... -
FMS3系列(七):FMS案例开发--视频聊天室
2012-01-15 22:15 734FMS3系列(七):FMS案例开发 ... -
转载 浅谈技术难度与公司收益
2012-01-15 22:15 665转载 浅谈技术难度 ...
相关推荐
多线程下载大概思路就是通过Range 属性实现文件分段,然后用RandomAccessFile 来读写文件,最终合并为一个文件 首先看下效果图: 创建工程 ThreadDemo 首先布局文件 threaddemo.xml <?xml version=1.0 encoding...
首要问题(多线程下载)已经被我们解决了,支持断点下载想必大家也已经想到了。就是将下载的进度保存到文件中,但在Android中却不能这么做。通过老黎的试验,在Android平台中,我们需要向文件中写出下载的文件数据...
从整体上来看 一款Android产品分为设计 编码和测试三个阶段 《Android开发三剑客:UML 模式与测试》着眼于Android产品的两个重要环节 设计和测试 分为上篇 中篇和下篇 上篇以Android多线程断点续传下载器为例 细致入...
从整体上来看 一款Android产品分为设计 编码和测试三个阶段 《Android开发三剑客:UML 模式与测试》着眼于Android产品的两个重要环节 设计和测试 分为上篇 中篇和下篇 上篇以Android多线程断点续传下载器为例 细致入...
在Android中实现多线程断点下载 Android提高篇内容整理 android移动开发案例精选 Android通过画线实现button效果 Android如何防止apk程序被反编译 Android 之 AIDL 和远程 Service 调用 Android 相对布局技巧 ...
这是一个基于android平台开发的一个MP3播放器,所使用的技术有AsyncTask 异步搜索sd卡下的mp3文件,ViewPager listview 做ui显示,启动服务播放MP3,服务器搜索MP3,多线程下载MP3,现在播放,上一首,下一首,暂停/...
如果不是从学习Android SDK中提供的那些样例代码开始,可能没有更好的方法来掌握在Android这个框架上开发。由Android的核心开发团队提供了15个优秀的示例项目,包含了游戏、图像处理、时间显示、开始菜单快捷方式...
如果不是从学习Android SDK中提供的那些样例代码开始,可能没有更好的方法来掌握在Android这个框架上开发。由Android的核心开发团队提供了15个优秀的示例项目,包含了游戏、图像处理、时间显示、开始菜单快捷方式...
参照书上的例子模仿的,虽然只实现了基本的功能,但它的思路比较好,为以后的开发奠定了基础。欢迎大家拍砖 项目简介: MultieThreadServer为服务器端程序,是普通的Java项目;MultieThreadClient是客户端程序...
本书大部分章节中都专门撰写了“拓展思路”的内容,希望这部分内容能激发读者对Android代码进行深入研 究的热情。 本书面向的读者 (1)Android应用开发工程师 . 对于Android应用开发工程师而言,本书中...
1. 内容丰富:本面试题集包含了大量关于Android项目的问题,涵盖了UI设计、数据存储、网络编程、多线程处理等多个方面,既有基础知识也有实战技巧。 2. 结构清晰:所有问题按照不同的知识点进行分类整理,方便求职者...
如果不是从学习Android SDK中提供的那些样例代码开始,可能没有更好的方法来掌握在Android这个框架上开发。由Android的核心开发团队提供了15个优秀的示例项目,包含了游戏、图像处理、时间显示、开始菜单快捷方式...
下面提出一些优化: 1、采用线程池 2、内存缓存+文件缓存 3、内存缓存中网上很多是采用SoftReference来防止堆溢出,这儿严格限制只能使用最大JVM内存的1/4 4、对下载的图片进行按比例缩放,以减少内存的消耗 具体的...
13.1.4 加入多线程 483 13.2 使用URL访问网络资源 488 13.2.1 使用URL读取网络资源 489 13.2.2 使用URLConnection 提交请求 490 13.3 使用HTTP访问网络 496 13.3.1 使用HttpURLConnection 496 ...
目前大公司的app开发都要基于模块化、层次化、组件化、控件化的思路来设计架构,而这一切的基础都是Android Framework。 如今,各企业Android招聘的面试中,也常常涉及到Framework相关的问题,如: 进程通信你用过...
AppInit 用于解决美团收银 B 端 App 在业务演进过程中的实际问题,取得了不错的效果,因此我们决定将其开源,希望更多技术同行一起开发,应用到更广泛的场景里去。 背景 随着业务的快速发展,新项目新业务不断出现,...
这篇android客户端源码我觉得写的非常好,因为这里面有很多线程处理,activity处理,以及有动态的listview 中adapter怎么去处理解析,觉得比mars老师写的代码好很多,思路好很多,mars老师写的东西只能适合初学者...