多线程断点续传(二)

时间:2021-05-17 18:33:09

一、 学习内容

1、 多文件下载列表的显示
2、 启动多个线程分段下载

多线程断点续传(二)

二、 多线程下载原理简介

假设要分3个线程下载一个100字节的文件:从头到尾,每个线程下载一段

多线程断点续传(二)

三、 学习点

1、 Adapter的getCount()

多线程断点续传(二)

2、 Adapter的ViewHolder

多线程断点续传(二)

3、 Adapter的notifyDataSetChanged()

getView() 方法会重新调用一遍

多线程断点续传(二)

4、 单例模式

1) 【问题】:多个线程在操作同一个数据库,导致数据库锁定的问题

多线程断点续传(二)

数据库设置为单例模式,保证这个类只有一个对象,一个实例。多个实例会产生多个地方去操作数据库。单例的话,也就是说只有一个实例会对数据库进行访问操作。

2) 【单例设计】:永远只会被实例化一次

a) 私有化构造方法
b) 定义静态对象引用
c) 获得类实例的静态方法


5、 Java中线程池

自己创建和销毁线程也是会销毁性能的,减少线程创建和销毁所使用的时间,让程序运行速度更快。
线程组成的池塘,当线程运行完成之后,不会简单的销毁线程,而是出于等待,当有新的任务开始,就从线程池中拿出线程复用,这样就减少了线程创建和销毁的次数,提高了程序运行的性能。

1) 线程池接口:ExecutorService
提供对线程池中的线程进行集中的管理,比如在某一段时间内,启动某些任务,可以对所有的线程进行停止,启动的任务。减少线程创建和销毁所使用的时间,便于服务器进行管理。减少了服务器频繁创建线程的资源消耗。

2) Executors类提供四种线程池:

a) newCachedThreadPool():带缓存的线程池

  • 线程池中的线程个数对于任务所需要的线程个数,就会将空闲的线程回收
  • 一旦线程中的线程不足任务所需的线程,就会将回收的线程重新拿取出来进行使用
  • 线程数量大小没有进行限制,与能够系统支持的线程数量有关

b) newFixedThreadPool(int):固定线程数量的线程池
c) newScheduledThreadPool():可以周期性的,定时的去执行任务,线程数量没有做限制
d) newSingleThreadExecutor():单个线程的线程池,所有的任务排队执行,先来先执行


四、 案例小结

1、 多线程(分段下载、线程池)

1) 分段下载:一个文件分成了的多段进行下载,每一个线程下载文件中的某个范围

a) 下载之前需要先计算出每个线程需要下载的长度,根据文件的长度/开启线程的个数
b) 开启线程下载,通过Range 设置开始下载点和结束下载点,来获取每个线程要下载的数据范围

  • conn.setRequestProperty(“Range”, “bytes=” + start + “-” + end);


2) 线程池:对线程进行管理,会减少线程开销。
线程池采用的是newCachedThreadPool带缓存的线程池,excute执行线程。好处是,减少了线程创建和销毁所需要的时间,线程池会直接将空闲的线程拿过来进行使用。

2、 续传(单例模式、线程安全)

把线程下载的进度保存起来,保存到数据库中。
多线程对数据库的访问可能会造成数据库的表或者数据库的锁定。

1) 单例模式:
通过数据库的帮助类DBHelper来操作数据库,如果有多个实例的话,就会在多个地方操作数据库,这是不安全的,所以将DBHelper设计为单例模式。

a) 将构造方法私有化
b) 初始化静态对象引用
c) 创建静态公共方法getIntance,保证该数据库帮助类DBHelper只创建一次

  • 先判断静态对象是否为空
  • 如果为空,创建该数据库帮助类DBHelper
  • 返回该数据库帮助类DBHelper


2) 线程安全:

a) 将数据库的增删改都设置为synchronized同步方法,保证同一时间段只有一个线程在操作修改数据库内容
b) 查询数据库不需要设置synchronized同步方法,因为并没有对数据库进行操作。使用同步方法会销毁程序运行的性能,所以查询建议不用使用同步锁。
c) 修改获取查询数据库的方式:

  • 获取可读数据库:(查询)
    SQLiteDatabase database = dbHelper.getReadableDatabase();
  • 获取可写数据库:(增删改)
    SQLiteDatabase database = dbHelper.getWritableDatabase();


3、 下载(网络连接、文件操作)

1) 网络连接:通过HttpURLConnection设置和获取网络请求
2) 文件操作:关键使用RandomAccessFile文件操作流,可以在文件的任意位置开始写入,通过使用seek(index)设置文件写入的指针位置

五、 问题反馈记录

1、 ListView的convertView 复用问题

1) 【问题】当一个屏幕不能显示全部的item的时候,就会因为item的复用而出现错乱
2) 【原因】设置文件名和按钮的操作放在第一次实例化的括号里是不可取的。

2、 ListView的item问题

生成list的 mFileList的时候 注意添加的fileinfo的 id要和position一致,不然会错乱(这点不太好 比如加入删除任务功能后会比较麻烦)

3、 下载,停止按钮注意不能多次点击需要可以在代码中进行限制

4、 退出Activity时,线程仍然会下载一段时间且不会保存

可以在service的onDestory() 方法中对map mtasks遍历 改isPaused为true

5、 删除未完成下载文件后,数据库中未删除,会导致下载不完整

可以在service中初始化时判断文件是否存在

6、 ftruncate failed:ENOSPC(No space left on device)

1) 【报错地方】accessFile.setLength(length);
2) 【原因】是需要下载的内容超过了Android设备的可存储空间。

多线程断点续传(二)

7、 下载内容过大,设置Progress出现问题

1) 【问题】设置进度的时候,由于下载内容过大,mFinished * 100 会导致丢失精度
2) 【解决】intent.putExtra(“finished”, (int) (mFinished * 1.0 / mFileInfo.getLength() * 100)); 并且可以使用long类型


【Demo】: