使用多线程断点续传下载器在下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度,在下载过程中记录每个线程已拷贝数据的数量,如果下载中断,比如无信号断线、电量不足等情况下,这就需要使用到断点续传功能,下次启动时从记录位置继续下载,可避免重复部分的下载。这里采用数据库来记录下载的进度。

效果图

断点续传

1.断点续传需要在下载过程中记录每条线程的下载进度

2.每次下载开始之前先读取数据库,查询是否有未完成的记录,有就继续下载,没有则创建新记录插入数据库

3.在每次向文件中写入数据之后,在数据库中更新下载进度

4.下载完成之后删除数据库中下载记录

Handler传输数据

这个主要用来记录百分比,每下载一部分数据就通知主线程来记录时间

1.主线程中创建的View只能在主线程中修改,其他线程只能通过和主线程通信,在主线程中改变View数据

2.我们使用Handler可以处理这种需求

主线程中创建Handler,重写handleMessage()方法

新线程中使用Handler发送消息,主线程即可收到消息,并且执行handleMessage()方法

动态生成新View

可实现多任务下载

1.创建XML文件,将要生成的View配置好

2.获取系统服务LayoutInflater,用来生成新的View

LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);

3.使用inflate(int resource, ViewGroup root)方法生成新的View

4.调用当前页面中某个容器的addView,将新创建的View添加进来

示例

进度条样式 download.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight=""
>
<!--进度条样式默认为圆形进度条,水平进度条需要配置style属性,
?android:attr/progressBarStyleHorizontal -->
<ProgressBar
android:layout_width="fill_parent"
android:layout_height="20dp"
style="?android:attr/progressBarStyleHorizontal"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="0%"
/>
</LinearLayout>
<Button
android:layout_width="40dp"
android:layout_height="40dp"
android:onClick="pause"
android:text="||"
/>
</LinearLayout>

顶部样式 main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/root"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="请输入下载路径"
/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="30dp"
>
<EditText
android:id="@+id/path"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:layout_weight=""
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下载"
android:onClick="download"
/>
</LinearLayout>
</LinearLayout>

MainActivity.Java

public class MainActivity extends Activity {
private LayoutInflater inflater;
private LinearLayout rootLinearLayout;
private EditText pathEditText; @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main); //动态生成新View,获取系统服务LayoutInflater,用来生成新的View
inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
rootLinearLayout = (LinearLayout) findViewById(R.id.root);
pathEditText = (EditText) findViewById(R.id.path); // 窗体创建之后, 查询数据库是否有未完成任务, 如果有, 创建进度条等组件, 继续下载
List<String> list = new InfoDao(this).queryUndone();
for (String path : list)
createDownload(path);
} /**
* 下载按钮
* @param view
*/
public void download(View view) {
String path = "http://192.168.1.199:8080/14_Web/" + pathEditText.getText().toString();
createDownload(path);
} /**
* 动态生成新View
* 初始化表单数据
* @param path
*/
private void createDownload(String path) {
//获取系统服务LayoutInflater,用来生成新的View
LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
LinearLayout linearLayout = (LinearLayout) inflater.inflate(R.layout.download, null); LinearLayout childLinearLayout = (LinearLayout) linearLayout.getChildAt();
ProgressBar progressBar = (ProgressBar) childLinearLayout.getChildAt();
TextView textView = (TextView) childLinearLayout.getChildAt();
Button button = (Button) linearLayout.getChildAt(); try {
button.setOnClickListener(new MyListener(progressBar, textView, path));
//调用当前页面中某个容器的addView,将新创建的View添加进来
rootLinearLayout.addView(linearLayout);
} catch (Exception e) {
e.printStackTrace();
}
} private final class MyListener implements OnClickListener {
private ProgressBar progressBar;
private TextView textView;
private int fileLen;
private Downloader downloader;
private String name; /**
* 执行下载
* @param progressBar //进度条
* @param textView //百分比
* @param path //下载文件路径
*/
public MyListener(ProgressBar progressBar, TextView textView, String path) {
this.progressBar = progressBar;
this.textView = textView;
name = path.substring(path.lastIndexOf("/") + ); downloader = new Downloader(getApplicationContext(), handler);
try {
downloader.download(path, );
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(), "下载过程中出现异常", ).show();
throw new RuntimeException(e);
}
} //Handler传输数据
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case :
//获取文件的大小
fileLen = msg.getData().getInt("fileLen");
//设置进度条最大刻度:setMax()
progressBar.setMax(fileLen);
break;
case :
//获取当前下载的总量
int done = msg.getData().getInt("done");
//当前进度的百分比
textView.setText(name + "\t" + done * / fileLen + "%");
//进度条设置当前进度:setProgress()
progressBar.setProgress(done);
if (done == fileLen) {
Toast.makeText(getApplicationContext(), name + " 下载完成", ).show();
//下载完成后退出进度条
rootLinearLayout.removeView((View) progressBar.getParent().getParent());
}
break;
}
}
}; /**
* 暂停和继续下载
*/
public void onClick(View v) {
Button pauseButton = (Button) v;
if ("||".equals(pauseButton.getText())) {
downloader.pause();
pauseButton.setText("▶");
} else {
downloader.resume();
pauseButton.setText("||");
}
}
}
}

Downloader.java

public class Downloader {

    private int done;
private InfoDao dao;
private int fileLen;
private Handler handler;
private boolean isPause; public Downloader(Context context, Handler handler) {
dao = new InfoDao(context);
this.handler = handler;
}
/**
* 多线程下载
* @param path 下载路径
* @param thCount 需要开启多少个线程
* @throws Exception
*/
public void download(String path, int thCount) throws Exception {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//设置超时时间
conn.setConnectTimeout();
if (conn.getResponseCode() == ) {
fileLen = conn.getContentLength();
String name = path.substring(path.lastIndexOf("/") + );
File file = new File(Environment.getExternalStorageDirectory(), name);
RandomAccessFile raf = new RandomAccessFile(file, "rws");
raf.setLength(fileLen);
raf.close(); //Handler发送消息,主线程接收消息,获取数据的长度
Message msg = new Message();
msg.what = ;
msg.getData().putInt("fileLen", fileLen);
handler.sendMessage(msg); //计算每个线程下载的字节数
int partLen = (fileLen + thCount - ) / thCount;
for (int i = ; i < thCount; i++)
new DownloadThread(url, file, partLen, i).start();
} else {
throw new IllegalArgumentException("404 path: " + path);
}
} private final class DownloadThread extends Thread {
private URL url;
private File file;
private int partLen;
private int id; public DownloadThread(URL url, File file, int partLen, int id) {
this.url = url;
this.file = file;
this.partLen = partLen;
this.id = id;
} /**
* 写入操作
*/
public void run() {
// 判断上次是否有未完成任务
Info info = dao.query(url.toString(), id);
if (info != null) {
// 如果有, 读取当前线程已下载量
done += info.getDone();
} else {
// 如果没有, 则创建一个新记录存入
info = new Info(url.toString(), id, );
dao.insert(info);
} int start = id * partLen + info.getDone(); // 开始位置 += 已下载量
int end = (id + ) * partLen - ; try {
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout();
//获取指定位置的数据,Range范围如果超出服务器上数据范围, 会以服务器数据末尾为准
conn.setRequestProperty("Range", "bytes=" + start + "-" + end);
RandomAccessFile raf = new RandomAccessFile(file, "rws");
raf.seek(start);
//开始读写数据
InputStream in = conn.getInputStream();
byte[] buf = new byte[ * ];
int len;
while ((len = in.read(buf)) != -) {
if (isPause) {
//使用线程锁锁定该线程
synchronized (dao) {
try {
dao.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
raf.write(buf, , len);
done += len;
info.setDone(info.getDone() + len);
// 记录每个线程已下载的数据量
dao.update(info);
//新线程中用Handler发送消息,主线程接收消息
Message msg = new Message();
msg.what = ;
msg.getData().putInt("done", done);
handler.sendMessage(msg);
}
in.close();
raf.close();
// 删除下载记录
dao.deleteAll(info.getPath(), fileLen);
} catch (IOException e) {
e.printStackTrace();
}
}
} //暂停下载
public void pause() {
isPause = true;
}
//继续下载
public void resume() {
isPause = false;
//恢复所有线程
synchronized (dao) {
dao.notifyAll();
}
}
}

Dao:

DBOpenHelper:

public class DBOpenHelper extends SQLiteOpenHelper {

    public DBOpenHelper(Context context) {
super(context, "download.db", null, );
} @Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE info(path VARCHAR(1024), thid INTEGER, done INTEGER, PRIMARY KEY(path, thid))");
} @Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
} }

InfoDao:

public class InfoDao {
private DBOpenHelper helper; public InfoDao(Context context) {
helper = new DBOpenHelper(context);
} public void insert(Info info) {
SQLiteDatabase db = helper.getWritableDatabase();
db.execSQL("INSERT INTO info(path, thid, done) VALUES(?, ?, ?)", new Object[] { info.getPath(), info.getThid(), info.getDone() });
} public void delete(String path, int thid) {
SQLiteDatabase db = helper.getWritableDatabase();
db.execSQL("DELETE FROM info WHERE path=? AND thid=?", new Object[] { path, thid });
} public void update(Info info) {
SQLiteDatabase db = helper.getWritableDatabase();
db.execSQL("UPDATE info SET done=? WHERE path=? AND thid=?", new Object[] { info.getDone(), info.getPath(), info.getThid() });
} public Info query(String path, int thid) {
SQLiteDatabase db = helper.getWritableDatabase();
Cursor c = db.rawQuery("SELECT path, thid, done FROM info WHERE path=? AND thid=?", new String[] { path, String.valueOf(thid) });
Info info = null;
if (c.moveToNext())
info = new Info(c.getString(), c.getInt(), c.getInt());
c.close(); return info;
} public void deleteAll(String path, int len) {
SQLiteDatabase db = helper.getWritableDatabase();
Cursor c = db.rawQuery("SELECT SUM(done) FROM info WHERE path=?", new String[] { path });
if (c.moveToNext()) {
int result = c.getInt();
if (result == len)
db.execSQL("DELETE FROM info WHERE path=? ", new Object[] { path });
}
} public List<String> queryUndone() {
SQLiteDatabase db = helper.getWritableDatabase();
Cursor c = db.rawQuery("SELECT DISTINCT path FROM info", null);
List<String> pathList = new ArrayList<String>();
while (c.moveToNext())
pathList.add(c.getString());
c.close();
return pathList;
} }

Android开发多线程断点续传下载器的更多相关文章

  1. 实现android支持多线程断点续传下载器功能

    多线程断点下载流程图: 多线程断点续传下载原理介绍: 在下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度手机端下载数据时难免会出现无信号断线.电量不足等情况,所以需要断点续传功能根据下 ...

  2. Android网络多线程断点续传下载

    本示例介绍在Android平台下通过HTTP协议实现断点续传下载. 我们编写的是Andorid的HTTP协议多线程断点下载应用程序.直接使用单线程下载HTTP文件对我们来说是一件非常简单的事.那么,多 ...

  3. android 多线程断点续传下载

    今天跟大家一起分享下Android开发中比较难的一个环节,可能很多人看到这个标题就会感觉头很大,的确如果没有良好的编码能力和逻辑思维,这块是很难搞明白的,前面2次总结中已经为大家分享过有关技术的一些基 ...

  4. Android实现网络多线程断点续传下载(转)

    本示例介绍在Android平台下通过HTTP协议实现断点续传下载. 我们编写的是Andorid的HTTP协议多线程断点下载应用程序.直接使用单线程下载HTTP文件对我们来说是一件非常简单的事.那么,多 ...

  5. Android实现网络多线程断点续传下载

    本示例介绍在Android平台下通过HTTP协议实现断点续传下载. 我们编写的是Andorid的HTTP协议多线程断点下载应用程序.直接使用单线程下载HTTP文件对我们来说是一件非常简单的事.那么,多 ...

  6. 我的Android进阶之旅------>Android基于HTTP协议的多线程断点下载器的实现

    一.首先写这篇文章之前,要了解实现该Android多线程断点下载器的几个知识点 1.多线程下载的原理,如下图所示 注意:由于Android移动设备和PC机的处理器还是不能相比,所以开辟的子线程建议不要 ...

  7. 用 python 实现一个多线程网页下载器

    今天上来分享一下昨天实现的一个多线程网页下载器. 这是一个有着真实需求的实现,我的用途是拿它来通过 HTTP 方式向服务器提交游戏数据.把它放上来也是想大家帮忙挑刺,找找 bug,让它工作得更好. k ...

  8. Python实现多线程HTTP下载器

    本文将介绍使用Python编写多线程HTTP下载器,并生成.exe可执行文件. 环境:windows/Linux + Python2.7.x 单线程 在介绍多线程之前首先介绍单线程.编写单线程的思路为 ...

  9. Java多线程的下载器(1)

    实现了一个基于Java多线程的下载器,可提供的功能有: 1. 对文件使用多线程下载,并显示每时刻的下载速度. 2. 对多个下载进行管理,包括线程调度,内存管理等. 一:单个文件下载的管理 1. 单文件 ...

随机推荐

  1. 判断UserAgent是否为手机

     , )))             {                 return true;             }             return false;         }

  2. HDU_2041——走楼梯,递推

    Problem Description 有一楼梯共M级,刚开始时你在第一级,若每次只能跨上一级或二级,要走上第M级,共有多少种走法?   Input 输入数据首先包含一个整数N,表示测试实例的个数,然 ...

  3. Cortex-M3 FLASH 日志文件系统

    本文简要介绍一下本人在Cortex-M3系统的STM32F10x芯片上开发的一个日志文件系统(与其说是系统,不如说是小小的库).该库的特点是将在STM32F10x芯片上处理数据(历史记录)变得简单可靠 ...

  4. hadoop 常用命令

    hdfs dfs -mkdir -p /usr/input/hot hdfs dfs -ls / hdfs dfs -ls /usr/input hdfd dfs -cat /usr/ouput/ho ...

  5. Myeclipse中java文件注释格式设置

    点击菜单windows->preferences,然后在左侧栏选择java ->Code Style -> CodeTemplates然后在右侧栏选择comments -> 依 ...

  6. Linux红黑树(一)——数据结构

    摘要 兹博文探讨四个重点:1.简单介绍红黑树:2.红黑树节点数据结构:3.红黑树节点中父节点指针域和自身节点颜色有机结合:4.定义红黑树和操作树节点父节点指针和节点颜色的接口,包括一系列宏和两个函数. ...

  7. Java JDBC连接SQL Server2005错误:通过port 1433 连接到主机 localhost 的 TCP/IP 连接失败

    错误原因例如以下: Exception in thread "main" org.hibernate.exception.JDBCConnectionException: Cann ...

  8. Duanxx的技术问题:word界面显示模糊

    今天打开word时,出现了word打开失败的现象,并且word的界面显示特别的模糊,找了好半天,才解决,问题见下图: 解决方式: 在word的文件->选项,这里面找到显示,然后勾选:禁用硬件图形 ...

  9. C++类的const成员函数、默认的构造函数、复制形参调用函数(转)

    C++类的const成员函数 double Sales_item::avg_price() const { } const关键字表明这是一个const成员函数,它不可以修改Sales_item类的成员 ...

  10. 酷Q机器人,QQ机器人使用教程

    软件介绍: 酷Q,软件酷Q机器人是一款基于webqq开发的一款自动接收.处理qq消息的软件. 改程序使用易语言编写,精简大量不必要代码,减小了软件体积,优化程序速度,使得酷Q更加轻巧好用. 在消息处理 ...