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

<?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

<?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

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

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:

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:

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程序设计有所帮助。

(0)

相关推荐

  • Android多线程+单线程+断点续传+进度条显示下载功能

    效果图 白话分析: 多线程:肯定是多个线程咯 断点:线程停止下载的位置 续传:线程从停止下载的位置上继续下载,直到完成任务为止. 核心分析: 断点: 当前线程已经下载的数据长度 续传: 向服务器请求上次线程停止下载位置的数据 con.setRequestProperty("Range", "bytes=" + start + "-" + end); 分配线程: int currentPartSize = fileSize / mThreadNum

  • Android实现网络多线程断点续传下载实例

    我们编写的是Andorid的HTTP协议多线程断点下载应用程序.直接使用单线程下载HTTP文件对我们来说是一件非常简单的事.那么,多线程断点需要什么功能? 1.多线程下载, 2.支持断点. 使用多线程的好处:使用多线程下载会提升文件下载的速度.那么多线程下载文件的过程是: (1)首先获得下载文件的长度,然后设置本地文件的长度. HttpURLConnection.getContentLength();//获取下载文件的长度 RandomAccessFile file = new RandomAc

  • Android多线程断点续传下载功能实现代码

    原理 其实断点续传的原理很简单,从字面上理解,所谓断点续传就是从停止的地方重新下载. 断点:线程停止的位置. 续传:从停止的位置重新下载. 用代码解析就是: 断点:当前线程已经下载完成的数据长度. 续传:向服务器请求上次线程停止位置之后的数据. 原理知道了,功能实现起来也简单.每当线程停止时就把已下载的数据长度写入记录文件,当重新下载时,从记录文件读取已经下载了的长度.而这个长度就是所需要的断点. 续传的实现也简单,可以通过设置网络请求参数,请求服务器从指定的位置开始读取数据. 而要实现这两个功

  • Android实现网络多线程断点续传下载功能

    我们编写的是Andorid的HTTP协议多线程断点下载应用程序.直接使用单线程下载HTTP文件对我们来说是一件非常简单的事.那么,多线程断点需要什么功能? 1.多线程下载 2.支持断点 使用多线程的好处:使用多线程下载会提升文件下载的速度 原理 多线程下载的原理就是将要下载的文件分成若干份,其中每份都使用一个单独的线程进行下载,这样对于文件的下载速度自然就提高了许多. 既然要分成若干部分分工下载,自然要知道各个线程自己要下载的起始位置,与要下载的大小.所以我们要解决线程的分配与各个线程定位到下载

  • android实现多线程下载文件(支持暂停、取消、断点续传)

    多线程下载文件(支持暂停.取消.断点续传) 多线程同时下载文件即:在同一时间内通过多个线程对同一个请求地址发起多个请求,将需要下载的数据分割成多个部分,同时下载,每个线程只负责下载其中的一部分,最后将每一个线程下载的部分组装起来即可. 涉及的知识及问题 请求的数据如何分段 分段完成后如何下载和下载完成后如何组装到一起 暂停下载和继续下载的实现(wait().notifyAll().synchronized的使用) 取消下载和断点续传的实现 一.请求的数据如何分段 首先通过HttpURLConne

  • PC版与Android手机版带断点续传的多线程下载

    一.多线程下载 多线程下载就是抢占服务器资源 原理:服务器CPU 分配给每条线程的时间片相同,服务器带宽平均分配给每条线程,所以客户端开启的线程越多,就能抢占到更多的服务器资源. 1.设置开启线程数,发送http请求到下载地址,获取下载文件的总长度           然后创建一个长度一致的临时文件,避免下载到一半存储空间不够了,并计算每个线程下载多少数据              2.计算每个线程下载数据的开始和结束位置           再次发送请求,用 Range 头请求开始位置和结束位

  • Android FTP 多线程断点续传下载\上传的实例

    最近在给我的开源下载框架Aria增加FTP断点续传下载和上传功能,在此过程中,爬了FTP的不少坑,终于将功能实现了,在此把一些核心功能点记录下载. FTP下载原理 FTP单线程断点续传 FTP和传统的HTTP协议有所不同,由于FTP没有所谓的头文件,因此我们不能像HTTP那样通过设置header向服务器指定下载区间. 但是FTP协议提供了一个更好用的命令REST用于从指定位置恢复任务,同时FTP协议也提供了一个命令SIZE用于获取下载的文件大小,有了这两个命令,FTP断点续传也就没有什么问题.

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

    本文实例讲述了Android编程开发实现多线程断点续传下载器.分享给大家供大家参考,具体如下: 使用多线程断点续传下载器在下载的时候多个线程并发可以占用服务器端更多资源,从而加快下载速度,在下载过程中记录每个线程已拷贝数据的数量,如果下载中断,比如无信号断线.电量不足等情况下,这就需要使用到断点续传功能,下次启动时从记录位置继续下载,可避免重复部分的下载.这里采用数据库来记录下载的进度. 效果图:   断点续传 1.断点续传需要在下载过程中记录每条线程的下载进度 2.每次下载开始之前先读取数据库

  • Android编程开发实现带进度条和百分比的多线程下载

    本文实例讲述了Android编程开发实现带进度条和百分比的多线程下载.分享给大家供大家参考,具体如下: 继上一篇<java多线程下载实例详解>之后,可以将它移植到我们的安卓中来,下面是具体实现源码: DownActivity.java: package com.example.downloads; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.net.H

  • Android编程开发音乐播放器实例

    本文实例讲述了Android编程开发音乐播放器,分享给大家供大家参考,具体如下: 音乐播放器中综合了以下内容: SeekBar.ListView.广播接收者(以代码的形式注册Receiver).系统服务.MediaPlayer 实现的功能: 1.暂停/播放.下一首/上一首,点击某一首时播放 2.支持拖动进度条快进 3.列表排序 4.来电话时,停止播放,挂断后继续播放 5.可在后台播放 效果图: 界面: main.xml: <?xml version="1.0" encoding=

  • Android编程开发之性能优化技巧总结

    本文详细总结了Android编程开发之性能优化技巧.分享给大家供大家参考,具体如下: 1.http用gzip压缩,设置连接超时时间和响应超时时间 http请求按照业务需求,分为是否可以缓存和不可缓存,那么在无网络的环境中,仍然通过缓存的httpresponse浏览部分数据,实现离线阅读. 2.listview 性能优化 1).复用convertView 在getItemView中,判断convertView是否为空,如果不为空,可复用.如果couvertview中的view需要添加listern

  • Android通过HTTP协议实现断点续传下载实例

    整理文档,搜刮出一个Android通过HTTP协议实现断点续传下载的代码,稍微整理精简一下做下分享. FileDownloader.java package cn.itcast.net.download; import java.io.File; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; import java.util.LinkedHashMap; impor

  • python实现多线程网页下载器

    本文为大家分享了python实现的一个多线程网页下载器,供大家参考,具体内容如下 这是一个有着真实需求的实现,我的用途是拿它来通过 HTTP 方式向服务器提交游戏数据.把它放上来也是想大家帮忙挑刺,找找 bug,让它工作得更好. keywords:python,http,multi-threads,thread,threading,httplib,urllib,urllib2,Queue,http pool,httppool 废话少说,上源码: # -*- coding:utf-8 -*- im

  • Android 使用AsyncTask实现多线程断点续传

    前面一篇博客<AsyncTask实现断点续传>讲解了如何实现单线程下的断点续传,也就是一个文件只有一个线程进行下载.    对于大文件而言,使用多线程下载就会比单线程下载要快一些.多线程下载相比单线程下载要稍微复杂一点,本博文将详细讲解如何使用AsyncTask来实现多线程的断点续传下载. 一.实现原理 多线程下载首先要通过每个文件总的下载线程数(我这里设定5个)来确定每个线程所负责下载的起止位置. long blockLength = mFileLength / DEFAULT_POOL_S

  • Android编程开发ScrollView中ViewPager无法正常滑动问题解决方法

    本文实例讲述了Android编程开发ScrollView中ViewPager无法正常滑动问题解决方法.分享给大家供大家参考,具体如下: 这里主要介绍如何解决ViewPager在ScrollView中滑动经常失效.无法正常滑动问题. 解决方法只需要在接近水平滚动时ScrollView不处理事件而交由其子View(即这里的ViewPager)处理即可,重写ScrollView的onInterceptTouchEvent函数,如下: package cc.newnews.view; import an

  • Android编程开发之打开文件的Intent及使用方法

    本文实例讲述了Android编程开发之打开文件的Intent及使用方法.分享给大家供大家参考,具体如下: 在写文件管理系统时会用到各种打开不同格式的文件的需求,由于Android系统默认内置了一些可以打开的系统应用,但还是不能满足需求,比如打开视频文件.word等,需要安装相应的播放软件才可以使用,这时程序会通过Intent查找可以使用的软件实现通过代码打开一个文件需要2部分,一部分是要获取到不同文件的后缀,以便根据需求匹配相应的Intent,另一个就是不同格式的文件打开的Intent不同 1.

随机推荐