服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Android - Android编程开发实现多线程断点续传下载器实例

Android编程开发实现多线程断点续传下载器实例

2021-04-29 17:22傅荣康 Android

这篇文章主要介绍了Android编程开发实现多线程断点续传下载器,涉及Android多线程,文件传输及断点续传的相关技巧,需要的朋友可以参考下

本文实例讲述了android编程开发实现多线程断点续传下载器。分享给大家供大家参考,具体如下:

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

效果图

Android编程开发实现多线程断点续传下载器实例 Android编程开发实现多线程断点续传下载器实例

断点续传

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?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="1"
    >
    <!--进度条样式默认为圆形进度条,水平进度条需要配置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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?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="1"
      />
    <button
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="下载"
      android:onclick="download"
      />
  </linearlayout>
</linearlayout>

mainactivity.java

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
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(0);
    progressbar progressbar = (progressbar) childlinearlayout.getchildat(0);
    textview textview = (textview) childlinearlayout.getchildat(1);
    button button = (button) linearlayout.getchildat(1);
    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("/") + 1);
      downloader = new downloader(getapplicationcontext(), handler);
      try {
        downloader.download(path, 3);
      } catch (exception e) {
        e.printstacktrace();
        toast.maketext(getapplicationcontext(), "下载过程中出现异常", 0).show();
        throw new runtimeexception(e);
      }
    }
    //handler传输数据
    private handler handler = new handler() {
      @override
      public void handlemessage(message msg) {
        switch (msg.what) {
          case 0:
            //获取文件的大小
            filelen = msg.getdata().getint("filelen");
            //设置进度条最大刻度:setmax()
            progressbar.setmax(filelen);
            break;
          case 1:
            //获取当前下载的总量
            int done = msg.getdata().getint("done");
            //当前进度的百分比
            textview.settext(name + "\t" + done * 100 / filelen + "%");
            //进度条设置当前进度:setprogress()
            progressbar.setprogress(done);
            if (done == filelen) {
              toast.maketext(getapplicationcontext(), name + " 下载完成", 0).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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
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(3000);
    if (conn.getresponsecode() == 200) {
      filelen = conn.getcontentlength();
      string name = path.substring(path.lastindexof("/") + 1);
      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 = 0;
      msg.getdata().putint("filelen", filelen);
      handler.sendmessage(msg);
      //计算每个线程下载的字节数
      int partlen = (filelen + thcount - 1) / thcount;
      for (int i = 0; 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, 0);
        dao.insert(info);
      }
      int start = id * partlen + info.getdone(); // 开始位置 += 已下载量
      int end = (id + 1) * partlen - 1;
      try {
        httpurlconnection conn = (httpurlconnection) url.openconnection();
        conn.setreadtimeout(3000);
        //获取指定位置的数据,range范围如果超出服务器上数据范围, 会以服务器数据末尾为准
        conn.setrequestproperty("range", "bytes=" + start + "-" + end);
        randomaccessfile raf = new randomaccessfile(file, "rws");
        raf.seek(start);
        //开始读写数据
        inputstream in = conn.getinputstream();
        byte[] buf = new byte[1024 * 10];
        int len;
        while ((len = in.read(buf)) != -1) {
          if (ispause) {
            //使用线程锁锁定该线程
            synchronized (dao) {
              try {
                dao.wait();
              } catch (interruptedexception e) {
                e.printstacktrace();
              }
            }
          }
          raf.write(buf, 0, len);
          done += len;
          info.setdone(info.getdone() + len);
          // 记录每个线程已下载的数据量
          dao.update(info);
          //新线程中用handler发送消息,主线程接收消息
          message msg = new message();
          msg.what = 1;
          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:

?
1
2
3
4
5
6
7
8
9
10
11
12
public class dbopenhelper extends sqliteopenhelper {
  public dbopenhelper(context context) {
    super(context, "download.db", null, 1);
  }
  @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:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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(0), c.getint(1), c.getint(2));
    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(0);
      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(0));
    c.close();
    return pathlist;
  }
}

希望本文所述对大家android程序设计有所帮助。

延伸 · 阅读

精彩推荐
  • AndroidAndroid实现固定屏幕显示的方法

    Android实现固定屏幕显示的方法

    这篇文章主要介绍了Android实现固定屏幕显示的方法,实例分析了Android屏幕固定显示所涉及的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下...

    鉴客6192021-03-27
  • AndroidAndroid编程解析XML方法详解(SAX,DOM与PULL)

    Android编程解析XML方法详解(SAX,DOM与PULL)

    这篇文章主要介绍了Android编程解析XML方法,结合实例形式详细分析了Android解析XML文件的常用方法与相关实现技巧,需要的朋友可以参考下...

    liuhe68810052021-05-03
  • AndroidAndroid实现Service获取当前位置(GPS+基站)的方法

    Android实现Service获取当前位置(GPS+基站)的方法

    这篇文章主要介绍了Android实现Service获取当前位置(GPS+基站)的方法,较为详细的分析了Service基于GPS位置的技巧,具有一定参考借鉴价值,需要的朋友可以参考下...

    Ruthless8342021-03-31
  • AndroidAndroid CardView+ViewPager实现ViewPager翻页动画的方法

    Android CardView+ViewPager实现ViewPager翻页动画的方法

    本篇文章主要介绍了Android CardView+ViewPager实现ViewPager翻页动画的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    Abby代黎明9602022-03-02
  • AndroidAndroid界面效果UI开发资料汇总(附资料包)

    Android界面效果UI开发资料汇总(附资料包)

    android ui界面设计,友好的界面会提高用户体验度;同时也增强了android ui界面设计的难度,本文提供了一些常用开发资料(有下载哦)感兴趣的朋友可以了解下...

    Android开发网4672021-01-03
  • Android汇总Android视频录制中常见问题

    汇总Android视频录制中常见问题

    这篇文章主要汇总了Android视频录制中常见问题,帮助大家更好地解决Android视频录制中常见的问题,需要的朋友可以参考下...

    yh_thu5192021-04-28
  • AndroidAndroid程序设计之AIDL实例详解

    Android程序设计之AIDL实例详解

    这篇文章主要介绍了Android程序设计的AIDL,以一个完整实例的形式较为详细的讲述了AIDL的原理及实现方法,需要的朋友可以参考下...

    Android开发网4642021-03-09
  • AndroidAndroid中AsyncTask详细介绍

    Android中AsyncTask详细介绍

    这篇文章主要介绍了Android中AsyncTask详细介绍,AsyncTask是一个很常用的API,尤其异步处理数据并将数据应用到视图的操作场合,需要的朋友可以参考下...

    Android开发网7452021-03-11