简单的说,只要利用了HTTP协议(http://www.ietf.org/rfc/rfc2616.txt)中的如下字段来和服务器端交互,就可以实现文件下载的断点续传:

Range : 用于客户端到服务器端的请求,可通过该字段指定下载文件的某一段大小,及其单位。典型的格式如:

Range: bytes=0-499 下载第0-499字节范围的内容
Range: bytes=500-999  下载第500-999字节范围的内容
Range: bytes=-500  下载最后500字节的内容
Range: bytes=500-  下载从第500字节开始到文件结束部分的内容(这是最常用的一种格式)
Range: bytes=0-0,-1  下载第一以及最后一个字节的内容(这个看上去有点变态...)

Accept-Ranges : 用于服务器端到客户端的应答,客户端通过该字段可以判断服务器是否支持断点续传(注意RFC中注明了这一部分并不是必须的)。格式如下:

Accept-Ranges: bytes  表示支持以bytes为单位进行传输。
Accept-Ranges: none  表示不支持

Content-Ranges : 用于服务器端到客户端的应答,与Accept-Ranges在同一个报文内,通过该字段指定了返回的文件资源的字节范围。格式如下:

Content-Ranges: bytes 0-499/1234  大小为1234的文件的第0-499字节范围的内容
Content-Ranges: bytes 734-1233/1234  大小为1234字节的文件的第734-结尾范围的内容

据此我们可以知道,断点续传这个功能是需要客户端和服务器端同时支持才能完成。

Android平台面向开发者提供了DownloadManager这个服务(service),可以用来完成下载,同时异步地得到下载进度的实时更新提示。原生的浏览器,Android Market以及GMail等客户端都使用了该接口。该接口也部分的提供了断点续传功能:如果在下载过程中遇到网络错误,如信号中断等,DownloadManager会在网络恢复时尝试断点续传继续下载该文件。但不支持由用户发起的暂停然后断点续传。

要扩展该功能也不难,只要为下载任务新增一种状态(类似paused_by_user),以及相关逻辑即可,这里暂不赘述,把话题引到一些常见问题上。

1. 关于ETag

RFC中的定义有些抽象,简单的说,ETag可以用来标识/保证文件的唯一性或完整性,你可以把它看作是服务器为某个文件生产的唯一标识值,每次文件有更新该值就会变化。通过这种机制客户端可以检查某个文件在断点续传(当然它不仅仅用于断点续传)的前后是否有所改动:如果ETag改变了就应该重新下载整个文件以保证它的完整性。

但是在现实环境中,有一些服务器并不返回ETag字段,同时它又是支持断点续传的,这种情况下原生的Android就会认为服务器端不支持断点续传。这应该不是什么bug,仅仅是这么实现而已。还有更麻烦的情况是,有些服务器给了错误的ETag,但文件是从未更改的,这时候要想从客户端修改这个“bug”,估计只能忽略ETag值了。

2. 关于HTTP 206

RFC中定义了断点续传时服务器端的应答情况:如果支持且返回的内容如请求所要求的那样,是该文件的一部分,则使用HTTP 206状态码;如果不支持,或需要返回整个文件,则使用HTTP 200状态码。但是现实网络中有些服务器不管三七二十一,都返回200。没办法,如果还是想从客户端来修改这个“bug”,那就多做一些判断处理吧:如果服务器指定了“Content-Ranges”,就忽略HTTP 200的状态码。

附图一张,简述流程。

补记:有一次被问起如何在原生的Android手机上暂停一个下载任务,回头再断点续传。我想是不是可以在下载过程中将手机信号关闭,下次再打开手机信号时,那个下载任务就可以自动接着续传了(当然前提是服务器支持)...这个用例没多大实用价值,懒得实测了。

多线程下载的原理是这样的:通常服务器同时与多个用户连接,用户之间共享带宽。如果N个用户的优先级都相同,那么每个用户连接到该服务器上的实际带宽就是服务器带宽的N分之一。可以想象,如果用户数目较多,则每个用户只能占有可怜的一点带宽,下载将会是个漫长的过程。如果你通过多个线程同时与服务器连接,那么你就可以榨取到较高的带宽了。

FileDownload

任务类:
1:联网获取下载文件的信息,在SD卡上建立相应文件
2:给每个任务创建固定数目的线程,并给每个线程分配任务,然后开启
3:暂停、重启线程,根据每个线程的运行情况,通过Handler反馈信息到主线程(进度、是否结束)
源码:

[java]  view plain copy
  1. public class FileDownloader {
  2. private static final String TAG = "FileDownloader"; //方便调试语句的编写
  3. private String urlStr; //文件下载位置
  4. private int fileLength; //下载文件长度
  5. private int downloadLength; //文件已下载长度
  6. private File saveFile; //SD卡上的存储文件
  7. private int block; //每个线程下载的大小
  8. private int threadCount = 2; //该任务的线程数目
  9. private DownloadThread[] loadThreads;
  10. private Handler handler;
  11. private Boolean isFinished = false; //该任务是否完毕
  12. private Boolean isPause = false;
  13. private Context context;
  14. RandomAccessFile accessFile;
  15. protected synchronized void append(int size){ //多线程访问需加锁
  16. this.downloadLength += size;
  17. }
  18. /*protected synchronized void update(int threadId , int thisThreadDownloadlength){ //写入数据库
  19. }*/
  20. public FileDownloader(String urlStr, Handler handler,Context context){
  21. this.urlStr = urlStr;
  22. this.handler = handler;
  23. this.loadThreads = new DownloadThread[threadCount];
  24. this.context = context;
  25. try {
  26. URL url = new URL(urlStr);
  27. HttpURLConnection conn = (HttpURLConnection)url.openConnection();
  28. conn.setConnectTimeout(3000); //设置超时
  29. if(conn.getResponseCode() == 200){
  30. fileLength = conn.getContentLength(); //获取下载文件长度
  31. String tmpFile = urlStr.substring(urlStr.lastIndexOf('/')+1); //获取文件名
  32. print(tmpFile);
  33. saveFile = new File(Environment.getExternalStorageDirectory(),tmpFile);
  34. accessFile= new RandomAccessFile(saveFile,"rws");
  35. accessFile.setLength(fileLength); //设置本地文件和下载文件长度相同
  36. accessFile.close();
  37. }
  38. } catch (Exception e) {
  39. // TODO Auto-generated catch block
  40. e.printStackTrace();
  41. }
  42. }
  43. public void Download(CompleteListener listener) throws Exception{
  44. URL url = new URL(urlStr);
  45. HttpURLConnection conn = (HttpURLConnection)url.openConnection();
  46. conn.setConnectTimeout(3000); //设置超时
  47. if(conn.getResponseCode() == 200){
  48. //发送消息设置进度条最大长度
  49. Message msg = new Message();
  50. msg.what = 0;
  51. msg.getData().putInt("filelength", fileLength);
  52. handler.sendMessage(msg);
  53. block = fileLength%threadCount == 0? fileLength/threadCount : fileLength/threadCount+1; //计算线程下载量
  54. print(Integer.toString(block));
  55. for(int i = 0 ; i < threadCount ; i++){
  56. print("哈哈,你妹啊");
  57. this.loadThreads = new DownloadThread(context,this,urlStr,saveFile,block,i); //新建线程开始下载
  58. print("嘿嘿");
  59. this.loadThreads.start();
  60. print("线程:"+Integer.toString(i)+" 开始下载");
  61. }
  62. while(!isPause && !this.isFinished){
  63. this.isFinished = true;
  64. Thread.sleep(900);
  65. for(int i = 0 ; i < threadCount ; i++){
  66. if(loadThreads != null && !loadThreads.isFinished()){
  67. this.isFinished = false;
  68. }
  69. }
  70. Message msg2 = new Message();
  71. msg2.what = 1;
  72. msg2.getData().putInt("currentlength", downloadLength); //
  73. handler.sendMessage(msg2); //发送 消息更新进度条
  74. //print(Integer.toString(downloadLength));
  75. }
  76. if(this.isFinished && listener != null){
  77. listener.isComplete(downloadLength);
  78. }
  79. }
  80. }
  81. private void print(String msg){ //打印提示消息
  82. Log.d(FileDownloader.TAG,msg);
  83. }
  84. public void setPause(){
  85. isPause = true; //该任务暂停
  86. for(int i = 0 ;i < threadCount ; i++){
  87. if(loadThreads!= null && !loadThreads.isFinished()){
  88. loadThreads.setPause(); //设置该线程暂停
  89. print(Integer.toString(i)+"草泥马");
  90. }
  91. }
  92. }
  93. public void setResume(final CompleteListener listener) throws Exception{
  94. isPause = false;
  95. this.downloadLength = 0;
  96. this.Download(new CompleteListener(){
  97. public void isComplete(int size) {
  98. listener.isComplete(size);
  99. print("listener");
  100. }
  101. });
  102. }
  103. public Boolean isFinished(){
  104. return this.isFinished;
  105. }
  106. }

DownloadThread
下载线程类:
1:需要参数:下载地址 、存储位置、起始结束下载位置、结束位置。  (时间、数据库等信息)
2:读取数据库判断是否有下载记录,然后根据情况计算下载的起始与结束位置,进行下载
3:每读取一定数据需要通过任务类更新任务显示,并写入数据库(多线程访问数据库需要加锁)
4:根据任务类的需要,暂停则让该线程运行结束,而非阻塞该线程(考虑到android内存的需要)
源码:

[java]  view plain copy
  1. public class DownloadThread extends Thread{
  2. private String urlStr; //下载地址
  3. private int startPosition; //开始下载位置
  4. private int downloadLength=0; //已下载字节数
  5. private int endPosition; //结束位置
  6. private File saveFile; //文件存储位置
  7. private int threadId; //线程ID
  8. private FileDownloader fileDownloader; //文件任务类
  9. private Boolean isFinished = false; //文件快是否下载完毕
  10. private DatabaseUtil databaseUtil;
  11. private Boolean isPause = false; //默认为运行(没有暂停)
  12. private Context context;
  13. public DownloadThread(String urlStr){
  14. this.urlStr = urlStr;
  15. }
  16. public DownloadThread(Context context,FileDownloader fileDownloader,String urlStr , File file , int block ,int threadId){
  17. this.context = context;
  18. this.fileDownloader = fileDownloader; //
  19. this.urlStr = urlStr;
  20. this.saveFile = file;
  21. this.threadId = threadId;
  22. this.startPosition = threadId * block; //计算起始下载位置
  23. this.endPosition = (threadId+1) * block -1; //计算下载结束位置
  24. }
  25. public void run(){
  26. try{
  27. RandomAccessFile accessFile = new RandomAccessFile(saveFile,"rwd");
  28. databaseUtil = new DatabaseUtil(context); //context设置需思考
  29. ItemRecord record = databaseUtil.query(urlStr, threadId); //查询是否有记录存在
  30. if(record != null){
  31. downloadLength = record.getDownloadLength(); //读取已下载字节数
  32. fileDownloader.append(downloadLength); //更新总下载字节数
  33. print("线程"+Integer.toString(threadId)+"存在记录"+Integer.toString(downloadLength));
  34. }else{
  35. synchronized(DatabaseUtil.lock){
  36. databaseUtil.insert(urlStr, threadId, downloadLength); //插入未完成线程任务
  37. print("线程"+Integer.toString(threadId)+"不存在记录");
  38. }
  39. }
  40. accessFile.seek(startPosition+downloadLength); //设置写入起始位置
  41. if((endPosition+1) == (startPosition + downloadLength) || endPosition == (startPosition + downloadLength)){
  42. print(Integer.toString(endPosition)+"endPosition");
  43. print(Integer.toString(startPosition)+"startPosition");
  44. print(Integer.toString(downloadLength)+"downloadLength");
  45. isFinished = true; //更新下载标志
  46. print("线程:"+Integer.toString(threadId)+" 曾成功下载");
  47. }else{
  48. URL url = new URL(urlStr);
  49. HttpURLConnection conn = (HttpURLConnection)url.openConnection();
  50. conn.setConnectTimeout(3000); //设置超时
  51. conn.setRequestMethod("GET");
  52. int tmpStartPosition = startPosition + downloadLength;
  53. conn.setRequestProperty("Range", "bytes=" + tmpStartPosition + "-" + endPosition);
  54. if(conn.getResponseCode()==206){
  55. InputStream inStream = conn.getInputStream();
  56. byte[] buffer = new byte[1024];
  57. int len = 0;
  58. while(!isPause && (len = inStream.read(buffer))!= -1){
  59. accessFile.write(buffer, 0, len); //写入文件
  60. fileDownloader.append(len); //动态更新下载数、
  61. downloadLength += len; //已下载数更新
  62. //写入数据库
  63. synchronized(DatabaseUtil.lock){
  64. databaseUtil.update(urlStr, threadId, downloadLength);
  65. }
  66. }
  67. if((endPosition+1) == (startPosition + downloadLength) || endPosition == (startPosition + downloadLength)){
  68. print(Integer.toString(endPosition)+"endPosition");
  69. print(Integer.toString(startPosition)+"startPosition");
  70. print(Integer.toString(downloadLength)+"downloadLength");
  71. isFinished = true; //更新下载标志
  72. print("线程:"+Integer.toString(threadId)+" 下载完毕");
  73. }else{
  74. print(Integer.toString(endPosition)+"endPosition");
  75. print(Integer.toString(startPosition)+"startPosition");
  76. print(Integer.toString(downloadLength)+"downloadLength");
  77. print("线程:"+Integer.toString(threadId)+" 未下载完!");
  78. }
  79. }else{
  80. print(conn.getResponseMessage());
  81. print(Integer.toString(conn.getResponseCode()));
  82. print("擦,线程:"+Integer.toString(threadId)+" 没开始下载");
  83. }
  84. }
  85. }catch(Exception e){
  86. e.printStackTrace();
  87. }
  88. }
  89. //返回下载结束的标志
  90. public Boolean isFinished(){
  91. return this.isFinished;
  92. }
  93. //设置暂停
  94. public void setPause(){
  95. this.isPause = true;
  96. }
  97. private void print(String msg){
  98. Log.d("DownloadThrea",msg);
  99. }
  100. }

DatabaseHelper
建立数据库文件,并建表
源码:

[java]  view plain copy
  1. public class DatabaseHelper extends SQLiteOpenHelper{
  2. //private SQLiteDatabase database;
  3. public DatabaseHelper(Context context , String name ,CursorFactory factory , int version) {
  4. super(context, "downloadFile.db", null,1);
  5. // TODO Auto-generated constructor stub
  6. }
  7. @Override
  8. public void onCreate(SQLiteDatabase db) {
  9. Cursor cursor = null;
  10. try {
  11. Log.d("哈哈哈","leixun");
  12. cursor = db.rawQuery("SELECT * FROM sqlite_master WHERE type = ? AND name = ?", new String[]{"table","info"});
  13. if(cursor.moveToNext()){
  14. Log.d("DatabaseHelper","该表已经存在");
  15. }else{
  16. Log.d("DatabaseHelper","该表不存在 ,马上建立");
  17. db.execSQL("CREATE TABLE info (path VARCHAR(1024), threadid INTEGER , " +
  18. "downloadlength INTEGER , PRIMARY KEY(path,threadid))");
  19. }
  20. cursor.close();
  21. } catch (Exception e) {
  22. e.printStackTrace();
  23. }
  24. /*db.execSQL("CREATE TABLE info (path VARCHAR(1024), threadid INTEGER , " +
  25. "downloadlength INTEGER , PRIMARY KEY(path,threadid))");*/
  26. }
  27. @Override
  28. public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  29. // TODO Auto-generated method stub
  30. }
  31. }

DatabaseUtil
1:增删改查任务列表
2:通过静态常量lock实现数据库写入时的并发锁功能
源码:

[java]  view plain copy
  1. public class DatabaseUtil {
  2. private DatabaseHelper helper;
  3. public static final String lock = "访问";
  4. public DatabaseUtil(Context context){
  5. helper = new DatabaseHelper(context, "downloadFile.db", null,1);
  6. }
  7. //添加记录
  8. public void insert(String path , int threadid , int downloadlength){
  9. SQLiteDatabase db = helper.getWritableDatabase();
  10. db.execSQL("INSERT INTO info(path,threadid,downloadlength) VALUES (?,?,?)", new Object[]{path,threadid,downloadlength});
  11. print("成功添加");
  12. db.close();
  13. }
  14. //删除记录
  15. public void delete(String path , int threadid){
  16. SQLiteDatabase db = helper.getWritableDatabase();
  17. db.execSQL("DELETE FROM info WHERE path = ? AND threadid = ?",new Object[]{path,threadid});
  18. print("成功删除");
  19. db.close();
  20. }
  21. //更新记录
  22. public void update(String path , int threadid , int downloadlength){
  23. SQLiteDatabase db = helper.getWritableDatabase();
  24. //print("准备更新1");
  25. db.execSQL("UPDATE info SET downloadlength = ? WHERE path = ? AND threadid = ?",new Object[]{downloadlength,path,threadid});
  26. //print("成功更新2");
  27. db.close();
  28. }
  29. //查询线程是否存在
  30. public ItemRecord query(String path, int threadid){
  31. SQLiteDatabase db = helper.getWritableDatabase();
  32. Cursor c = db.rawQuery("SELECT path,threadid,downloadlength FROM info WHERE path = ? AND threadid = ?", new String[]{path,Integer.toString(threadid)});
  33. ItemRecord record = null;
  34. if(c.moveToNext()){
  35. record = new ItemRecord(c.getString(0),c.getInt(1),c.getInt(2));
  36. }
  37. c.close();
  38. db.close();
  39. return record;
  40. }
  41. //查询未完成任务
  42. public List<String> query(String path){
  43. print("List<String> query 开始");
  44. SQLiteDatabase db = helper.getWritableDatabase();
  45. Cursor c = db.rawQuery("SELECT DISTINCT path FROM info", new String[]{path});
  46. List<String> arrayList = new ArrayList<String>();
  47. while(c.moveToNext()){
  48. arrayList.add(c.getString(0));
  49. }
  50. c.close();
  51. db.close();
  52. print("List<String> query 结束");
  53. return arrayList;
  54. }
  55. //调试信息输出
  56. private void print(String msg){
  57. Log.d("DatabaseUtil",msg);
  58. }
  59. }

ItemRecord
任务记录类,方便数据库操作而建立的任务记录类

[java]  view plain copy
  1. public class ItemRecord {
  2. private String path;
  3. private int threadId;
  4. private int downloadLength;
  5. public ItemRecord(String path,int threadId,int downloadLength){
  6. this.path = path;
  7. this.threadId = threadId;
  8. this.downloadLength = downloadLength;
  9. }
  10. public String getPath() {
  11. return path;
  12. }
  13. public void setPath(String path) {
  14. this.path = path;
  15. }
  16. public int getThreadId() {
  17. return threadId;
  18. }
  19. public void setThreadId(int threadId) {
  20. this.threadId = threadId;
  21. }
  22. public int getDownloadLength() {
  23. return downloadLength;
  24. }
  25. public void setDownloadLength(int downloadLength) {
  26. this.downloadLength = downloadLength;
  27. }
  28. }

ApplicationUrlSpinner操作用来完成应用名称与下载地址的对应关系源码:

[java]  view plain copy
  1. public class ApplicationUrl {
  2. private String applicationName = "";
  3. private String applicationUrl = "";
  4. public String getApplicationName() {
  5. return applicationName;
  6. }
  7. public void setApplicationName(String applicationName) {
  8. this.applicationName = applicationName;
  9. }
  10. public String getApplicationUrl() {
  11. return applicationUrl;
  12. }
  13. public void setApplicationUrl(String applicationUrl) {
  14. this.applicationUrl = applicationUrl;
  15. }
  16. public String toString() {
  17. // TODO Auto-generated method stub
  18. return applicationName;
  19. }
  20. }

CompleteListener下载完成判断接口:

[java]  view plain copy
  1. public interface CompleteListener {
  2. public void isComplete(int size);
  3. }

MainActivity
1:动态添加下载任务
2:根据任务动态添加布局,并给相应按钮添加监听,开启下载任务
源码:

[java]  view plain copy
  1. public class MaintActivity extends Activity{
  2. private LayoutInflater inflater;
  3. private LinearLayout root;
  4. private Spinner spinnerApp;
  5. private List<ApplicationUrl> appUrl;
  6. private ArrayAdapter appAdapter;
  7. private Button startButton;
  8. FileDownloader fileDownloader;
  9. private String url5 = "http://update.android.doplive.com.cn/dopoolv2.4.player.apk";
  10. private String url5App = "Dopool手机电视";
  11. private String url = "http://www.ipmsg.org.cn/downloads/ipmsg.apk";
  12. private String urlApp = "飞鸽传书";
  13. private String url2 = "http://down1.cnmo.com/app/a135/aiku_2.0.apk";
  14. private String url2App = "爱酷天气";
  15. private ApplicationUrl userChoose;
  16. @Override
  17. public void onCreate(Bundle savedInstanceState){
  18. super.onCreate(savedInstanceState);
  19. setContentView(R.layout.main_layout);
  20. inflater = (LayoutInflater)this.getSystemService(LAYOUT_INFLATER_SERVICE);
  21. root = (LinearLayout)findViewById(R.id.root);
  22. spinnerApp = (Spinner)findViewById(R.id.urlSpinner);
  23. startButton = (Button)findViewById(R.id.startBtn);
  24. //createDownloadTask(url); //创建任务 添加View
  25. //createDownloadTask(url2);
  26. init();
  27. }
  28. public void init(){
  29. //-----------------给Spinner制作数据----------------
  30. appUrl = new ArrayList<ApplicationUrl>();
  31. ApplicationUrl[] tmp = new ApplicationUrl[3];
  32. for(int i = 0 ; i<3;i++){
  33. tmp = new ApplicationUrl();
  34. }
  35. tmp[0].setApplicationName(urlApp);
  36. tmp[0].setApplicationUrl(url);
  37. tmp[1].setApplicationName(url2App);
  38. tmp[1].setApplicationUrl(url2);
  39. tmp[2].setApplicationName(url5App);
  40. tmp[2].setApplicationUrl(url5);
  41. appUrl.add(tmp[0]);
  42. appUrl.add(tmp[1]);
  43. appUrl.add(tmp[2]);
  44. userChoose = new ApplicationUrl();
  45. userChoose.setApplicationName(tmp[0].getApplicationName());
  46. userChoose.setApplicationUrl(tmp[0].getApplicationUrl());
  47. appAdapter = new ArrayAdapter(MaintActivity.this,android.R.layout.simple_spinner_item,appUrl);
  48. appAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
  49. spinnerApp.setAdapter(appAdapter);
  50. spinnerApp.setSelection(0, true);
  51. spinnerApp.setOnItemSelectedListener(new OnItemSelectedListener(){
  52. @Override
  53. public void onItemSelected(AdapterView<?> arg0, View arg1,
  54. int position, long arg3) {
  55. print("onItemSelected:"+appUrl.get(position).getApplicationName()+Integer.toString(position));
  56. userChoose.setApplicationName(appUrl.get(position).getApplicationName());
  57. userChoose.setApplicationUrl(appUrl.get(position).getApplicationUrl());
  58. }
  59. @Override
  60. public void onNothingSelected(AdapterView<?> arg0) {
  61. // TODO Auto-generated method stub
  62. }
  63. });
  64. startButton.setOnClickListener(new View.OnClickListener() {
  65. @Override
  66. public void onClick(View v) {
  67. if(!appUrl.isEmpty()){
  68. createDownloadTask(userChoose);
  69. }else{
  70. Toast.makeText(MaintActivity.this, "没有应用程序可下载!", Toast.LENGTH_LONG).show();
  71. }
  72. int count = appUrl.size();
  73. print(Integer.toString(count));
  74. for(int i = 0 ;i< count;i++){
  75. if(appUrl.get(i).getApplicationName().equals(userChoose.getApplicationName())){
  76. print("+++++++++++++++++"+Integer.toString(i)+"++++++++++++++++");
  77. printappUrl();
  78. appUrl.remove(i);
  79. printappUrl();
  80. count = appUrl.size();
  81. print("for循环找到"+Integer.toString(count));
  82. appAdapter.notifyDataSetChanged();
  83. if(!appUrl.isEmpty()){
  84. spinnerApp.setSelection(0,true);
  85. userChoose.setApplicationName(appUrl.get(0).getApplicationName()); //擦 必须这样赋值
  86. userChoose.setApplicationUrl(appUrl.get(0).getApplicationUrl());
  87. }
  88. printappUrl();
  89. print(userChoose.getApplicationName()+"数据源已更新"+Integer.toString(count));
  90. }else{
  91. print("数据源未更新");
  92. }
  93. }
  94. }
  95. });
  96. }
  97. @Override
  98. public boolean onCreateOptionsMenu(Menu menu) {
  99. getMenuInflater().inflate(R.menu.main_layout, menu);
  100. return true;
  101. }
  102. private void createDownloadTask(ApplicationUrl path){
  103. LayoutInflater inflater = (LayoutInflater)this.getSystemService(LAYOUT_INFLATER_SERVICE); //获取系统服务layoutinflater
  104. LinearLayout linearLayout = (LinearLayout)inflater.inflate(R.layout.download_layout, null);
  105. LinearLayout childLayout = (LinearLayout)linearLayout.getChildAt(0);
  106. ProgressBar progressBar = (ProgressBar)childLayout.getChildAt(0); //获取进度条
  107. TextView textView = (TextView)childLayout.getChildAt(1); //获取textview
  108. Button button = (Button)linearLayout.getChildAt(1); //获取下载按钮
  109. try{
  110. button.setOnClickListener(new MyListener(progressBar,textView,path));
  111. root.addView(linearLayout); //将创建的View添加到主线程 的布局页面中
  112. }catch(Exception e){
  113. e.printStackTrace();
  114. }
  115. }
  116. private final class MyListener implements OnClickListener{
  117. private ProgressBar pb ;
  118. private TextView tv;
  119. private String url;
  120. private FileDownloader fileDownloader;
  121. private String name ;
  122. public MyListener(ProgressBar pb ,TextView tv, ApplicationUrl url){
  123. this.pb = pb;
  124. this.tv = tv;
  125. this.url = url.getApplicationUrl();
  126. this.name = url.getApplicationName();
  127. }
  128. public void onClick(View v) {
  129. final Button pauseButton = (Button)v;
  130. final Handler mainHandler = new Handler(){
  131. @Override
  132. public void handleMessage(Message msg) {
  133. switch(msg.what){
  134. case 2:
  135. pauseButton.setText("安装");
  136. break;
  137. }
  138. }
  139. };
  140. if(pauseButton.getText().equals("开始")){
  141. pauseButton.setText("暂停");
  142. new Thread(){
  143. public void run(){
  144. try{
  145. fileDownloader = new FileDownloader(url,handler,MaintActivity.this);
  146. fileDownloader.Download(new CompleteListener(){
  147. public void isComplete(int size) {
  148. Message msg = new Message();
  149. msg.what = 2;
  150. mainHandler.sendMessage(msg);
  151. }
  152. });
  153. }catch(Exception e){
  154. e.printStackTrace();
  155. }
  156. }
  157. }.start();
  158. }else if(pauseButton.getText().equals("暂停")){
  159. print("暂停");
  160. fileDownloader.setPause();
  161. pauseButton.setText("继续");
  162. }else if(pauseButton.getText().equals("继续")){
  163. print("继续");
  164. pauseButton.setText("暂停");
  165. new Thread(){
  166. public void run(){
  167. try{
  168. fileDownloader.setResume(new CompleteListener(){
  169. public void isComplete(int size) {
  170. print("妹纸");
  171. Message msg = new Message();
  172. msg.what = 2;
  173. mainHandler.sendMessage(msg);
  174. }
  175. });
  176. }catch(Exception e){
  177. e.printStackTrace();
  178. }
  179. }
  180. }.start();
  181. }else if(pauseButton.getText().equals("安装")){
  182. Intent intent = new Intent(Intent.ACTION_VIEW);
  183. intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  184. String installPath = Environment.getExternalStorageDirectory() + "/"+url.substring(url.lastIndexOf('/')+1);
  185. print(installPath);
  186. intent.setDataAndType(Uri.fromFile(new File(installPath)), "application/vnd.android.package-archive");
  187. MaintActivity.this.startActivity(intent);
  188. }
  189. }
  190. private Handler handler = new Handler(){
  191. int fileLength = 1;
  192. @Override
  193. public void handleMessage(Message msg) {
  194. switch(msg.what){
  195. case 0: //得到进度条的最大长度
  196. fileLength = msg.getData().getInt("filelength");
  197. pb.setMax(fileLength); //设置进度条最大长度
  198. break;
  199. case 1: //设置进度条现在的长度
  200. int currentLength = msg.getData().getInt("currentlength");
  201. pb.setProgress(currentLength); //设置当前进度条长度
  202. tv.setText(name+" 已下载:"+currentLength*100 / fileLength+"%");
  203. if(currentLength == fileLength){
  204. tv.setText(name+" 下载完成:"+currentLength*100 / fileLength+"%");
  205. }
  206. break;
  207. default: print("handleMessage msg.what 没有曲子好");
  208. }
  209. }
  210. };
  211. }
  212. /**
  213. * 调试信息输出
  214. * @param msg
  215. */
  216. private void print(String msg){
  217. Log.d("MainActivity",msg);
  218. }
  219. private void printappUrl(){
  220. Log.d("=========================","=========================");
  221. for(int i = 0;appUrl!=null && i<appUrl.size();i++){
  222. print(appUrl.get(i).getApplicationName());
  223. }
  224. Log.d("=========================","=========================");
  225. }
  226. }

源码下载

多线程多任务断点下载相关推荐

  1. 高并发多线程分片断点下载

    基于Java的高并发多线程分片断点下载 首先直接看测试情况: 单线程下载72MB文件 7线程并发分片下载72MB文件: 下载效率提高2-3倍,当然以上测试结果还和设备CPU核心数.网络带宽息息相关. ...

  2. android实现多任务多线程支持断点下载的下载软件

    运行效果图: 多任务多线程下载并不麻烦,只要思路清晰,逻辑清晰正确,是很好实现的.我最后遇到的纠结问题是数据库的操作上,我是拿数据库来存储下载信息的,所以在数据库的关闭上遇到了麻烦.上面那个版本是建立 ...

  3. Java使用HttpUrlConnection实现多线程断点下载

    相信很多同学在面试的时候,经常会被面试官问到这么一个问题:请问如何实现断点下载,即在文件未下载完成时,保存进度,在下次继续下载.要实现这个功能其实并不难,只要使用一个临时文件记录当前的下载进度,然后在 ...

  4. 快速下载助手1.1--添加断点下载

    在上一章中实现了多线程的断点下载,将快速下载助手添加断点下载功能,明天实现了速率统计功能 效果图如下: 打印信息如下: 欢迎使用快速下载助手-->并不是线程多就下载的快! 文件夹已经存在 默认的 ...

  5. Python Tkinter 下载器 多任务下载+多线程下载+多任务断点续存

    功能介绍: 这次的下载器特点:多线程下载 + 多任务下载 + 多任务断点续存 视频演示: Python Tkinter 系列 - 下载器 界面: 轮子 文件大小 界面:Tkinter 下载功能 :re ...

  6. java多线程下载文件(断点下载、进度展示、网速展示)

    引言 多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销. 多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的. 一个线程可以创建和撤销另一个线程:同一个进程中的多个线程之间可 ...

  7. Android多线程断点下载

    到华为后,信息管理特别严格,文件不能外发.所以好久都没写博客了,今天周日,老婆非要我学习.就闲来无事,写一篇博客,呵呵-- 前段时间,项目中提到了断点下载apk并静默安装的需求.本打算用应用市场成熟的 ...

  8. iOS开发网络篇—多线程断点下载

    iOS开发网络篇-多线程断点下载 说明:本文介绍多线程断点下载.项目中使用了苹果自带的类,实现了同时开启多条线程下载一个较大的文件.因为实现过程较为复杂,所以下面贴出完整的代码. 实现思路:下载开始, ...

  9. Java多线程断点下载

    多线程下载已经提高了下载的效率,但是当一些特殊情况发生的时候,我们需要对程序进行处理,这样效率会更高.比如,断电断网等造成下载中断,那么我们下一次又要重新开始下载,这样效率底下,所以我们可以考虑使用断 ...

最新文章

  1. [Python]urllib库的简单应用-实现北航宿舍自动上网
  2. 如何解决MySQL连接超时关闭
  3. rancher安装mysql_四、rancher搭建Mysql集群化部署,做到同步备份
  4. python处理csv数据
  5. sendkeys.send 始终输出英文._PLC的三种输出方式,你知道有哪些吗?
  6. Java学习之SpringBoot整合SSM Demo
  7. (转)解决RabbitMQ service is already present - only updating service parameters
  8. editplus查找文件中的字符串
  9. Neutron Vlan Network 原理- 每天5分钟玩转 OpenStack(92)
  10. c语言第六版题目,C primer plus 第六版 第6版 002章 第二章 复习题 答案 中文
  11. Java的图标和由来
  12. android手机无分区无法刷机,adb sideload 刷机教程:当你手机无法开机,内存里没有ROM时......
  13. 宣传6个9的可靠性就真的可靠吗
  14. 淘特,阿里在下沉市场的一把好刀
  15. 2018最受欢迎测试工具
  16. 【第三方API】顺丰电子面单SDK调用总结-java
  17. mysql数据库中到底能建多少张表?(单实例下单个库)
  18. 计算机专业考研可以考哪些研究所,计算机专业考研可以考哪些专业
  19. 基于SSM企业生产计划管理系统
  20. 新华三半导体:初芯如磐,笃行致远

热门文章

  1. echarts极坐标Polar结合热力(维彩、色阶)图Heatmap
  2. OLAP(三):Impala介绍 、 (和hive/spark对比)、COMPUTE STATS
  3. java 生成dump文件_程序自动生成Dump文件
  4. java通讯方式_Java线程通讯方式 - osc_63rgy8af的个人空间 - OSCHINA - 中文开源技术交流社区...
  5. xampp、lamp、lampp
  6. ant-design vue的tree组件点击小三角符号展开,触点太小的问题
  7. 诺基亚7 刷android p,诺基亚真良心:所有手机先升 Android O,再升 Android P
  8. css3禁止选中文本图片
  9. 帮G1刷新的hboot和radio
  10. 戴志康谈微信二维码运营:学会注重结果