一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Android - Android 使用AsyncTask實現多線程斷點續傳

Android 使用AsyncTask實現多線程斷點續傳

2022-02-25 15:38殘劍_ Android

本文將詳細講解如何使用AsyncTask來實現多線程的斷點續傳下載功能,感興趣的朋友跟隨腳本之家小編一起學習吧

前面一篇博客《AsyncTask實現斷點續傳》講解了如何實現單線程下的斷點續傳,也就是一個文件只有一個線程進行下載。

    對于大文件而言,使用多線程下載就會比單線程下載要快一些。多線程下載相比單線程下載要稍微復雜一點,本博文將詳細講解如何使用AsyncTask來實現多線程的斷點續傳下載。

一、實現原理

  多線程下載首先要通過每個文件總的下載線程數(我這里設定5個)來確定每個線程所負責下載的起止位置。

?
1
2
3
4
5
6
7
8
9
long blockLength = mFileLength / DEFAULT_POOL_SIZE;
 for (int i = 0; i < DEFAULT_POOL_SIZE; i++) {
  long beginPosition = i * blockLength;//每條線程下載的開始位置
  long endPosition = (i + 1) * blockLength;//每條線程下載的結束位置
  if (i == (DEFAULT_POOL_SIZE - 1)) {
   endPosition = mFileLength;//如果整個文件的大小不為線程個數的整數倍,則最后一個線程的結束位置即為文件的總長度
  }
  ......
 }

  這里需要注意的是,文件大小往往不是線程個數的整數倍,所以最后一個線程的結束位置需要設置為文件長度。

  確定好每個線程的下載起止位置之后,需要設置http請求頭來下載文件的指定位置:

?
1
2
3
//設置下載的數據位置beginPosition字節到endPosition字節
Header header_size = new BasicHeader("Range", "bytes=" + beginPosition + "-" + endPosition);
request.addHeader(header_size);

  以上是多線程下載的原理,但是還要實現斷點續傳需要在每次暫停之后記錄每個線程已下載的大小,下次繼續下載時從上次下載后的位置開始下載。一般項目中都會存數據庫中,我這里為了簡單起見直接存在了SharedPreferences中,已下載url和線程編號作為key值。

?
1
2
3
4
5
6
7
8
9
10
11
12
@Override
  protected void onPostExecute(Long aLong) {
   Log.i(TAG, "download success ");
   //下載完成移除記錄
   mSharedPreferences.edit().remove(currentThreadIndex).commit();
  }
  @Override
  protected void onCancelled() {
   Log.i(TAG, "download cancelled ");
   //記錄已下載大小current
   mSharedPreferences.edit().putLong(currentThreadIndex, current).commit();
  }

下載的時候,首先獲取已下載位置,如果已經下載過,就從上次下載后的位置開始下載:

?
1
2
3
4
5
6
7
8
9
10
//獲取之前下載保存的信息,從之前結束的位置繼續下載
 //這里加了判斷file.exists(),判斷是否被用戶刪除了,如果文件沒有下載完,但是已經被用戶刪除了,則重新下載
 long downedPosition = mSharedPreferences.getLong(currentThreadIndex, 0);
 if(file.exists() && downedPosition != 0) {
  beginPosition = beginPosition + downedPosition;
  current = downedPosition;
  synchronized (mCurrentLength) {
   mCurrentLength += downedPosition;
  }
 }

二、完整代碼

?
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
package com.bbk.lling.multithreaddownload;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicHeader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class MainActivity extends Activity {
 private static final String TAG = "MainActivity";
 private static final int DEFAULT_POOL_SIZE = 5;
 private static final int GET_LENGTH_SUCCESS = 1;
 //下載路徑
 private String downloadPath = Environment.getExternalStorageDirectory() +
   File.separator + "download";
// private String mUrl = "http://ftp.neu.edu.cn/mirrors/eclipse/technology/epp/downloads/release/juno/SR2/eclipse-java-juno-SR2-linux-gtk-x86_64.tar.gz";
 private String mUrl = "http://p.gdown.baidu.com/c4cb746699b92c9b6565cc65aa2e086552651f73c5d0e634a51f028e32af6abf3d68079eeb75401c76c9bb301e5fb71c144a704cb1a2f527a2e8ca3d6fe561dc5eaf6538e5b3ab0699308d13fe0b711a817c88b0f85a01a248df82824ace3cd7f2832c7c19173236";
 private ProgressBar mProgressBar;
 private TextView mPercentTV;
 SharedPreferences mSharedPreferences = null;
 long mFileLength = 0;
 Long mCurrentLength = 0L;
 private InnerHandler mHandler = new InnerHandler();
 //創建線程池
 private Executor mExecutor = Executors.newCachedThreadPool();
 private List<DownloadAsyncTask> mTaskList = new ArrayList<DownloadAsyncTask>();
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  mProgressBar = (ProgressBar) findViewById(R.id.progressbar);
  mPercentTV = (TextView) findViewById(R.id.percent_tv);
  mSharedPreferences = getSharedPreferences("download", Context.MODE_PRIVATE);
  //開始下載
  findViewById(R.id.begin).setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
    new Thread() {
     @Override
     public void run() {
      //創建存儲文件夾
      File dir = new File(downloadPath);
      if (!dir.exists()) {
       dir.mkdir();
      }
      //獲取文件大小
      HttpClient client = new DefaultHttpClient();
      HttpGet request = new HttpGet(mUrl);
      HttpResponse response = null;
      try {
       response = client.execute(request);
       mFileLength = response.getEntity().getContentLength();
      } catch (Exception e) {
       Log.e(TAG, e.getMessage());
      } finally {
       if (request != null) {
        request.abort();
       }
      }
      Message.obtain(mHandler, GET_LENGTH_SUCCESS).sendToTarget();
     }
    }.start();
   }
  });
  //暫停下載
  findViewById(R.id.end).setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
    for (DownloadAsyncTask task : mTaskList) {
     if (task != null && (task.getStatus() == AsyncTask.Status.RUNNING || !task.isCancelled())) {
      task.cancel(true);
     }
    }
    mTaskList.clear();
   }
  });
 }
 /**
  * 開始下載
  * 根據待下載文件大小計算每個線程下載位置,并創建AsyncTask
  */
 private void beginDownload() {
  mCurrentLength = 0L;
  mPercentTV.setVisibility(View.VISIBLE);
  mProgressBar.setProgress(0);
  long blockLength = mFileLength / DEFAULT_POOL_SIZE;
  for (int i = 0; i < DEFAULT_POOL_SIZE; i++) {
   long beginPosition = i * blockLength;//每條線程下載的開始位置
   long endPosition = (i + 1) * blockLength;//每條線程下載的結束位置
   if (i == (DEFAULT_POOL_SIZE - 1)) {
    endPosition = mFileLength;//如果整個文件的大小不為線程個數的整數倍,則最后一個線程的結束位置即為文件的總長度
   }
   DownloadAsyncTask task = new DownloadAsyncTask(beginPosition, endPosition);
   mTaskList.add(task);
   task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mUrl, String.valueOf(i));
  }
 }
 /**
  * 更新進度條
  */
 synchronized public void updateProgress() {
  int percent = (int) Math.ceil((float)mCurrentLength / (float)mFileLength * 100);
//  Log.i(TAG, "downloading " + mCurrentLength + "," + mFileLength + "," + percent);
  if(percent > mProgressBar.getProgress()) {
   mProgressBar.setProgress(percent);
   mPercentTV.setText("下載進度:" + percent + "%");
   if (mProgressBar.getProgress() == mProgressBar.getMax()) {
    Toast.makeText(MainActivity.this, "下載結束", Toast.LENGTH_SHORT).show();
   }
  }
 }
 @Override
 protected void onDestroy() {
  for(DownloadAsyncTask task: mTaskList) {
   if(task != null && task.getStatus() == AsyncTask.Status.RUNNING) {
    task.cancel(true);
   }
   mTaskList.clear();
  }
  super.onDestroy();
 }
 /**
  * 下載的AsyncTask
  */
 private class DownloadAsyncTask extends AsyncTask<String, Integer , Long> {
  private static final String TAG = "DownloadAsyncTask";
  private long beginPosition = 0;
  private long endPosition = 0;
  private long current = 0;
  private String currentThreadIndex;
  public DownloadAsyncTask(long beginPosition, long endPosition) {
   this.beginPosition = beginPosition;
   this.endPosition = endPosition;
  }
  @Override
  protected Long doInBackground(String... params) {
   Log.i(TAG, "downloading");
   String url = params[0];
   currentThreadIndex = url + params[1];
   if(url == null) {
    return null;
   }
   HttpClient client = new DefaultHttpClient();
   HttpGet request = new HttpGet(url);
   HttpResponse response = null;
   InputStream is = null;
   RandomAccessFile fos = null;
   OutputStream output = null;
   try {
    //本地文件
    File file = new File(downloadPath + File.separator + url.substring(url.lastIndexOf("/") + 1));
    //獲取之前下載保存的信息,從之前結束的位置繼續下載
    //這里加了判斷file.exists(),判斷是否被用戶刪除了,如果文件沒有下載完,但是已經被用戶刪除了,則重新下載
    long downedPosition = mSharedPreferences.getLong(currentThreadIndex, 0);
    if(file.exists() && downedPosition != 0) {
     beginPosition = beginPosition + downedPosition;
     current = downedPosition;
     synchronized (mCurrentLength) {
      mCurrentLength += downedPosition;
     }
    }
    //設置下載的數據位置beginPosition字節到endPosition字節
    Header header_size = new BasicHeader("Range", "bytes=" + beginPosition + "-" + endPosition);
    request.addHeader(header_size);
    //執行請求獲取下載輸入流
    response = client.execute(request);
    is = response.getEntity().getContent();
    //創建文件輸出流
    fos = new RandomAccessFile(file, "rw");
    //從文件的size以后的位置開始寫入,其實也不用,直接往后寫就可以。有時候多線程下載需要用
    fos.seek(beginPosition);
    byte buffer [] = new byte[1024];
    int inputSize = -1;
    while((inputSize = is.read(buffer)) != -1) {
     fos.write(buffer, 0, inputSize);
     current += inputSize;
     synchronized (mCurrentLength) {
      mCurrentLength += inputSize;
     }
     this.publishProgress();
     if (isCancelled()) {
      return null;
     }
    }
   } catch (MalformedURLException e) {
    Log.e(TAG, e.getMessage());
   } catch (IOException e) {
    Log.e(TAG, e.getMessage());
   } finally{
    try{
     /*if(is != null) {
      is.close();
     }*/
     if (request != null) {
      request.abort();
     }
     if(output != null) {
      output.close();
     }
     if(fos != null) {
      fos.close();
     }
    } catch(Exception e) {
     e.printStackTrace();
    }
   }
   return null;
  }
  @Override
  protected void onPreExecute() {
   Log.i(TAG, "download begin ");
   super.onPreExecute();
  }
  @Override
  protected void onProgressUpdate(Integer... values) {
   super.onProgressUpdate(values);
   //更新界面進度條
   updateProgress();
  }
  @Override
  protected void onPostExecute(Long aLong) {
   Log.i(TAG, "download success ");
   //下載完成移除記錄
   mSharedPreferences.edit().remove(currentThreadIndex).commit();
  }
  @Override
  protected void onCancelled() {
   Log.i(TAG, "download cancelled ");
   //記錄已下載大小current
   mSharedPreferences.edit().putLong(currentThreadIndex, current).commit();
  }
  @Override
  protected void onCancelled(Long aLong) {
   Log.i(TAG, "download cancelled(Long aLong)");
   super.onCancelled(aLong);
   mSharedPreferences.edit().putLong(currentThreadIndex, current).commit();
  }
 }
 private class InnerHandler extends Handler {
  @Override
  public void handleMessage(Message msg) {
   switch (msg.what) {
    case GET_LENGTH_SUCCESS :
     beginDownload();
     break;
   }
   super.handleMessage(msg);
  }
 }
}

布局文件和前面一篇博客《AsyncTask實現斷點續傳》布局文件是一樣的,這里就不貼代碼了。

  以上代碼親測可用,幾百M大文件也沒問題。

三、遇到的坑

  問題描述:在使用上面代碼下載http://ftp.neu.edu.cn/mirrors/eclipse/technology/epp/downloads/release/juno/SR2/eclipse-java-juno-SR2-linux-gtk-x86_64.tar.gz文件的時候,不知道為什么暫停時候執行AsyncTask.cancel(true)來取消下載任務,不執行onCancel()函數,也就沒有記錄該線程下載的位置。并且再次點擊下載的時候,5個Task都只執行了onPreEexcute()方法,壓根就不執行doInBackground()方法。而下載其他文件沒有這個問題。

  這個問題折騰了我好久,它又沒有報任何異常,調試又調試不出來。看AsyncTask的源碼、上stackoverflow也沒有找到原因。看到這個網站(https://groups.google.com/forum/#!topic/android-developers/B-oBiS7npfQ)時,我還真以為是AsyncTask的一個bug。

  百番周折,問題居然出現在上面代碼239行(這里已注釋)。不知道為什么,執行這一句的時候,線程就阻塞在那里了,所以doInBackground()方法一直沒有結束,onCancel()方法當然也不會執行了。同時,因為使用的是線程池Executor,線程數為5個,點擊取消之后5個線程都阻塞了,所以再次點擊下載的時候只執行了onPreEexcute()方法,沒有空閑的線程去執行doInBackground()方法。真是巨坑無比有木有。。。

  雖然問題解決了,但是為什么有的文件下載執行到is.close()的時候線程會阻塞而有的不會?這還是個謎。如果哪位大神知道是什么原因,還望指點指點!

源碼下載:https://github.com/liuling07/MultiTaskAndThreadDownload

總結

以上所述是小編給大家介紹的Android 使用AsyncTask實現多線程斷點續傳,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對服務器之家網站的支持!

原文鏈接:http://www.cnblogs.com/liuling/p/2015-10-13-01.html

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 羞羞漫画免费漫画页面在线看漫画秋蝉 | 手机看片1024国产 | 亚洲黄色三级视频 | 四虎tv在线观看884aa | 国产色网| 四川一级毛片 | mmkk在线看片 | 网站色小妹 | 精品美女国产互换人妻 | 楚乔传第二部免费观看全集完整版 | 久久九九久精品国产尤物 | 久久久久免费视频 | 欧美男女爱爱视频 | 调教开发新婚娇妻放荡 | 男生同性啪视频在线观看 | 高h短篇辣肉各种姿势bl | 亚洲高清在线视频 | 热色综合 | 欧美成人免费草草影院视频 | 麻豆自拍 | 无码国产成人午夜在线观看不卡 | 91桃色视频 | 欧美香蕉视频 | 成人国产精品一区二区不卡 | 人与动人物人a级特片 | 91精品国产综合久久精品 | 无人区1免费完整观看 | 日韩在线观看免费 | 手机看片日韩1024你懂的首页 | 国产性tv国产精品 | 亚洲精品 欧美 | 无敌秦墨漫画免费阅读 | 成人久久网站 | 韩国三级在线播放 | 青青草在线观看 | 美女撒尿毛片免费看 | 办公室出轨秘书高h | 狠狠的撞击发泄h | 亚洲日日操 | 91免费精品国自产拍在线可以看 | 日本一片免费观看高清完整 |