之前給大家分享了一下關(guān)于斷點下載的基本套路和邏輯下面引入線程池來完成斷點下載!主要就是讓大家更深入的了解一下斷點下載的各種樣式!可以幫助新手對代碼邏輯的一種鍛煉!這次注解非常詳細,我就不多說了!
下載的接口
public interface RetrofitService {
@GET("https://www.wandoujia.com/apps/604363/download/dot?ch=detail_normal_dl")
Call<ResponseBody> breadPointRetrofit(@Header("range") String range);
@HEAD("https://www.wandoujia.com/apps/604363/download/dot?ch=detail_normal_dl")
Call<Void> getHeaderMessage();
}
服務(wù)下載
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//先封裝文件
File directory = Environment.getExternalStorageDirectory();
final File file = new File(directory, "1221.apk");
//請求頭信息 創(chuàng)建retrofit對象
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://www.wandoujia.com/")
.build();
//請求頭信息 創(chuàng)建服務(wù)器對象 注解封裝的接口對象
RetrofitService service = retrofit.create(RetrofitService.class);
//請求頭信息 調(diào)用接口方法 獲取call對象
Call<Void> call = service.getHeaderMessage();
//請求頭信息 call執(zhí)行異步任務(wù) 獲取結(jié)果
call.enqueue(new Callback<Void>() {
@Override
public void onResponse(Call<Void> call, Response<Void> response) {
Log.i("tag", "onResponse: ");
//響應(yīng)對象獲取請求的頭信息對象
Headers headers = response.headers();
//利用請固定頭字段 獲取響應(yīng)回來的文件大小
String s = headers.get("content-length");
//將文件大小轉(zhuǎn)成long類型
final long contentLength = Long.parseLong(s);
//準(zhǔn)備開啟5個線程來下載該文件
int threadCount=5;
//計算每個線程需要下載的文件大小
long partFile = contentLength / threadCount;
//開啟循環(huán) 開啟線程池 下載文件
for (int i = 0; i < threadCount; i++) {
/**
* 線程分配下載開始指針位置和結(jié)束指針位置
* range:bytes=start-end(斷點續(xù)傳的主要頭信息必須配置的)
* partFile*i(start)-partFile*(i+1)-1(end)這里減一是分段下載的規(guī)律
* partFile*0(Start)-partFile*(0+1)-1(end)
* partFile*1(Start)-partFile*(1+1)-1(end)
* partFile*2(Start)-contentLength
*
*/
//這里屬于初始化每個線程開始和結(jié)束的時間點的位置 因為可能有斷點的情況發(fā)生
//所以我們把時間斷點在后面存在sp上 在這里取出sp里存儲的時間斷點位置
//如果有斷點存進來那么就替換partstart如果沒有就用原有的
//定義動態(tài)key
final String threadId="thread"+i;
//定義每段起始的start時間指針
long defaultStart = partFile * i;
//取出sp里存儲的斷點時間指針設(shè)為此段子線程的開始位置,默認值為defultStart
//也就是如果沒有發(fā)生斷點下載那就直接用默認值為起始斷點時間指針即可
SharedPreferences mysp = getSharedPreferences("mysp", MODE_PRIVATE);
final long myspStart = mysp.getLong(threadId, defaultStart);
//定義每段end時間指針
long end = partFile * (i + 1) - 1;
//如果是最后一圈也就是最后一段線程end為contentlength
if (i == threadCount - 1) {
end=contentLength;
}
//開始的時間指針和結(jié)束的時間指針安排完事就開始構(gòu)建Range頭
final String range=String.format(Locale.CHINESE,"bytes=%d-%d",myspStart,end);
//由于開啟多片下載 需要多個線程 所以線程池登場
Executors.newCachedThreadPool().execute(new Runnable() {
@Override
public void run() {
try {
Log.i("tag", "當(dāng)前線程是: "+Thread.currentThread().getName());
//正式開始下載文件
Retrofit retrofit1 = new Retrofit.Builder()
.baseUrl("https://www.wandoujia.com/")
.build();
RetrofitService retrofitService = retrofit1.create(RetrofitService
.class);
Call<ResponseBody> call1 = retrofitService.breadPointRetrofit(range);
Response<ResponseBody> execute = call1.execute();
//獲取下載流
InputStream inputStream = execute.body().byteStream();
//創(chuàng)建RandomAccessFile
//這里創(chuàng)建是為了每個線程操作各自的對象 如果在循環(huán)外創(chuàng)建會造成多線程操作同一個對象指針錯亂
RandomAccessFile accessFile = new RandomAccessFile(file, "rw");
byte[] bytes = new byte[1024];
int len;
//將時間指針移動到開始位置開始讀取
accessFile.seek(myspStart);
//創(chuàng)建sp
SharedPreferences mysp = getSharedPreferences("mysb", MODE_PRIVATE);
while ((len=inputStream.read(bytes))!=-1){
//寫入我們之前創(chuàng)建好的文件里
accessFile.write(bytes,0,len);
//這里就是sp的存檔點了,如果出現(xiàn)事故斷開連接,那么在這里就可以存入當(dāng)前斷開的
//時間指針的位置
//獲取當(dāng)前時間指針
long filePointer = accessFile.getFilePointer();
//存入sp內(nèi)
mysp.edit().putLong(threadId,filePointer).commit();
long length = file.length();
int currentProgress = (int) (length * 100 / contentLength);
EventBus.getDefault().post(currentProgress);
}
EventBus.getDefault().post(file);
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
@Override
public void onFailure(Call<Void> call, Throwable t) {
}
});
return super.onStartCommand(intent, flags, startId);
}
view層的代碼
@Override
public void onClick(View v) {
startService(new Intent(this, MyService.class));
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void breadPoint(Integer index) {
progressbar.setProgress(index);
tv.setText("當(dāng)前進度:" + index);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void downloadApp(File files) {
// 判斷小于7.0版本
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
Log.i("tag", "downloadApp: " + 99999);
Intent intent = new Intent(Intent.ACTION_VIEW);
// 7.0版本的uri方法
Uri uri = Uri.fromFile(files);
intent.setDataAndType(uri, "application/vnd.android.package-archive");
startActivityForResult(intent, 2);
} else {
Intent intent = new Intent(Intent.ACTION_VIEW);
// 7.0以上版本用fileprovider
Uri uri = FileProvider.getUriForFile(this, "com.example.breadpointdownload" + "" + "" +
".provider", files);
intent.setDataAndType(uri, "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(intent, 2);
}
}
總結(jié):開啟了多線程來分段式下載,提高了下載的速度,但是對于未來的5g來說也許就沒有什么用了,但是往往更新更好用的工具和框架都是基于笨拙的老一代而生成的!