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

Android入門(mén)之使用OKHttp多線程下載文件

 更新時(shí)間:2023年01月03日 11:31:17   作者:TGITCIC  
OkHttp是一個(gè)神器。OkHttp分為異步、同步兩種調(diào)用。今天我們就會(huì)基于OkHttp的異步調(diào)用實(shí)現(xiàn)一個(gè)多線程并行下載文件并以進(jìn)度條展示總進(jìn)度的實(shí)用例子,需要的可以參考一下

簡(jiǎn)介

OkHttp是一個(gè)神器。OkHttp分為異步、同步兩種調(diào)用。今天我們就會(huì)基于OkHttp的異步調(diào)用實(shí)現(xiàn)一個(gè)多線程并行下載文件并以進(jìn)度條展示總進(jìn)度的實(shí)用例子。當(dāng)然這不是我們的Android里使用OkHttp的最終目標(biāo),我們最終在下一篇中會(huì)在今天這一課的基礎(chǔ)上加入“斷點(diǎn)續(xù)傳”的功能,從而以這么連續(xù)的幾篇從易到難的循序漸進(jìn)的過(guò)程,讓大家熟悉和掌握Android中使用OkHttp的技巧以便于形成大腦的“肌肉記憶”。

課程目標(biāo)

  • 熟悉OkHttp的同步、異步調(diào)用;
  • 實(shí)現(xiàn)n個(gè)線程并行下載文件;
  • 使用線程中的回調(diào)機(jī)制實(shí)時(shí)傳輸下載時(shí)的進(jìn)度;
  • 用進(jìn)度條輔助顯示我們的整體下載進(jìn)度;

OkHttp的同步調(diào)用例子

    public int getDownloadFileSize(String downloadUrl) throws Exception {
        int size = -1;
        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)//設(shè)置連接超時(shí)時(shí)間
                .readTimeout(10, TimeUnit.SECONDS).build();//設(shè)置讀取超時(shí)時(shí)間
        Request request = new Request.Builder().url(downloadUrl)//請(qǐng)求接口,如果需要傳參拼接到接口后面
                .build(); //創(chuàng)建Request對(duì)象
        Response response = null;
        try {
            Call call = client.newCall(request);
            response = call.execute();
            if (200 == response.code()) {
                Log.d(TAG, ">>>>>>response.code()==" + response.code());
                Log.d(TAG, ">>>>>>response.message()==" + response.message());
                try {
                    size = (int) response.body().contentLength();
                    Log.d(TAG, ">>>>>>file length->" + size);
                    //fileSizeListener.onHttpResponse((int) size);
                } catch (Exception e) {
                    Log.e(TAG, ">>>>>>get remote file size error: " + e.getMessage(), e);
                }
            }
        } catch (Exception e) {
            Log.e(TAG, ">>>>>>open connection to path->" + downloadUrl + "\nerror: " + e.getMessage(), e);
            throw new Exception(">>>>>>getDownloadFileSize from->" + downloadUrl + "\nerror: " + e.getMessage(), e);
        } finally {
            try {
                response.close();
            } catch (Exception e) {
            }
        }
        return size;
    }

這是一個(gè)OkHttp的同步調(diào)用例子,訪問(wèn)后根據(jù)response.code來(lái)作出響應(yīng)并取response.body()的內(nèi)容做出相應(yīng)的業(yè)務(wù)處理。

OkHttp的異步調(diào)用例子

            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder()
                    .url(downloadFilePath)//請(qǐng)求接口,如果需要傳參拼接到接口后面
                    .build(); //創(chuàng)建Request對(duì)象
            Log.d(TAG, ">>>>>>線程" + (threadId + 1) + "開(kāi)始下載...");
            Call call = client.newCall(request);
            //異步請(qǐng)求
            call.enqueue(new Callback() {
                //失敗的請(qǐng)求
                @Override
                public void onFailure(@NonNull Call call, @NonNull IOException e) {
                    Log.e(TAG, ">>>>>>下載進(jìn)程加載->" + downloadFilePath + " error:" + e.getMessage(), e);
                }
 
                //結(jié)束的回調(diào)
                @Override
                public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
                    Log.d(TAG, ">>>>>>連接->" + downloadFilePath + " 己經(jīng)連接,進(jìn)入下載...");
                    InputStream is = null;
                    try {
                        if (200 == response.code()) {
                            Log.d(TAG, ">>>>>>response.code()==" + response.code());
                            Log.d(TAG, ">>>>>>response.message()==" + response.message());
                            is = response.body().byteStream();
                            byte[] buffer = new byte[1024];
                            int len = -1;
                            int length = 0;
                            while (length < threadLength && (len = is.read(buffer)) != -1) {
                                threadFile.write(buffer, 0, len);
                                //計(jì)算累計(jì)下載的長(zhǎng)度
                                length += len;
                                downloadListener.onDownload(length,totalSize);
                            }
                            Log.d(TAG, ">>>>>>線程" + (threadId + 1) + "已下載完成");
                        }
                    } catch (Exception e) {
                        Log.e(TAG, ">>>>>>線程:" + threadId + " 下載出錯(cuò): " + e.getMessage(), e);
                    } finally {
                        try {
                            threadFile.close();
                        } catch (Exception e) {
                        }
                        try {
                            is.close();
                            ;
                        } catch (Exception e) {
                        }
                    }
 
                }
            });

這是一個(gè)OkHttp的異步調(diào)用例子,我們可以看到它首先以call.enqueue來(lái)執(zhí)行調(diào)用,然后至少有2個(gè)回調(diào)方法:onFailure和onResponse需要自己覆蓋來(lái)實(shí)現(xiàn)業(yè)務(wù)功能。

各位記得同步、異步的調(diào)用還是有很大區(qū)別的。比如說(shuō)有以下調(diào)用順序:

  • OkHttp調(diào)用
  • A方法根據(jù)OkHttp調(diào)用后的結(jié)果再執(zhí)行B

此時(shí)你就必須使用同步調(diào)用,而不能使用異步。因?yàn)槿绻阌玫氖钱惒胶苡锌赡蹷在執(zhí)行到一半時(shí),第一步OkHttp調(diào)用的結(jié)果才剛剛到達(dá)。

多線程并行下載文件需要解決的幾個(gè)核心問(wèn)題

如何從一個(gè)遠(yuǎn)程Http資源得到文件的尺寸

OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)//設(shè)置連接超時(shí)時(shí)間
                .readTimeout(10, TimeUnit.SECONDS).build();//設(shè)置讀取超時(shí)時(shí)間
Request request = new Request.Builder().url(downloadUrl)
                .build(); //創(chuàng)建Request對(duì)象
Response response = null;
Call call = client.newCall(request);
response = call.execute();
if (200 == response.code()) {
   size = (int) response.body().contentLength();
}

我們通過(guò)response.body().contentLength()即可以獲得遠(yuǎn)程資源文件的尺寸了。

如何對(duì)一個(gè)本地文件進(jìn)行并行的寫(xiě)

在這兒,我們使用RandomAccessFile來(lái)進(jìn)行并行寫(xiě)操作。因?yàn)镽andomAccessFile里有一個(gè)seek屬性。

seek即寫(xiě)文件起始、結(jié)束位置。因此我們?cè)O(shè)每個(gè)不同的線程處理自己的start-end的位置就可以做到對(duì)文件進(jìn)行并行寫(xiě)操作了。為此我們需要執(zhí)行以下這么幾步:

創(chuàng)建一個(gè)長(zhǎng)度=遠(yuǎn)程資源長(zhǎng)度的空的文件

先創(chuàng)建一個(gè)空的RandomAccessFile,并把遠(yuǎn)程資源的長(zhǎng)度以如下的API set進(jìn)去;

file.setLength(fileLength)

為每個(gè)線程分配寫(xiě)入的start-end區(qū)間段

假設(shè)我們有n個(gè)線程,每個(gè)線程寫(xiě)文件的長(zhǎng)度可以用以下公式得到:

int threadlength = (int) fileLength % threadCount == 0 
                   ? (int) fileLength / threadCount : (int) fileLength + 1;

當(dāng)?shù)玫搅藅hreadLength即每個(gè)線程固定寫(xiě)入的長(zhǎng)度后我們就可以得到每個(gè)線程起始的寫(xiě)文件位置即: startPosition。

int startPosition = threadNo * threadlength;

每個(gè)線程在寫(xiě)入操作時(shí)需要先進(jìn)行:seek(起始位)。

threadFile.seek(startPosition);

然后在寫(xiě)時(shí)每個(gè)線程不得超過(guò)自己被分配的固定長(zhǎng)度,到了寫(xiě)入的固定長(zhǎng)度后就結(jié)束寫(xiě)操作。

is = response.body().byteStream();
byte[] buffer = new byte[1024];
int len = -1;
int length = 0;
while (length < threadLength && (len = is.read(buffer)) != -1) {
        threadFile.write(buffer, 0, len);
        //計(jì)算累計(jì)下載的長(zhǎng)度
        length += len;
 
}

如何在子線程里把當(dāng)前正寫(xiě)入的文件的size傳給Android界面以作進(jìn)度展示

答案就是:回調(diào)函數(shù)。

我們先設(shè)一個(gè)接口如下

package org.mk.android.demo.http;
 
public interface DownloadListener {
    public void onDownload(int size,int totalSize);
}

然后我們?cè)诰€程實(shí)例化時(shí)需要轉(zhuǎn)入這個(gè)接口

public DownLoadThread(int threadId, int startPosition,
                              RandomAccessFile threadFile, int threadLength, String downloadFilePath,DownloadListener downloadListener,

在寫(xiě)文件時(shí)我們作如下操作

while (length < threadLength && (len = is.read(buffer)) != -1) {
       threadFile.write(buffer, 0, len);
       //計(jì)算累計(jì)下載的長(zhǎng)度
       length += len;
       downloadListener.onDownload(length,totalSize);
}

而在外層調(diào)用時(shí)如下實(shí)現(xiàn)這個(gè)onDownload

multiDownloadHelper.download(new DownloadListener() {
     @Override
      public void onDownload(int size, int totalSize) {
            Log.d(TAG, ">>>>>>download size->" + size);
            float progress = ((float) size / (float) fileSize) * 100;
            int pgValue = (int) progress;
      }
});

就可以在多線程的最外層得到當(dāng)前寫(xiě)文件的“進(jìn)度”了。

下面就給出全代碼。

全代碼

前端

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
 
    <Button
        android:id="@+id/buttonDownload"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="download"
        android:textSize="20sp" />
 
 
    <ProgressBar
        android:id="@+id/progressBarDownload"
        style="@android:style/Widget.DeviceDefault.Light.ProgressBar.Horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:max="100" />
 
</LinearLayout>

后端

DownloadListener

package org.mk.android.demo.http;
 
public interface DownloadListener {
    public void onDownload(int size,int totalSize);
}

MultiDownloadHelper

package org.mk.android.demo.http;
 
import android.os.Environment;
import android.util.Log;
 
import androidx.annotation.NonNull;
 
import org.apache.commons.io.FilenameUtils;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.URL;
import java.util.EnumMap;
 
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
 
public class MultiDownloadHelper {
    private static final String TAG = "DemoMultiDownloadWithProgressBar";
    private int threadCount = 0;
    private String downloadFilePath = "";
 
    public MultiDownloadHelper(int threadCount, String filePath) {
        this.threadCount = threadCount;
        this.downloadFilePath = filePath;
    }
 
    private enum DownLoadThreadInfor {
        threadLength, startPosition
    }
 
    private EnumMap<DownLoadThreadInfor, Object> calcStartPosition(long fileLength, int threadNo) {
        int threadlength = (int) fileLength % threadCount == 0 ? (int) fileLength / threadCount : (int) fileLength + 1;
        int startPosition = threadNo * threadlength;
        EnumMap<DownLoadThreadInfor, Object> downloadThreadInfor = new EnumMap<DownLoadThreadInfor, Object>(DownLoadThreadInfor.class);
        downloadThreadInfor.put(DownLoadThreadInfor.threadLength, threadlength);
        downloadThreadInfor.put(DownLoadThreadInfor.startPosition, startPosition);
        return downloadThreadInfor;
    }
 
    private String generateTempFile(String filePath, long fileLength) throws Exception {
        String end = filePath.substring(filePath.lastIndexOf("."));
        URL url = new URL(filePath);
        //String downloadFilePath = "Cache_" + System.currentTimeMillis() + end;
        String urlFileName=FilenameUtils.getName(url.getPath());
        RandomAccessFile file = null;
        try {
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                String fileName = Environment.getExternalStorageDirectory().getCanonicalPath() + "/" + urlFileName;
                Log.d(TAG,">>>>>>寫(xiě)入->"+fileName);
                file = new RandomAccessFile(fileName, "rwd");
                file.setLength(fileLength);
                return fileName;
            } else {
                throw new Exception("SD卡不可讀寫(xiě)");
            }
        } catch (Exception e) {
            throw new Exception("GenerateTempFile error: " + e.getMessage(), e);
        } finally {
            try {
                file.close();
            } catch (Exception e) {
            }
        }
 
    }
 
    private class DownLoadThread extends Thread {
        private int threadId;
        private int startPosition;
        private RandomAccessFile threadFile;
        private int threadLength;
        private String downloadFilePath;
        private DownloadListener downloadListener;
        private int totalSize=0;
        public DownLoadThread(int threadId, int startPosition,
                              RandomAccessFile threadFile, int threadLength, String downloadFilePath,DownloadListener downloadListener,
                int totalSize) {
            this.threadId = threadId;
            this.startPosition = startPosition;
            this.threadFile = threadFile;
            this.threadLength = threadLength;
            this.downloadFilePath = downloadFilePath;
            this.downloadListener=downloadListener;
            this.totalSize=totalSize;
        }
 
        public void run() {
            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder()
                    .url(downloadFilePath)//請(qǐng)求接口,如果需要傳參拼接到接口后面
                    .build(); //創(chuàng)建Request對(duì)象
            Log.d(TAG, ">>>>>>線程" + (threadId + 1) + "開(kāi)始下載...");
            Call call = client.newCall(request);
            //異步請(qǐng)求
            call.enqueue(new Callback() {
                //失敗的請(qǐng)求
                @Override
                public void onFailure(@NonNull Call call, @NonNull IOException e) {
                    Log.e(TAG, ">>>>>>下載進(jìn)程加載->" + downloadFilePath + " error:" + e.getMessage(), e);
                }
 
                //結(jié)束的回調(diào)
                @Override
                public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
                    Log.d(TAG, ">>>>>>連接->" + downloadFilePath + " 己經(jīng)連接,進(jìn)入下載...");
                    InputStream is = null;
                    try {
                        if (200 == response.code()) {
                            Log.d(TAG, ">>>>>>response.code()==" + response.code());
                            Log.d(TAG, ">>>>>>response.message()==" + response.message());
                            is = response.body().byteStream();
                            byte[] buffer = new byte[1024];
                            int len = -1;
                            int length = 0;
                            while (length < threadLength && (len = is.read(buffer)) != -1) {
                                threadFile.write(buffer, 0, len);
                                //計(jì)算累計(jì)下載的長(zhǎng)度
                                length += len;
                                downloadListener.onDownload(length,totalSize);
                            }
                            Log.d(TAG, ">>>>>>線程" + (threadId + 1) + "已下載完成");
                        }
                    } catch (Exception e) {
                        Log.e(TAG, ">>>>>>線程:" + threadId + " 下載出錯(cuò): " + e.getMessage(), e);
                    } finally {
                        try {
                            threadFile.close();
                        } catch (Exception e) {
                        }
                        try {
                            is.close();
                            ;
                        } catch (Exception e) {
                        }
                    }
 
                }
            });
 
        }
    }
 
    public void download(DownloadListener downloadListener) {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(downloadFilePath)//請(qǐng)求接口,如果需要傳參拼接到接口后面
                .build(); //創(chuàng)建Request對(duì)象
        try {
            Call call = client.newCall(request);
            //異步請(qǐng)求
            call.enqueue(new Callback() {
                //失敗的請(qǐng)求
                @Override
                public void onFailure(@NonNull Call call, @NonNull IOException e) {
                    Log.e(TAG, ">>>>>>加載->" + downloadFilePath + " error:" + e.getMessage(), e);
                }
 
                //結(jié)束的回調(diào)
                @Override
                public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
                    //響應(yīng)碼可能是404也可能是200都會(huì)走這個(gè)方法
                    Log.i(TAG, ">>>>>>the response code is: " + response.code());
                    if (200 == response.code()) {
                        Log.d(TAG, ">>>>>>response.code()==" + response.code());
                        Log.d(TAG, ">>>>>>response.message()==" + response.message());
                        try {
                            long size = response.body().contentLength();
                            Log.d(TAG, ">>>>>>file length->" + size);
                            for (int i = 0; i < threadCount; i++) {
                                EnumMap<DownLoadThreadInfor, Object> downLoadThreadInforObjectEnumMap = new EnumMap<DownLoadThreadInfor, Object>(DownLoadThreadInfor.class);
                                downLoadThreadInforObjectEnumMap = calcStartPosition(size, i);
                                String threadFileName = generateTempFile(downloadFilePath, size);
                                int startPosition = (int) downLoadThreadInforObjectEnumMap.get(DownLoadThreadInfor.startPosition);
                                int threadLength = (int) downLoadThreadInforObjectEnumMap.get(DownLoadThreadInfor.threadLength);
                                RandomAccessFile threadFile = new RandomAccessFile(threadFileName, "rwd");
                                threadFile.seek(startPosition);
                                new DownLoadThread(i, startPosition, threadFile, threadLength, downloadFilePath,downloadListener,(int)size).start();
                                Log.d(TAG, ">>>>>>start thread: " + i + 1 + " start position->" + startPosition);
                            }
                        } catch (Exception e) {
                            Log.e(TAG, ">>>>>>get remote file size error: " + e.getMessage(), e);
                        }
                    }
                }
            });
        } catch (Exception e) {
            Log.e(TAG, ">>>>>>open connection to path->" + downloadFilePath + "\nerror: " + e.getMessage(), e);
        }
    }
}

MainActivity

package org.mk.android.demo.http;
 
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
 
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.Toast;
 
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "DemoMultiDownloadWithProgressBar";
    private static final String picUrl = "https://tqjimg.tianqistatic.com/toutiao/images/202106/08/3721f7ae444ddfc4.jpg";
    private Button buttonDownload;
    private ProgressBar progressBarDownload;
    private Context ctx=null;
 
    private Handler downloadHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
            Log.i(TAG, ">>>>>>receive handler Message msg.what is: " + msg.what);
            switch (msg.what) {
                case 101:
                    //Toast.makeText(ctx, "下載圖片完成", Toast.LENGTH_LONG).show();
                    progressBarDownload.setVisibility(View.VISIBLE);
                    //progressBarDownload.setProgress();
                    int inputNum = msg.getData().getInt("pgValue");
                    progressBarDownload.setProgress(inputNum);
                    if (inputNum >= 100) {
                        Toast.makeText(ctx, "下載圖片完成", Toast.LENGTH_LONG).show();
                    }
                    break;
            }
            return false;
        }
    });
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ctx=getApplicationContext();
        buttonDownload = (Button) findViewById(R.id.buttonDownload);
        progressBarDownload = (ProgressBar) findViewById(R.id.progressBarDownload);
        progressBarDownload.setVisibility(View.GONE);
        progressBarDownload.setMax(100);
        buttonDownload.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                    Log.i(TAG, ">>>>>>version.SDK->" + Build.VERSION.SDK_INT);
                    if (!Environment.isExternalStorageManager()) {
                        Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
                        startActivity(intent);
                        return;
                    }
                }
                MultiDownloadHelper multiDownloadHelper = new MultiDownloadHelper(3, picUrl);
                multiDownloadHelper.download(new DownloadListener() {
                    @Override
                    public void onDownload(int size, int totalSize) {
                        Log.d(TAG, ">>>>>>download size->" + size);
                        float progress = ((float) size / (float) totalSize) * 100;
                        int pgValue = (int) progress;
                        Message msg = new Message();
                        msg.what = 101;
                        Bundle bundle = new Bundle();
                        bundle.putInt("pgValue", pgValue);
                        msg.setData(bundle);
                        downloadHandler.sendMessage(msg);
                        Log.d(TAG, ">>>>>>current pgValue->" + progress);
                    }
                });
            }
        });
    }
}

此處需要注意的點(diǎn)是

  • 需要使用異步線程去驅(qū)動(dòng)我們的多線程;
  • 使用handler技術(shù)來(lái)處理進(jìn)度條的界面變化;

到此這篇關(guān)于Android入門(mén)之使用OKHttp多線程下載文件的文章就介紹到這了,更多相關(guān)Android OKHttp下載文件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論