欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Android中的多線程斷點(diǎn)下載

 更新時(shí)間:2016年12月20日 09:07:14   作者:huang502  
本文主要介紹了Android中多線程下載的幾個(gè)步驟以及實(shí)現(xiàn)功能的具體代碼,具有很好的參考價(jià)值,需要的朋友一起來看下吧

首先來看一下多線程下載的原理。多線程下載就是將同一個(gè)網(wǎng)絡(luò)上的原始文件根據(jù)線程個(gè)數(shù)分成均等份,然后每個(gè)單獨(dú)的線程下載對應(yīng)的一部分,然后再將下載好的文件按照原始文件的順序“拼接”起來就構(gòu)

成了完整的文件了。這樣就大大提高了文件的下載效率。對于文件下載來說,多線程下載是必須要考慮的環(huán)節(jié)。

多線程下載大致可分為以下幾個(gè)步驟:

一.獲取服務(wù)器上的目標(biāo)文件的大小

顯然這一步是需要先訪問一下網(wǎng)絡(luò),只需要獲取到目標(biāo)文件的總大小即可。目的是為了計(jì)算每個(gè)線程應(yīng)該分配的下載任務(wù)。

二. 在本地創(chuàng)建一個(gè)跟原始文件同樣大小的文件

在本地可以通過RandomAccessFile 創(chuàng)建一個(gè)跟目標(biāo)文件同樣大小的文件,該api 支持文件任意位置的讀寫操作。這樣就給多線程下載提供了方便,每個(gè)線程只需在指定起始和結(jié)束腳標(biāo)范圍內(nèi)寫數(shù)據(jù)即可。

三.計(jì)算每個(gè)線程下載的起始位置和結(jié)束位置

我們可以把原始文件當(dāng)成一個(gè)字節(jié)數(shù)組,每個(gè)線程只下載該“數(shù)組”的指定起始位置和指定結(jié)束位置之間的部分。在第一步中我們已經(jīng)知道了“數(shù)組”的總長度。因此只要再知道總共開啟的線程的個(gè)數(shù)就好計(jì)算每個(gè)線程要下載的范圍了。每個(gè)線程需要下載的字節(jié)個(gè)數(shù)(blockSize)=總字節(jié)數(shù)(totalSize)/線程數(shù)(threadCount)。       假設(shè)給線程按照0,1,2,3...n 的方式依次進(jìn)行編號(hào),那么第n 個(gè)線程下載文件的范圍為:

起始腳標(biāo)startIndex=n*blockSize。

結(jié)束腳標(biāo)endIndex=(n-1)*blockSize-1。

考慮到totalSize/threadCount 不一定能整除,因此對已最后一個(gè)線程應(yīng)該特殊處理,最后一個(gè)線程的起始腳標(biāo)計(jì)算公式不變,但是結(jié)束腳標(biāo)為endIndex=totalSize-1即可。

四.開啟多個(gè)子線程開始下載

在子線程中實(shí)現(xiàn)讀流操作,將conn.getInputStream()讀到RandomAccessFile中。

五.記錄下載進(jìn)度

為每一個(gè)單獨(dú)的線程創(chuàng)建一個(gè)臨時(shí)文件,用于記錄該線程下載的進(jìn)度。對于單獨(dú)的一個(gè)線程,每下載一部分?jǐn)?shù)據(jù)就在本地文件中記錄下當(dāng)前下載的字節(jié)數(shù)。這樣子如果下載任務(wù)異常終止了,那么下次重新開始下載時(shí)就可以接著上次的進(jìn)度下載。

六. 刪除臨時(shí)文件

當(dāng)多個(gè)線程都下載完成之后,最后一個(gè)下載完的線程將所有的臨時(shí)文件刪除。

Android有界面可以跟用戶進(jìn)行良好的交互,在界面上讓用戶輸入原文件地址、線程個(gè)數(shù),然后點(diǎn)擊確定開始下載。為了讓用戶可以清晰的看到每個(gè)線程下載的進(jìn)度根據(jù)線程個(gè)數(shù)動(dòng)態(tài)的生成等量的進(jìn)度條(ProgressBar)。ProgressBar 是一個(gè)進(jìn)度條控件,用于顯示一項(xiàng)任務(wù)的完成進(jìn)度。其有兩種樣式,一種是圓形的,該種樣式是系統(tǒng)默認(rèn)的,由于無法顯示具體的進(jìn)度值,適合用于不確定要等待多久的情形下;另一種是長條形的,此類進(jìn)度條有兩種顏色,高亮顏色代表任務(wù)完成的總進(jìn)度。對于我們下載任務(wù)來說,由于總?cè)蝿?wù)(要下載的字節(jié)數(shù))是明確的,當(dāng)前已經(jīng)完成的任務(wù)(已經(jīng)下載的字節(jié)數(shù))也是明確的,因此特別適合使用后者。由于在我們的需求里ProgressBar 是需要根據(jù)線程的個(gè)數(shù)動(dòng)態(tài)添加的,而且要求是長條形的。因此可以事先在布局文件中編寫好ProgressBar 的樣式。當(dāng)需要用到的時(shí)候再將該布局填充起來。ProgressBar 的max 屬性代表其最大刻度值,progress 屬性代表當(dāng)前進(jìn)度值。使用方法如下:

ProgressBar.setMax(int max);設(shè)置最大刻度值。

ProgressBar.setProgress(int progress);設(shè)置當(dāng)前進(jìn)度值。

給ProgressBar 設(shè)置最大刻度值和修改進(jìn)度值可以在子線程中操作的,其內(nèi)部已經(jīng)特殊處理過了,因此不需要再通過handler發(fā)送Message 讓主線程修改進(jìn)度。

下面就給出我們自己寫的安卓環(huán)境下的多線程。

多線程下載界面布局如下,三個(gè)進(jìn)度條分別表示三個(gè)子線程的下載進(jìn)度。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
 tools:context=".MainActivity" >
 <EditText
 android:id="@+id/et_path"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:hint="請輸入要下載的文件資源路徑"
android:text="http://192.168.1.104:8080/gg.exe" />
 <Button
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:onClick="download"
 android:text="下載" />
 <ProgressBar
 android:id="@+id/pb0"
 style="?android:attr/progressBarStyleHorizontal"
 android:layout_width="match_parent"
 android:layout_height="wrap_content" />
 <ProgressBar
 android:id="@+id/pb1"
 style="?android:attr/progressBarStyleHorizontal"
 android:layout_width="match_parent"
 android:layout_height="wrap_content" />
 <ProgressBar
 android:id="@+id/pb2"
 style="?android:attr/progressBarStyleHorizontal"
 android:layout_width="match_parent"
 android:layout_height="wrap_content" />
</LinearLayout>

多線程下載的內(nèi)部邏輯如下,其實(shí)這在開頭已經(jīng)有了,只不過是代碼的實(shí)現(xiàn)了。

 public class MainActivity extends Activity {
 private EditText et_path;
 private ProgressBar pb0;
 private ProgressBar pb1;
 private ProgressBar pb2;
 /**
 * 開啟幾個(gè)線程從服務(wù)器下載數(shù)據(jù)
 */
 public static int threadCount = 3;
 public static int runningThreadCount;
 private String path;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 //初始化控件
 et_path = (EditText) findViewById(R.id.et_path);
 pb0 = (ProgressBar) findViewById(R.id.pb0);
 pb1 = (ProgressBar) findViewById(R.id.pb1);
 pb2 = (ProgressBar) findViewById(R.id.pb2);
 }
 //下載按鈕的點(diǎn)擊事件
 public void download(View view) {
 path = et_path.getText().toString().trim();
 if (TextUtils.isEmpty(path) || (!path.startsWith("http://"))) {
 Toast.makeText(this, "對不起路徑不合法", 0).show();
 return;
 }
 new Thread(){
 public void run() {
 try {
 //1.獲取服務(wù)器上的目標(biāo)文件的大小
 URL url = new URL(path);
 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
 conn.setConnectTimeout(5000);
 conn.setRequestMethod("GET");
 int code = conn.getResponseCode();
 if (code == 200) {
 int length = conn.getContentLength();
 System.out.println("服務(wù)器文件的長度為:" + length);
 //2.在本地創(chuàng)建一個(gè)跟原始文件同樣大小的文件
 RandomAccessFile raf = new RandomAccessFile(Environment.getExternalStorageDirectory().getAbsolutePath()+"/"+getFileName(path), "rw");
 raf.setLength(length);
 raf.close();
 //3.計(jì)算每個(gè)線程下載的起始位置和結(jié)束位置
 int blocksize = length / threadCount;
 runningThreadCount = threadCount;
 for (int threadId = 0; threadId < threadCount; threadId++) {
 int startIndex = threadId * blocksize;
 int endIndex = (threadId + 1) * blocksize - 1;
 if (threadId == (threadCount - 1)) {
 endIndex = length - 1;
 }
 //4.開啟多個(gè)子線程開始下載
 new DownloadThread(threadId, startIndex, endIndex).start();
 }
 }
 } catch (Exception e) {
 e.printStackTrace();
 }
 };
 }.start();
 }
 private class DownloadThread extends Thread {
 /**
 * 線程id
 */
 private int threadId;
 /**
 * 線程下載的理論開始位置
 */
 private int startIndex;
 /**
 * 線程下載的結(jié)束位置
 */
 private int endIndex;
 /**
 * 當(dāng)前線程下載到文件的那一個(gè)位置了.
 */
 private int currentPosition;
 public DownloadThread(int threadId, int startIndex, int endIndex) {
 this.threadId = threadId;
 this.startIndex = startIndex;
 this.endIndex = endIndex;
 System.out.println(threadId + "號(hào)線程下載的范圍為:" + startIndex
 + " ~~ " + endIndex);
 currentPosition = startIndex;
 }
 @Override
 public void run() {
 try {
 URL url = new URL(path);
 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
 //檢查當(dāng)前線程是否已經(jīng)下載過一部分的數(shù)據(jù)了 
 File info = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"/"+threadId+".position");
 RandomAccessFile raf = new RandomAccessFile(Environment.getExternalStorageDirectory().getAbsolutePath()+"/"+getFileName(path), "rw");
 if(info.exists()&&info.length()>0){
 FileInputStream fis = new FileInputStream(info);
 BufferedReader br = new BufferedReader(new InputStreamReader(fis));
 currentPosition = Integer.valueOf(br.readLine());
 conn.setRequestProperty("Range", "bytes="+currentPosition+"-"+endIndex);
 System.out.println("原來有下載進(jìn)度,從上一次終止的位置繼續(xù)下載"+"bytes="+currentPosition+"-"+endIndex);
 fis.close();
 raf.seek(currentPosition);//每個(gè)線程寫文件的開始位置都是不一樣的.
 }else{
 //告訴服務(wù)器 只想下載資源的一部分
 conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex);
 System.out.println("原來沒有有下載進(jìn)度,新的下載"+ "bytes="+startIndex+"-"+endIndex);
 raf.seek(startIndex);//每個(gè)線程寫文件的開始位置都是不一樣的.
 }
 InputStream is = conn.getInputStream();
 byte[] buffer = new byte[1024];
 int len = -1;
 while((len = is.read(buffer))!=-1){
 //把每個(gè)線程下載的數(shù)據(jù)放在自己的空間里面.
// System.out.println("線程:"+threadId+"正在下載:"+new String(buffer));
 raf.write(buffer,0, len);
 //5.記錄下載進(jìn)度
 currentPosition+=len;
 File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"/"+threadId+".position");
 RandomAccessFile fos = new RandomAccessFile(file,"rwd");
 //System.out.println("線程:"+threadId+"寫到了"+currentPosition);
 fos.write(String.valueOf(currentPosition).getBytes());
 fos.close();//fileoutstream數(shù)據(jù)是不一定被寫入到底層設(shè)備里面的,有可能是存儲(chǔ)在緩存里面.
 //raf 的 rwd模式,數(shù)據(jù)是立刻被存儲(chǔ)到底層硬盤設(shè)備里面.
 //更新進(jìn)度條的顯示
 int max = endIndex - startIndex;
 int progress = currentPosition - startIndex;
 if(threadId==0){
 pb0.setMax(max);
 pb0.setProgress(progress);
 }else if(threadId==1){
 pb1.setMax(max);
 pb1.setProgress(progress);
 }else if(threadId==2){
 pb2.setMax(max);
 pb2.setProgress(progress);
 }
 }
 raf.close();
 is.close();
 System.out.println("線程:"+threadId+"下載完畢了...");
 File f = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"/"+threadId+".position");
 f.renameTo(new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"/"+threadId+".position.finish"));
 synchronized (MainActivity.class) {
 runningThreadCount--;
 //6.刪除臨時(shí)文件
 if(runningThreadCount<=0){
 for(int i=0;i<threadCount;i++){
 File ft = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"/"+i+".position.finish");
 ft.delete();
 }
 }
 }
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
 }
 /**
 * 獲取一個(gè)文件名稱
 * @param path
 * @return
 */
 public String getFileName(String path){
 int start = path.lastIndexOf("/")+1;
 return path.substring(start);
 }
}

最后別忘了添加權(quán)限,在該工程中不僅用到了網(wǎng)絡(luò)訪問還用到了sdcard 存儲(chǔ),因此需要添加兩個(gè)權(quán)限。

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

另外,xUtils同樣可以實(shí)現(xiàn)多線程下載。xUtils 是開源免費(fèi)的Android 工具包,代碼托管在github 上。目前xUtils 主要有四大模塊:DbUtils 模塊,主要用于操作數(shù)據(jù)庫的框架。ViewUtils 模塊,通過注解的方式可以對UI,資源和事件綁定進(jìn)行管理。HttpUtils 模塊,提供了方便的網(wǎng)絡(luò)訪問,斷點(diǎn)續(xù)傳等功能。BitmapUtils 模塊,提供了強(qiáng)大的圖片處理工具。我們在這里只簡單實(shí)用xUtils 工具中的HttpUtils 工具。第三方包的使用較為簡單,直接拷貝xUtils的jar包到libs目錄下,然后添加依賴。

接下來就可以使用xUtils中的httpUtils的功能了:

HttpUtils http = new HttpUtils();
 /**
 * 參數(shù)1:原文件網(wǎng)絡(luò)地址
 * 參數(shù)2:本地保存的地址
 * 參數(shù)3:是否支持?jǐn)帱c(diǎn)續(xù)傳,true:支持,false:不支持
 * 參數(shù)4:回調(diào)接口,該接口中的方法都是在主線程中被調(diào)用的,
 * 也就是該接口中的方法都可以修改UI
 */
 http.download(path, "/mnt/sdcard/xxx.exe", true, new RequestCallBack<File>() {
 //下載成功后調(diào)用一次
 @Override
 public void onSuccess(ResponseInfo<File> arg0) {
 Toast.makeText(MainActivity.this, "下載成功", 0).show();
 }
 /**
 * 每下載一部分就被調(diào)用一次,通過該方法可以知道當(dāng)前下載進(jìn)度
 * 參數(shù)1:原文件總字節(jié)數(shù)
 * 參數(shù)2:當(dāng)前已經(jīng)下載好的字節(jié)數(shù)
 * 參數(shù)3:是否在上傳,對于下載,該值為false
 */
 @Override
 public void onLoading(long total, long current, boolean isUploading) {
 pb0.setMax((int) total);
 pb0.setProgress((int) current);
 super.onLoading(total, current, isUploading);
 }
 //失敗后調(diào)用一次
 @Override
 public void onFailure(HttpException arg0, String arg1) {
 Toast.makeText(MainActivity.this, "下載失敗"+arg1, 0).show();
 }
 });

以上就是本文的全部內(nèi)容,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作能帶來一定的幫助,同時(shí)也希望多多支持腳本之家!

相關(guān)文章

  • Android ConstraintLayout約束布局使用詳解

    Android ConstraintLayout約束布局使用詳解

    ConstraintLayout 即約束布局,也是 Android Studio 的默認(rèn)布局,它可以減少布局的層級(jí),改善布局性能。不夸張地說,它基本上可以實(shí)現(xiàn)任何你想要的布局效果,下面,咱們一起來瞧瞧吧
    2022-11-11
  • android實(shí)現(xiàn)記事本app

    android實(shí)現(xiàn)記事本app

    這篇文章主要為大家詳細(xì)介紹了android實(shí)現(xiàn)記事本app的相關(guān)代碼 ,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-12-12
  • Android如何監(jiān)測文件夾內(nèi)容變化詳解

    Android如何監(jiān)測文件夾內(nèi)容變化詳解

    最近在開發(fā)android應(yīng)用程序的時(shí)候遇到了一個(gè)監(jiān)測文件夾的功能,所以下面這篇文章主要給大家介紹了關(guān)于Android如何監(jiān)測文件夾內(nèi)容變化的相關(guān)資料,需要的朋友可以參考下
    2021-12-12
  • Android Studio中統(tǒng)一管理版本號(hào)引用配置問題

    Android Studio中統(tǒng)一管理版本號(hào)引用配置問題

    這篇文章主要介紹了Android Studio中統(tǒng)一管理版本號(hào)引用配置問題,需要的朋友可以參考下
    2018-01-01
  • Android中Service服務(wù)詳解(二)

    Android中Service服務(wù)詳解(二)

    這篇文章主要介紹了Android中Service服務(wù),在前面一篇的基礎(chǔ)上進(jìn)一步分析了Android Service的綁定服務(wù)與解綁服務(wù)的相關(guān)使用技巧,需要的朋友可以參考下
    2016-01-01
  • android實(shí)現(xiàn)下拉菜單三級(jí)聯(lián)動(dòng)

    android實(shí)現(xiàn)下拉菜單三級(jí)聯(lián)動(dòng)

    這篇文章主要為大家詳細(xì)介紹了android實(shí)現(xiàn)下拉菜單三級(jí)聯(lián)動(dòng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-10-10
  • Android繼承ViewGroup實(shí)現(xiàn)Scroll滑動(dòng)效果的方法示例

    Android繼承ViewGroup實(shí)現(xiàn)Scroll滑動(dòng)效果的方法示例

    這篇文章主要介紹了Android繼承ViewGroup實(shí)現(xiàn)Scroll滑動(dòng)效果的方法,結(jié)合實(shí)例形式分析了Android滑動(dòng)效果的原理及擴(kuò)展ViewGroup實(shí)現(xiàn)滑動(dòng)功能的相關(guān)操作技巧,需要的朋友可以參考下
    2017-08-08
  • Android基礎(chǔ)開發(fā)之手勢識(shí)別

    Android基礎(chǔ)開發(fā)之手勢識(shí)別

    這篇文章主要為大家詳細(xì)介紹了Android基礎(chǔ)開發(fā)之手勢識(shí)別的相關(guān)資料,感興趣的小伙伴們可以參考一下
    2016-06-06
  • android中DatePicker和TimePicker的使用方法詳解

    android中DatePicker和TimePicker的使用方法詳解

    這篇文章主要介紹了android中DatePicker和TimePicker的使用方法,是Android中常用的功能,需要的朋友可以參考下
    2014-07-07
  • Android Flutter利用貝塞爾曲線畫一個(gè)小海豚

    Android Flutter利用貝塞爾曲線畫一個(gè)小海豚

    貝塞爾曲線的應(yīng)用填補(bǔ)了計(jì)算機(jī)繪制與手繪之前的差距,更能表達(dá)人想畫出的曲線。本文就將利用貝塞爾曲線繪制一個(gè)可愛的小海豚,需要的可以參考一下
    2022-04-04

最新評論