1. 程式人生 > >Android 之 OkHttp + EventBus 進行後臺下載網路檔案

Android 之 OkHttp + EventBus 進行後臺下載網路檔案

前言:

        本篇文章純粹是個人學習日記。如有錯誤或不正確的地方,請指出,謝謝!

        我在學習郭霖的《第一行程式碼》,就想找找有沒有比較簡便的方法替代第十章的最佳實踐網路下載去下載網路檔案。然後就想用EventBus代替其中的 AsyncTask 和 Interface回撥介面。

1.EventBus可以非常方便的傳送和訂閱事件。

2.EventBus自帶執行緒池,利用自身的執行緒排程機制,隨意轉換子執行緒和主執行緒。

效果圖:


上圖是下載中的一些操作和回顯


上圖是完成時的一些回顯

自己畫了一張大概的"大局流程圖",方便大家更容易的理解。


有專家研究過,影象記憶是最好的記憶方法!

匯入:

我用的是Android Studio3.0 在app的build.gradle裡新增

implementation 'org.greenrobot:eventbus:3.1.1'                 //EventBus導包
implementation 'com.squareup.okhttp3:okhttp:3.10.0'            //OkHttp導包
implementation 'com.daimajia.numberprogressbar:library:[email protected]'//好看的ProgressBar

EventBus部分程式碼:

1.自定義MessageEvent實體類
public class MessageEvent {
    private int tag;            //標誌位,方便EventBus辨識
    private String url;         //下載地址,我也利用這個傳入一些操作資訊,所以名字不標準
    private int progress;       //反饋過程的進度

    public MessageEvent(int tag,String url,int progress) {
        super();
        this.tag = tag;
        this.url = url;
        this.progress = progress;
    }

    public int getProgress() {
        return progress;
    }

    public int getTag() {
        return tag;
    }

    public String getUrl() {
        return url;
    }
}

2.子執行緒的下載流程(敲黑板)

放在Service類裡面的

static final int START_DOWNLOAD = 1;            //標誌位:開始下載
static final int FAILED__DOWNLOAD = 2;          //標誌位:下載失敗  
static final int SUCCESS_DOWNLOAD = 3;          //標誌位:下載完成
static final int CANCEL_DOWNLOAD = 4;           //標誌位:取消下載
static final int PAUSE_DOWNLOAD = 5;            //標誌位:暫停下載
static final int UPDATE_PROGRESS = 6;           //標誌位:更新進度

private boolean isStarted = false;            //操作子執行緒的標誌狀態:是否執行
private boolean isCanceled = false;           //操作子執行緒的標誌狀態:是否取消
private boolean isPaused = false;             //操作子執行緒的標誌狀態:是否暫停

@Subscribe(threadMode = ThreadMode.ASYNC)
public void onSubThread(MessageEvent event) {
switch (event.getTag()) {
    case START_DOWNLOAD:
        //最開始的啟動前臺服務訊息
        startForeground(1,getNotification("正在獲取資源...",-1));

        InputStream is = null;
        RandomAccessFile saveFile = null;
        File file = null;
        try {
            //已經下載的長度
            long downloadLength = 0;

            //獲得傳入的下載地址
            String downloadUrl = event.getUrl();

            //根據地址擷取檔名
            String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));

            //檔名中不能包含特殊字元
            fileName = fileName.replace("?", "");
            fileName = fileName.replace("\\", "");
            fileName = fileName.replace(":", "");
            fileName = fileName.replace("*", "");

            //獲取SD卡下載目錄的路徑
            String directory = Environment.getExternalStoragePublicDirectory(
                    Environment.DIRECTORY_DOWNLOADS).getPath();

            //根據檔名和下載目錄生成檔案
            file = new File(directory + fileName);

            //如果檔案存在,獲取已下載的長度,等等實現斷點下載
            if (file.exists()) {
                downloadLength = file.length();
            }

            //獲取檔案總長度
            long contentLength = getContentLength(downloadUrl);

            if (contentLength == 0) {
                //獲取不了將要下載的檔案的總長度
                //發出資訊,下載失敗
                EventBus.getDefault().post(new MessageEvent(FAILED__DOWNLOAD,
                        "下載地址有誤!!", -1));
                isStarted = false;
                return;
            } else if (contentLength == downloadLength) {
                //總長度等於已下載的長度
                //說明已經完整下載好了
                //發出資訊,無需下載,已經下載好了
                EventBus.getDefault().post(new MessageEvent(SUCCESS_DOWNLOAD,
                        "已經下載完成!!", -1));
                return;
            }

            //開始斷點下載
            OkHttpClient client = new OkHttpClient().newBuilder()
                    .connectTimeout(15, TimeUnit.SECONDS)
                    .readTimeout(15, TimeUnit.SECONDS)
                    .writeTimeout(15, TimeUnit.SECONDS)
                    .sslSocketFactory(SSLSocketClient.getSSLSocketFactory())//配置
                    .hostnameVerifier(SSLSocketClient.getHostnameVerifier())//配置  !!看文章底部!!
                    .build();

            Request request = new Request.Builder()
                    //這個就是跳到斷點的地方
                    .addHeader("RANGE", "bytes=" + downloadLength + "-")
                    .url(downloadUrl)
                    .build();

            Response response = client.newCall(request).execute();
            if (response != null) {
                is = response.body().byteStream();
                saveFile = new RandomAccessFile(file, "rw");
                //跳到斷點的地方
                saveFile.seek(downloadLength);
                byte[] b = new byte[1024];
                int total = 0;
                int len;
                while ((len = is.read(b)) != -1) {

                    total += len;
                    saveFile.write(b, 0, len);

                    //計算已下載的百分比
                    int progress = (int) ((total + downloadLength) * 100 / contentLength);

                    //發出訊息,更新UI介面進度
                    EventBus.getDefault().post(new MessageEvent(UPDATE_PROGRESS, "", progress));
                    getManager().notify(1, getNotification("正在下載...", progress));

                    if (isCanceled) {
                        //發出訊息,取消下載
                        EventBus.getDefault().post(new MessageEvent(CANCEL_DOWNLOAD,
                                "取消下載!!", -1));
                        return;
                    }
                    if (isPaused) {
                        //發出訊息,暫停下載
                        EventBus.getDefault().post(new MessageEvent(PAUSE_DOWNLOAD,
                                "暫停下載!!", progress));
                        return;
                    }
                }
                response.close();
                //發出訊息,下載成功
                EventBus.getDefault().post(new MessageEvent(SUCCESS_DOWNLOAD, "", -1));
            }
        } catch (Exception e) {
            EventBus.getDefault().post(new MessageEvent(FAILED__DOWNLOAD,
                    e.getMessage(), -1));
            e.printStackTrace();
        }finally {
            try {
                if (is != null) {
                    is.close();
                }
                if (saveFile != null) {
                    saveFile.close();
                }
                if (isCanceled && file != null) {
                    isCanceled = false;
                    file.delete();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        break;
    default:

    }
}

其中有個獲取下載檔案大小的總長度方法

private long getContentLength(String downloadUrl) throws Exception {
    OkHttpClient client = new OkHttpClient().newBuilder()
            .connectTimeout(15, TimeUnit.SECONDS)
            .readTimeout(15, TimeUnit.SECONDS)
            .writeTimeout(15, TimeUnit.SECONDS)
            .sslSocketFactory(SSLSocketClient.getSSLSocketFactory())//配置
            .hostnameVerifier(SSLSocketClient.getHostnameVerifier())//配置   !!看文章底部!!
            .build();

    Request request = new Request.Builder()
            .url(downloadUrl)
            .build();

    Response response = client.newCall(request).execute();
    if (response != null && response.isSuccessful()) {
        long contentLength = response.body().contentLength();
        response.close();
        return contentLength;
    }
    return 0;
}

還有獲取NotificationManager 和 傳送前臺服務的方法

private NotificationManager getManager() {
    return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
}

private Notification getNotification(String content,int progress){
    Intent intent = new Intent(this, MainActivity.class);
    PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
    Notification.Builder builder = new Notification.Builder(this);

    builder.setContentTitle("Day3_22");                    //這裡我改成自己的app名字
    builder.setSmallIcon(R.mipmap.ic_launcher);
    builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
    builder.setContentIntent(pi);
    builder.setContentText(content);
    if (progress >= 0) {
        builder.setContentText(progress + " %");
        builder.setProgress(100, progress, false);
    }

    return builder.build();
}
3.主執行緒的回顯操作
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMainThread(MessageEvent event) {
switch (event.getTag()) {
    case FAILED__DOWNLOAD:
        isStarted = false;
        Toast.makeText(this,
                event.getUrl(),Toast.LENGTH_SHORT).show();
        stopForeground(true);
        getManager().notify(1, getNotification("下載失敗," + event.getUrl(), -1));
        break;

    case SUCCESS_DOWNLOAD:
        isStarted = false;
        Toast.makeText(this,
                "下載完成!!",Toast.LENGTH_SHORT).show();
        stopForeground(true);
        getManager().notify(1, getNotification("下載完成!!", -1));
        break;

    case CANCEL_DOWNLOAD:
        isStarted = false;
        Toast.makeText(this,
                "取消下載並刪除殘留檔案!!",Toast.LENGTH_SHORT).show();
        stopForeground(true);
        getManager().notify(1, getNotification("取消下載並刪除殘留檔案!!", -1));
        break;

    case PAUSE_DOWNLOAD:
        isStarted = false;
        isPaused = false;
        Toast.makeText(this,
                "暫停下載!!",Toast.LENGTH_SHORT).show();
        getManager().notify(1, getNotification("暫停下載!!", -1));
        break;
    }
}


Service部分程式碼:

因為要保證子執行緒在後臺持續執行,當然要後臺會維持。

1.因為要用到EventBus,所以生命週期裡要註冊和登出
public class DownloadService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
        EventBus.getDefault().register(this);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mBinder.cancelDownload();                //強制關閉APP,防止子執行緒還在下載
        EventBus.getDefault().unregister(this);
    }
}

2.自定義的Binder
public class DownloadService extends Service {
    private DownloadBinder mBinder = new DownloadBinder();

    class DownloadBinder extends Binder{
        void startDownload(String downloadUrl){
            isStarted = true;
            EventBus.getDefault().post(new MessageEvent(START_DOWNLOAD, downloadUrl, 0));
        }

        void pauseDownload(){
            isStarted = false;
            isPaused = true;
        }

        void cancelDownload(){
            isStarted = false;
            isCanceled = true;
        }

        boolean isStarted(){
            return isStarted;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}


MainActivity的部分程式碼:

1.因為也需要EventBus訂閱事件改變progressBar

    所以生命週期方法裡要註冊和登出,至於Service繫結或onDestory的解綁我就不寫了

public class Main2Activity extends AppCompatActivity {
    private NumberProgressBar npb;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);

        npb = findViewById(R.id.numberProgressBar);
        npb.setProgressTextSize(33f);
        npb.setReachedBarHeight(5f);
        npb.setUnreachedBarHeight(5f);
    }
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMainThread(MessageEvent event) {
        switch (event.getTag()) {
            case UPDATE_PROGRESS:
                npb.setProgress(event.getProgress());
                break;

            case CANCEL_DOWNLOAD:
                npb.setProgress(0);
                break;

            default:

        }
    }

    @Override
    public void onStart() {
        super.onStart();
        EventBus.getDefault().register(this);
    }

    @Override
    public void onStop() {
        super.onStop();
        EventBus.getDefault().unregister(this);
    }

}


2.各按鈕的對應方法
至於Service繫結或onDestory的解綁我就不寫了
public void startDownload(View view) {
    if (mBinder.isStarted()) {
        Toast.makeText(this, "正在下載,請勿重複點選。",
                Toast.LENGTH_SHORT).show();
    } else {
        //空地址測試連結
//            String url = "";

        //嗶哩嗶哩客戶端
//            String url = "https://dl.hdslb.com/mobile/latest/" +
//                    "iBiliPlayer-bili.apk?spm_id_from=333.47.download-link.1";

        //taptap客戶端
        String url = "https://c.tapimg.com/pub2/201803/" +
                "82fef2820826b8b0738ef95f463f1403.apk?_upd=com.taptap_1.9.11.apk";

        mBinder.startDownload(url);
    }
}

public void pauseDownload(View view) {
    if (mBinder.isStarted()) {
        mBinder.pauseDownload();
    } else {
        Toast.makeText(this, "當前沒有下載任務!!",
                Toast.LENGTH_SHORT).show();
    }
}

public void cancelDownload(View view) {
    if (mBinder.isStarted()) {
        mBinder.cancelDownload();
    } else {
        Toast.makeText(this, "當前沒有下載任務!!",
                Toast.LENGTH_SHORT).show();
    }
}

實現過程中遇到的坑:

1.從程式碼中看到  !!看文章底部!!,來這裡集合

    為什麼要加 //配置 那兩條程式碼,因為下載時會出現 沒法認證https證書問題!!

2.建立檔案時,檔名是擷取下載地址"/"後面的一部分

    但是就是這一部分都有些特殊符號例如:? 就是不能作為檔名一部分

解決辦法:我在程式碼中去掉擷取檔名的特殊符號。

    但是這不是最好的辦法!我很好奇瀏覽器怎麼獲取到正確規範的檔名。求有心人指點!先謝!

3.儲存空間滿了!下載報錯!

解決辦法:這個只能彈出訊息提示 下載錯誤:儲存空間已滿!

Thanks:

    以上部分程式碼來自

        郭霖大神的《第一行程式碼》

相關推薦

Android OkHttp + EventBus 進行後臺下載網路檔案

前言:        本篇文章純粹是個人學習日記。如有錯誤或不正確的地方,請指出,謝謝!        我在學習郭霖的《第一行程式碼》,就想找找有沒有比較簡便的方法替代第十章的最佳實踐網路下載去下載網路檔案。然後就想用EventBus代替其中的 AsyncTask 和 Int

Android利用EventBus進行資料傳遞

在專案中,不可避免的要在兩個頁面之間進行資料的傳遞,就算不傳遞,也需要進行重新整理之類的,我們根據Google提供的庫類方法,也是可以做的,主要有廣播broadcastreceiver,startactivity方法或者是application例項等等,都是可以工作的(只要

Android利用EventBus進行訊息傳遞

前言:EventBus是上週專案中用到的,網上的文章大都一樣,或者過時,有用的沒幾篇,經過琢磨,請教他人,也終於弄清楚點眉目,記錄下來分享給大家。 一、概述 EventBus是一款針對Android優化的釋出/訂閱事件匯流排。主要功能是替代Intent

Android使用HttpURLConnection進行網路訪問

一、概述 在Android 上傳送HTTP 請求的方式一般有兩種:HttpURLConnection 和HttpClient。因為在Android 5.0之後,HttpClient被HttpURLConnecetion替代,後來在Android 6.0完全被捨

androidOkhttp框架進行網路請求的工具類()

package com.example.utils; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Iterat

androidOkHttp簡單使用,鍵值對,json資料,檔案上傳。

okHttp 的簡單使用 引入Gradle依賴:compile 'com.squareup.okhttp3:okhttp:3.4.1' HTTP GET請求 MainActivity程式碼如下: import android.support.v7

Android使用webView長按儲存下載網路圖片

最近發現在webView的setOnLongClickListener中可以獲取到WebView.HitTestResult,根據獲取的HitTestResult的Type來判斷做不同的處理。通過判斷Type的型別獲取點選圖片的url,然後把圖片下載到本地,傳送廣播通知系統

Android利用ColorMatrix進行圖片的各種特效處理

原圖:效果1:效果2: 效果3:效果4: 檢視官方的API,其中ColorMatrix的說明如下: 5x4 matrix for transforming the color+alpha components of a Bitmap. The matri

Androidokhttp攔截器的使用

上週在部門的技術分享會上簡單分享了一下使用okhttp攔截器的使用,趁著端午假期好好將內容整理整理,希望能夠幫助到其他的朋友 okhttp攔截器主要在以下幾種情況使用: 網路請求、響應日誌輸出 在Header中統一新增cookie、token 設定網路

Python 下載網路檔案

# -*- coding: utf-8 -*- import requests import csv #下載檔案 def downloadfiles(url,count): f = requests.get(url) filename = str(count)+".pdf" with open

Deepin學習筆記---如何多執行緒下載網路檔案

**** 命令列下載網路檔案* 當時看到師兄用多執行緒下載一個檔案,簡直帥呆了,然後自己偷摸學習了一下,很爽,這邊推薦兩個命令列下載網路檔案的方法,前提是要有下載連結哦! 一、 1.wget下載方式 wget http://www.linuxsense.or

Java多執行緒下載網路檔案

新建一個JavaGUI介面 package Download; import java.awt.EventQueue; import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.Action

Android的斷點續傳的下載線上檔案示例

Android的斷點續傳的下載線上檔案示例 檔案的結構如下: activity_main.xml: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://

下載網路檔案HttpURLConnection.getContentLength()大小為 -1

做一個andriod系統,測試的時候是在android 2.2系統上測試的一切正常,等釋出的時候發現個小問題,就是當程式有更新時,需要重新下載APK,為了友好,做了個進度條,但是在 2.2以上的系統中進度條不會走動,部分程式碼如下:    HttpURLConnecti

Android動態修改system/etc目錄下檔案的一種實現方式-SELinux

在沒有root的前提下,system分割槽為只讀,若要動態修改該分割槽下的檔案,可以按照下面流程實現: 1.寫執行指令碼,這裡以修改system/etc/hosts檔案為例,在/device/mediatek/mt67xx目錄下建立名為modifyhosts.sh的檔案,檔

HttpURLConnection下載網路檔案,載入網路圖片

說明: 做sdk開發的時候(sdk不採取任何第三方框架),涉及到下載網路檔案,和載入網路圖片的功能,由於不能用第三方jar包進行,所以只能用基本的HttpURLConnection把檔案作為流來處理,進行下載和載入。 1、HttpURLConnection載入圖片 程

Android根據Uri獲得圖片或視訊檔案路徑(解決4.4以上版本得不到路徑的情況)

package com.example.listviewcheckdemo; import android.annotation.TargetApi; import android.content.ContentUris; import android.content.Co

MFC 下載網路檔案到本地 利用 CHttpFile 和 URLDownloadToFile 【可設超時及進度】兩種方式

說明 方法1較為簡單,通用的多,但在某些環境下可能出現未知錯誤(也有可能你碰不到,反正我是碰到了)。 方法2實現相對麻煩點,但可設定超時時間以及進度展示,但要例項化一個LPBINDSTATUSCALLBACK 子類,在這個子類中去實現。這個相對好用點,方法1

效能優化-AndroidANR問題分析解決 traces.txt檔案分析 CPU佔用過高

(由於公司專案特殊情況,需要使用一些小廠的三防功能手機,不能使用我們平時用的這些民用手機) 前期測試的時候是用民用手機測試的,有六七種機型(小米,華為,中興,oppo),使用過程中均沒有出現ANR的情況,但是在公司採購的一款工程機上面用了一段時間後肯定就會出現ANR,出現了

Android技能樹 — 網路小結 OkHttp超超超超超超超詳細解析

前言: 本文也做了一次標題黨,哈哈,其實寫的還是很水,各位原諒我O(∩_∩)O。 介於自己的網路方面知識爛的一塌糊塗,所以準備寫相關網路的文章,但是考慮全部寫在一篇太長了,所以分開寫,希望大家能仔細看,最好可以指出我的錯誤,讓我也能糾正。 1.講解相關的整個網路體系結構: 網路體系結構小結 2.講解