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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


AdvserView.java package com.earen.viewflipper; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory;
ImageView的scaleType的属性有好几种,分别是matrix(默认)、center、centerCrop、centerInside、fitCenter、fitEnd、fitStart、fitXY。 |值|说明| |:--:|:--| |center|保持原图的大小,显示在ImageVie
文章浏览阅读8.8k次,点赞9次,收藏20次。本文操作环境:win10/Android studio 3.21.环境配置 在SDK Tools里选择 CMAKE/LLDB/NDK点击OK 安装这些插件. 2.创建CMakeLists.txt文件 在Project 目录下,右键app,点击新建File文件,命名为CMakeLists.txt点击OK,创建完毕! 3.配置文件 在CMa..._link c++ project with gradle
文章浏览阅读1.2w次,点赞15次,收藏69次。实现目的:由mainActivity界面跳转到otherActivity界面1.写好两个layout文件,activity_main.xml和otherxml.xmlactivity_main.xml&lt;?xml version="1.0" encoding="utf-8"?&gt;&lt;RelativeLayout ="http://schemas..._android studio 界面跳转
文章浏览阅读3.8w次。前言:最近在找Android上的全局代理软件来用,然后发现了这两款神作,都是外国的软件,而且都是开源的软件,因此把源码下载了下来,给有需要研究代理这方面的童鞋看看。不得不说,国外的开源精神十分浓,大家相互使用当前基础的开源软件,然后组合成一个更大更强的大开源软件。好吧,废话不多说,下面简单介绍一下这两款开源项目。一、ProxyDroid:ProxyDroid功能比较强大,用到的技术也比较多,源码也_proxydroid
文章浏览阅读2.5w次,点赞17次,收藏6次。创建项目后,运行项目时Gradle Build 窗口却显示错误:程序包R不存在通常情况下是不会出现这个错误的。我是怎么遇到这个错误的呢?第一次创建项目,company Domain我使用的是:aven.com,但是创建过程在卡在了Building 'Calculator' Gradle Project info这个过程中,于是我选择了“Cancel”第二次创建项目,我还是使用相同的项目名称和项目路_r不存在
文章浏览阅读8.9w次,点赞4次,收藏43次。前言:在Android上使用系统自带的代理,限制灰常大,仅支持系统自带的浏览器。这样像QQ、飞信、微博等这些单独的App都不能使用系统的代理。如何让所有软件都能正常代理呢?ProxyDroid这个软件能帮你解决!使用方法及步骤如下:一、推荐从Google Play下载ProxyDroid,目前最新版本是v2.6.6。二、对ProxyDroid进行配置(基本配置:) (1) Auto S_proxydroid使用教程
文章浏览阅读1.1w次,点赞4次,收藏17次。Android Studio提供了一个很实用的工具Android设备监视器(Android device monitor),该监视器中最常用的一个工具就是DDMS(Dalvik Debug Monitor Service),是 Android 开发环境中的Dalvik虚拟机调试监控服务。可以进行的操作有:为测试设备截屏,查看特定进程中正在运行的线程以及堆栈信息、Logcat、广播状态信息、模拟电话_安卓摄像头调试工具
文章浏览阅读2.1k次。初学Android游戏开发的朋友,往往会显得有些无所适从,他们常常不知道该从何处入手,每当遇到自己无法解决的难题时,又往往会一边羡慕于 iPhone下有诸如Cocos2d-iphone之类的免费游戏引擎可供使用,一边自暴自弃的抱怨Android平台游戏开发难度太高,又连个像样的游 戏引擎也没有,甚至误以为使用Java语言开发游戏是一件费力不讨好且没有出路的事情。事实上,这种想法完全是没有必_有素材的游戏引擎
文章浏览阅读3.2k次,点赞2次,收藏2次。2014年12月从csdn专家福利获得的一本书《Android游戏开发技术实战详解》,尘封了一年多的时间,今天才翻开来看。我认识中的Android,提到Android最先浮现在我脑海中的是那可爱的机器人图标:这个Logo是由Ascender公司设计的,诞生于2010年,其设计灵感源于男女厕所门上的图形符号(真的是灵感无处不在),于是布洛克绘制了一个简单的机器人,它的躯干就像锡罐的形状,头上还有两根_智能手机的特点有哪些?
文章浏览阅读8.1k次,点赞9次,收藏11次。首先,Android是不是真的找工作越来越难呢?这个可能是大家最关心的。这个受大的经济环境以及行业发展前景的影响,同时也和个人因素有关。2016-08-26近期一方面是所在的公司招聘Java开发人员很难招到合适的,投简历的人很少;而另一方面,经常听身边的人说Android、iOS方面找工作不好找,特别是没什么经验的,经验比较少的!说是不好找,但在我家所在的吉林省省会长春,会Unity3D+Maya_android 开发和asp.net哪个好 site:blog.csdn.net
文章浏览阅读6.1k次。在上篇“走进Android开发的世界,HelloWorld”,我们创建了一个Android 项目 HelloWorld,并演示了如何通过USB连接手机查看运行效果;而如果没有手机或没有对应型号的手机,又想做对应型号(屏幕尺寸、Android系统版本)的适配,应该怎么办呢?这时Android模拟器就派上用场了。Android模拟器Android SDK自带一个移动模拟器。它是一个可以运行在你电脑上的_安卓移动开发软件怎样预览
文章浏览阅读8.9k次。Google IO 2017 上宣布,将Kotlin语言作为安卓开发的官方语言。Kotlin由JetBrains公司开发,与Java 100%互通,并具备诸多Java尚不支持的新特性。谷歌称还将与JetBrains公司合作,为Kotlin设立一个非盈利基金会。Kotlin 是一个基于 JVM 的静态类型编程语言,Kotlin可以编译成Java字节码,也可以编译成JavaScript,方便在没有JV_kotlin为什么被嫌弃
文章浏览阅读9.6w次,点赞17次,收藏35次。有些情况下,不方便使用断点的方式来调试,而是希望在控制台打印输出日志,使用过Eclipse的同学都知道Java可以使用 System.out.println(""); 来在控制台打印输出日志,但是在android studio中却是不行的,还是有差别的,那应该用什么呢?android.util.Log在调试代码的时候我们需要查看调试信息,那我们就需要用Android Log类。android.ut_andirod.studio 为什么不在控制台打印输出
文章浏览阅读8.2k次,点赞2次,收藏8次。在上篇“走进Android开发的世界,HelloWorld”,我们创建了一个Android 项目 HelloWorld,并演示了如何通过USB连接手机查看运行效果;这里讲一下如何为应用添加一个按钮,并为按钮添加Click单击事件处理程序,显示/隐藏另一个按钮。添加按钮在HelloWorld项目的基础上,打开界面布局文件:activity_main.xml切换到Design(设计)模式;在组件But_activity_main.xml按钮隐藏
文章浏览阅读2.9k次,点赞3次,收藏9次。android 开发工具主流的还是Android Studio,当然也有很多人喜欢用Eclipse,也有人喜欢用IntelliJ IDEA ;还有Xamarin这种只需要编写一次代码,可以编译多种平台可运行的强大工具。但是它又真的强大吗?就我看来没有,身边很多人还是在用Android Studio、XCode开发应用,没见谁在用Xamarin之类的工具。系统要求WindowsMicrosoft®_android开发下载安装
文章浏览阅读4.2k次,点赞7次,收藏26次。你知道Hello World程序的由来吗?对于大多数编程语言的学习来说,真正入门的一课就是 Hello World!会而不难,难而不会。虽然很多人写过关于Android开发Hello World的文章,但随着时间的推移,开发工具、技术的进步,可能有些已经过时了。我就记录一下当下我所经历的第一个Android APP HelloWorld。一、准备1、开发环境参考:Android Studio 下载_android helloworld textview 句柄获取
这篇“android轻量级无侵入式管理数据库自动升级组件怎么实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定...
今天小编给大家分享一下Android实现自定义圆形进度条的常用方法有哪些的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文...
这篇文章主要讲解了“Android如何解决字符对齐问题”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Android...