多線程下載和斷點續(xù)傳用HttpUrlConnection和OkHttp都可以實現(xiàn)
這里說的是用OkHttp來實現(xiàn)的
以下是代碼,多線程下載,暫停開始,斷點續(xù)傳,進度條顯示
public class testActivity extends Activity {
private String name="aaa";
private int pwd=123;
private final String getUrl="http://139.129.110.99:8901/test?name="+name+"&pwd="+pwd;
private final String postUrl="http://139.129.110.99:8901/posttest";
private final String sourceUrl="http://api.jisuapi.com/news/get?channel=頭條&start=0&num=10&appkey=e06ac19faa18ef4a";
public final String fieUrl="http://139.129.110.99:81/witraffic_app.rar";
private ProgressBar progressBar,progressBar1,progressBar2,progressBar3;
private OkHttpClient client=new OkHttpClient();
private Call call;
//設置線程的數(shù)量
private final static int threadCount=3;
//正在下載的線程
private int runningThread;
//設置下載的文件的路徑
public String filePath;
//總的下載進度
public int totalCount=0;
//文件總大小
public long total;
//判斷是否正在下載
public Boolean isRunning=false;
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
progressBar=(ProgressBar)findViewById(R.id.progress);
button=(Button)findViewById(R.id.btn);
runningThread=threadCount;
//創(chuàng)建下載的文件
File file=new File(Environment.getExternalStorageDirectory(),"aaa");
file.mkdirs();
File file1=new File(file,"aaa.rar");
try {
file1.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
//點擊這個按鈕的時候開始下載或者繼續(xù)下載,對應button的點擊事件
public void click1(View v)
{
if(!isRunning) {
//這次請求就為了獲取文件長度
Request request = new Request.Builder()
.url(fieUrl)
.tag("a")
.build();
call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.i("test", e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//1 獲取到文件的大小
total = response.body().contentLength();
//設置總大小
progressBar.setMax((int) total);
//2 創(chuàng)建和服務器大小一樣的文件,同時為他分配和服務器上文件大小一樣的空間
//支持對文件隨機讀寫的類,第一個參數(shù)傳入文件的地址,第二個參數(shù)是模式,rwd代表同步到底層設備
//隨機讀寫的意思是可以從任何的位置開始讀寫
filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/aaa/aaa.rar";
RandomAccessFile file = new RandomAccessFile(filePath, "rwd");
//設置文件的大小
file.setLength(total);
Log.i("test", "線程" + total);
//3 計算每個線程下載的大小和開始的長度
/*
計算方法說明:
文件下載的起點是從0開始的,所以如果一個文件共10000bit,那么下載的位置就是0~9999
首先用總大小除以線程數(shù)量,這兩者都是整型,做除法后得到的也是整型
得到的就是處理最后一個線程以外每個線程需要下載的長度
文件的地址是從0開始的,所以第一個線程下載的位置就是0*blockSize~1*blockSize-1
第二個就是1*blockSize~2*blockSize-1
......
第n個就是(n-1)*blockSize~n*blockSize-1
.....
最后一個線程下載區(qū)間就是,假設一共三個線程
2*blockSize~文件末尾
*/
/*
所以第一個線程的開始位置為0,結束位置就是1*blockSize-1
第二個線程開始位置為1*blockSize,結束位置就是2*blockSize-1
....
第n個線程開始位置就是(n-1)*blockSize,結束位置就是n*blockSize-1
假設一共三個線程,最后一個線程開始位置就是,2*blockSize~(文件總長度-1)
*/
//3.1 算出除了最后一個線程外每個線程下載的大小
long blockSize = total / threadCount;
Log.i("test", "" + blockSize);
//3.2 開多個線程進行下載
for (int i = 0; i < threadCount; i++) {
//每個線程的開始位置
long startIndex = i * blockSize;
//每個線程的結束位置
long endIndex = (i + 1) * blockSize - 1;
//如果是最后一個線程,那么結束位置是文件的末尾
if (i == (threadCount - 1)) {
endIndex = total - 1;
}
Log.i("test", "線程" + i + "開始位置" + startIndex + "結束位置:" + endIndex);
//開啟線程開始下載,單寫一個DownLoadThread類,直接傳入?yún)?shù)開始下載
new DownLoadThread(startIndex, endIndex, i).start();
}
isRunning = true;
}
});
button.setText("暫停");
}
else
{
CancelTag("a");
button.setText("開始");
isRunning=false;
}
}
//下載文件的線程的代碼
public class DownLoadThread extends Thread
{
public long startIndex;
public long endIndex;
public long threadId;
public DownLoadThread(long startIndex,long endIndex,long threadId)
{
this.startIndex=startIndex;
this.endIndex=endIndex;
this.threadId=threadId;
}
@Override
public void run() {
//創(chuàng)建存放開始位置的文件
final File startIndexFile=new File(Environment.getExternalStorageDirectory()+"/aaa",threadId+".txt");
if(!startIndexFile.exists())
{
try {
startIndexFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
//判斷是第一次下載還是斷點后繼續(xù)下載
try {
FileInputStream inputStream=new FileInputStream(startIndexFile);
BufferedReader reader=new BufferedReader(new InputStreamReader(inputStream));
String currentIndex=reader.readLine();
if(currentIndex!=null)
{
startIndex=Integer.parseInt(currentIndex);
Log.i("test","線程"+threadId+"開始位置:"+startIndex+"結束位置:"+endIndex);
}
inputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e)
{
e.printStackTrace();
}
//這個判斷很重要,在結束的時候暫停很可能導致開始位置大于結束位置
if(startIndex<=endIndex)
{
//這里開始請求數(shù)據(jù),每個線程請求的都是部分數(shù)據(jù),所以要設置一個頭
//這個頭的名為Range,參數(shù)是下載的起止位置,格式為"bytes=開始位置-結束位置"
//這時返回的狀態(tài)碼為206,206代表請求部分數(shù)據(jù)
//同時設置一個tag標簽,用于暫停下載時使用
final Request request=new Request.Builder()
.url(fieUrl)
.tag("a")
.addHeader("Range","bytes="+startIndex+"-"+endIndex)
.build();
Call call=client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//這里開始處理接收文件的邏輯
//記錄該線程當前下載的大小
long currentCount=0;
//記錄當前的位置
long currentPosition=0;
//首先再拿到之前創(chuàng)建的空文件
RandomAccessFile file=new RandomAccessFile(filePath,"rwd");
//設置開始寫入的位置
file.seek(startIndex);
int len=-1;
InputStream inputStream=response.body().byteStream();
byte[] bytes=new byte[1024*1024];
while((len=inputStream.read(bytes))!=-1)
{
//RandomAccessFile類同樣有write方法,寫入文件,相當于輸出流,也需要關閉
file.write(bytes,0,len);
//當前線程下載的總大小
currentCount+=len;
//如果下載暫停了以后新的下載位置
currentPosition=startIndex+currentCount;
//用RandomAccessFile來記錄位置是因為它可以用rwd模式將數(shù)據(jù)同步到底層設備,
//避免數(shù)據(jù)同步不上
RandomAccessFile raf=new RandomAccessFile(startIndexFile,"rwd");
raf.write(String.valueOf(currentPosition).getBytes());
raf.close();
//設置下載進度,progress可以直接在子線程更新UI
synchronized (DownLoadThread.class)
{
totalCount+=len;
progressBar.setProgress(totalCount);
if(totalCount==total)
{
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(testActivity.this,"下載完畢",Toast.LENGTH_SHORT).show();
}
});
}
}
}
//關閉RandomAccessFile
file.close();
Log.i("test","線程"+threadId+"下載完畢");
//每次下載完畢一個線程runningThread就減一,當runningThread為0時,代表所有線程都下載完畢
//為DownLoadThread在操作runningThread--時加鎖,在多個線程同時操作runningThread--時保證先后順序
synchronized (DownLoadThread.class)
{
runningThread--;
//當所有線程都下載完畢時,刪除存儲開始位置的文件
if(runningThread==0)
{
for(int i=0;i<threadCount;i++)
{
File deleteFile=new File(Environment.getExternalStorageDirectory()+"/aaa",i+".txt");
deleteFile.delete();
}
}
}
}
});
}
}
}
//取消對應tag的call的方法,okhttp3.0以后沒有直接根據(jù)標簽取消的方法
//只能間接的寫出這個方法
public void CancelTag(Object o)
{
for(Call call:client.dispatcher().queuedCalls())
{
if(call.request().tag().equals(o))
{
call.cancel();
}
}
for(Call call:client.dispatcher().runningCalls())
{
if(call.request().tag().equals(o))
{
call.cancel();
}
}
isRunning=false;
}
@Override
protected void onDestroy() {
super.onDestroy();
//在需要的地方調(diào)用ActivityCollector.exit(),就可以退出所有的活動
ActivityCollector.exit();
}
}