想看原始碼的請移步github:github.com/kb185191420…
大家喜歡的話,就給個star^_^,有問題或者建議,可以直接提issues,也可以在部落格下面給我留言。謝謝~
在demo中我分別實現了視訊和圖片的下載,並附帶有下載進度顯示,視訊下載完成後運用exo播放器直接播放的,圖片只是用Glide簡單展示了一下。好了,我們步入正題吧!
一、新增依賴
在app的build.gradle的dependencies節點中新增以下程式碼:
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.google.android.exoplayer:exoplayer:r2.5.4'
implementation 'com.github.bumptech.glide:glide:4.3.1複製程式碼
俗話說的好,工欲善其事必先利器!我們分別新增Retrofit、exoplayer和glide的依賴,可能有朋友要問了,“implementation ”這是什麼玩意呀?添依賴不是用compile嗎?ok!兄弟不要急,如果你有這個疑問,很明顯你平日裡吃飯的傢伙什兒已經out了,趕緊去升級Android Studio3.0吧!www.android-studio.org/
二、新增許可權和動態許可權處理
在清單檔案AndroidManifest中的manifest節點中新增以下程式碼:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
複製程式碼
要實現將檔案下載到本地,那必然需要網路許可權和記憶體的讀寫許可權啦!
注意:由於我們用到了寫入記憶體的許可權,所以千萬要注意6.0以上動態許可權的申請!
我在demo裡用的是自己簡單封裝的許可權申請工具類,有興趣的可以直接去看demo原始碼。
三、設計回撥
/**
* Description:
* Created by kang on 2018/3/9.
*/
public interface DownloadListener {
void onStart();
void onProgress(int currentLength);
void onFinish(String localPath);
void onFailure();
}
複製程式碼
回撥中包括下載開始、下載進度、下載結束和下載失敗等四個方法。其中我們在下載進度的回撥中返回進度的百分比,在此可以將進度顯示在控制元件上;在下載結束的回撥中返回下載至本地的檔案路徑,在此可直接對下載完成的檔案進行操作。如果你還有一些個性化的需求,可以自行新增。
四、網路工具類準備
/**
* ApiHelper
* Created by kang on 2018/3/9.
*/
public class ApiHelper {
private static final String TAG = "ApiHelper";
private static ApiHelper mInstance;
private Retrofit mRetrofit;
private OkHttpClient mHttpClient;
private ApiHelper() {
this( 30, 30, 30);
}
public ApiHelper( int connTimeout, int readTimeout, int writeTimeout) {
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(connTimeout, TimeUnit.SECONDS)
.readTimeout(readTimeout, TimeUnit.SECONDS)
.writeTimeout(writeTimeout, TimeUnit.SECONDS);
mHttpClient = builder.build();
}
public static ApiHelper getInstance() {
if (mInstance == null) {
mInstance = new ApiHelper();
}
return mInstance;
}
public ApiHelper buildRetrofit(String baseUrl) {
mRetrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.client(mHttpClient)
.build();
return this;
}
public <T> T createService(Class<T> serviceClass) {
return mRetrofit.create(serviceClass);
}
}
複製程式碼
這裡我對Retrofit進行了簡單封裝。
/**
* Description:
* Created by kang on 2018/3/9.
*/
public interface ApiInterface {
/**
* 下載視訊
*
* @param fileUrl
* @return
*/
@Streaming //大檔案時要加不然會OOM
@GET
Call<ResponseBody> downloadFile(@Url String fileUrl);
}
複製程式碼
注意:對於大檔案的操作一定要加@Streaming,否則會出現OOM
五、檔案下載工具類準備
/**
* Description:下載檔案工具類
* Created by kang on 2018/3/9.
*/
public class DownloadUtil {
private static final String TAG = "DownloadUtil";
private static final String PATH_CHALLENGE_VIDEO = Environment.getExternalStorageDirectory() + "/DownloadFile";
//視訊下載相關
protected ApiInterface mApi;
private Call<ResponseBody> mCall;
private File mFile;
private Thread mThread;
private String mVideoPath; //下載到本地的視訊路徑
public DownloadUtil() {
if (mApi == null) {
//初始化網路請求介面
mApi = ApiHelper.getInstance().buildRetrofit("https://sapi.daishumovie.com/")
.createService(ApiInterface.class);
}
}
public void downloadFile(String url, final DownloadListener downloadListener) {
String name = url;
//通過Url得到檔案並建立本地檔案
if (FileUtils.createOrExistsDir(PATH_CHALLENGE_VIDEO)) {
int i = name.lastIndexOf('/');//一定是找最後一個'/'出現的位置
if (i != -1) {
name = name.substring(i);
mVideoPath = PATH_CHALLENGE_VIDEO +
name;
}
}
if (TextUtils.isEmpty(mVideoPath)) {
Log.e(TAG, "downloadVideo: 儲存路徑為空了");
return;
}
//建立一個檔案
mFile = new File(mVideoPath);
if (!FileUtils.isFileExists(mFile) && FileUtils.createOrExistsFile(mFile)) {
if (mApi == null) {
Log.e(TAG, "downloadVideo: 下載介面為空了");
return;
}
mCall = mApi.downloadFile(url);
mCall.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(@NonNull Call<ResponseBody> call, @NonNull final Response<ResponseBody> response) {
//下載檔案放在子執行緒
mThread = new Thread() {
@Override
public void run() {
super.run();
//儲存到本地
writeFile2Disk(response, mFile, downloadListener);
}
};
mThread.start();
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
downloadListener.onFailure(); //下載失敗
}
});
} else {
downloadListener.onFinish(mVideoPath); //下載完成
}
}
//將下載的檔案寫入本地儲存
private void writeFile2Disk(Response<ResponseBody> response, File file, DownloadListener downloadListener) {
downloadListener.onStart();
long currentLength = 0;
OutputStream os = null;
InputStream is = response.body().byteStream(); //獲取下載輸入流
long totalLength = response.body().contentLength();
try {
os = new FileOutputStream(file); //輸出流
int len;
byte[] buff = new byte[1024];
while ((len = is.read(buff)) != -1) {
os.write(buff, 0, len);
currentLength += len;
Log.e(TAG, "當前進度: " + currentLength);
//計算當前下載百分比,並經由回撥傳出
downloadListener.onProgress((int) (100 * currentLength / totalLength));
//當百分比為100時下載結束,呼叫結束回撥,並傳出下載後的本地路徑
if ((int) (100 * currentLength / totalLength) == 100) {
downloadListener.onFinish(mVideoPath); //下載完成
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (os != null) {
try {
os.close(); //關閉輸出流
} catch (IOException e) {
e.printStackTrace();
}
}
if (is != null) {
try {
is.close(); //關閉輸入流
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
複製程式碼
這一段時我們下載檔案的核心程式碼,我們來簡單分析一下。首先我在DownloadUtil這個類的建構函式中初始化了網路請求介面,然後提供了兩個方法,downloadFile和writeFile2Disk,顧名思義第一個是下載檔案的方法,第二個是將檔案寫入SDCard的方法。
方法一:downloadFile(String url, final DownloadListener downloadListener)
兩個引數:url和downloadListener
url是我們要下載的地址,downloadListener是第三步我們設計的下載回撥
先擷取url最後一個'/'之後的內容,得到我們將要儲存到本地的檔名,然後建立該檔案,通過網路請求得到Response物件,接著開啟子執行緒,呼叫writeFile2Disk方法。
方法二:writeFile2Disk(Response<ResponseBody> response, File file, DownloadListener downloadListener)
三個引數:Response物件,file和downloadListener 通過Response物件我們可以獲取到InputStream輸入流,file是之前建立好的本地資料夾,downloadListener是第三步我們設計的下載回撥
ok!到此我們要開始計算下載百分比了!
通過InputStream is = response.body().byteStream()可以獲取到下載的InputStream輸入流,通過long totalLength = response.body().contentLength()獲取到下載的總長度;再通過file建立輸出流os = new FileOutputStream(file); 此時通過輸入流的read(buff)方法每次讀取固定大小的buff(一般1024即可),再呼叫輸出流的write方法將buff寫入檔案,這是一個while迴圈,直到將輸入流的位元組全部讀取完畢,而正好在每次迴圈裡,我們可以將讀取的位元組數累加,得到當前已下載的位元組長度currentLength,(100*currentLength/totalLength)就是當前下載百分比啦,這個時候我們用downloadListener.onProgress回撥將進度傳出即可;當進度達到100時,將本地檔案地址通過downloadListener.onFinish回撥傳出! 最後別忘記在finally中關閉輸入輸出流!
到此,我的Retrofit帶進度下載檔案的核心程式碼已經介紹完畢了!有木有迫不及待的想要用用試試呢!
六、最後來看看使用
private void downloadPicture() {
mDownloadUtil = new DownloadUtil();
mDownloadUtil.downloadFile(PICTURE_URL, new DownloadListener() {
@Override
public void onStart() {
Log.e(TAG, "onStart: ");
runOnUiThread(new Runnable() {
@Override
public void run() {
fl_circle_progress.setVisibility(View.VISIBLE);
}
});
}
@Override
public void onProgress(final int currentLength) {
Log.e(TAG, "onLoading: " + currentLength);
runOnUiThread(new Runnable() {
@Override
public void run() {
circle_progress.setProgress(currentLength);
}
});
}
@Override
public void onFinish(final String localPath) {
Log.e(TAG, "onFinish: " + localPath);
runOnUiThread(new Runnable() {
@Override
public void run() {
fl_circle_progress.setVisibility(View.GONE);
Glide.with(mContext).load(localPath).into(iv_picture);
}
});
}
@Override
public void onFailure() {
Log.e(TAG, "onFailure: ");
runOnUiThread(new Runnable() {
@Override
public void run() {
fl_circle_progress.setVisibility(View.GONE);
}
});
}
});
}
複製程式碼
這裡我放上的是下載圖片的程式碼(視訊、檔案等都類似),fl_circle_progress是進度條的父佈局,circle_progress是環形進度條,在onStart中將fl_circle_progress顯示,onProgress中給circle_progress設定進度,onFinish中將fl_circle_progress隱藏,並利用Glide將下載完成的圖片顯示在iv_picture中,iv_picture就是一個imageView,如果下載過程中出錯那就在onFailure中將fl_circle_progress隱藏。因為對UI的處理需要在UI執行緒中進行,**所以這些處理需要通過runOnUiThread切換執行緒**!
到這裡整個下載過程就結束了,是不是很簡單呀!歡迎到github下載原始碼:
https://github.com/kb18519142009/DownloadFile 大家喜歡的話,就給個star^_^,有問題或者建議,可以直接提issues,也可以在部落格下面給我留言。謝謝~