1. 程式人生 > >Androif Rxjava+RxAndroid+ReTrofit2+okHttp3使用

Androif Rxjava+RxAndroid+ReTrofit2+okHttp3使用

在 app 的 build.gradle 中

dependencies{
    / /網路請求框架Rxjava+RxAndroid+ReTrofit2+okHttp3+RxBinding
   //匯入retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.4.0'
    //轉換器,請求結果轉換成Model
    implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
    //配合Rxjava 使用
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
    //RxJava
    implementation "io.reactivex.rxjava2:rxjava:2.2.3"
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' // 必要rxandrroid依賴,切執行緒時需要用到
    //Gson 庫
    //implementation 'com.google.code.gson:gson:2.8.5'
    //日誌攔截器
    implementation 'com.squareup.okhttp3:logging-interceptor:3.5.0'
    //RxBinding
    implementation 'com.jakewharton.rxbinding3:rxbinding:3.0.0-alpha1'
    }

一:Observer 返回的請求結果統一處理類**

package io.dcloud.H56580E2E.util;

import android.accounts.NetworkErrorException;
import java.net.ConnectException;
import java.net.UnknownHostException;
import java.util.concurrent.TimeoutException;

import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;

/**
 * Observer 返回的請求結果統一處理類
 * @param <T>
 */
public abstract  class BaseObserver<T> implements Observer<T> {
    protected String errMsg = "";
    //訂閱器
    protected Disposable disposable;

    //開始
    @Override
    public void onSubscribe(Disposable d) {
        disposable = d;
        //請求開始
        onRequestStart();
    }

    //獲取資料
    @Override
    public void onNext(T t) {
        try {
            //請求成功,獲取資料
            onSuccees(t);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //失敗
    @Override
    public void onError(Throwable e) {
        onRequestEnd();
        try {
            if (e instanceof ConnectException
                    || e instanceof TimeoutException
                    || e instanceof NetworkErrorException
                    || e instanceof UnknownHostException) {
                onFailure(e, true);  //網路錯誤
            } else {
                onFailure(e, false);
            }
        } catch (Exception e1) {
            e1.printStackTrace();
        }
    }

    //結束
    @Override
    public void onComplete() {
        onRequestEnd();

    }

    /**
     * 返回失敗,外部類要繼承實現的
     *
     * @param e
     * @param isNetWorkError 是否是網路錯誤
     * @throws Exception
     */
    protected abstract void onFailure(Throwable e, boolean isNetWorkError) throws Exception;

    /**
     * 返回成功 外部類要繼承實現的
     *
     * @param t
     * @throws Exception
     */
    protected abstract void onSuccees(T t) throws Exception;


    /**
     * 請求開始 如果外部類要繼承實現,就加上修飾符  abstract
     */
    protected void onRequestStart() {
        //開始進度條
        /*if (progressHUD != null) {
            progressHUD.setLabel(labelTxt);
        }*/
    }

    /**
     * 請求結束  如果外部類要繼承實現,就加上修飾符  abstract
     */
    protected void onRequestEnd() {
        //取消訂閱
        if (disposable != null && !disposable.isDisposed()) {
            disposable.dispose();
        }
        //結束進度條
       /* if (progressHUD != null) {
            progressHUD.dismiss();
            progressHUD = null;
        }*/
    }
}

二:使用 okhttp3 配置請求攔截器,

package io.dcloud.H56580E2E.util;

import android.util.Log;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;

/**
 * 請求攔截器
 * 專案中,每個介面都有一些基本的相同的引數
 * 我們不必每個介面都去寫,可以寫一個攔截器,在攔截器裡面攔截請求,為每個請求都新增相同的公共引數。
 */

public class HttpCommonInterceptor implements Interceptor {
    private Map<String,String> mHeaderParamsMap = new HashMap<>();
    public HttpCommonInterceptor() {
    }
    @Override
    public Response intercept(Chain chain) throws IOException {
        Log.d("HttpCommonInterceptor","add common params");
        Request oldRequest = chain.request();
        // 新增新的引數,新增到url 中
        /*HttpUrl.Builder authorizedUrlBuilder = oldRequest.url().newBuilder()
        .scheme(oldRequest.url().scheme())
        .host(oldRequest.url().host());*/

        // 新的請求
        Request.Builder requestBuilder =  oldRequest.newBuilder();
        requestBuilder.method(oldRequest.method(), oldRequest.body());

        //新增公共引數,新增到header中
        if(mHeaderParamsMap.size() > 0){
            for(Map.Entry<String,String> params:mHeaderParamsMap.entrySet()){
                requestBuilder.header(params.getKey(),params.getValue());
            }
        }
        Request newRequest = requestBuilder.build();
        return chain.proceed(newRequest);
    }

    public static class Builder{
        HttpCommonInterceptor mHttpCommonInterceptor;
        public Builder(){
            mHttpCommonInterceptor = new HttpCommonInterceptor();
        }

        public Builder addHeaderParams(String key, String value){
            mHttpCommonInterceptor.mHeaderParamsMap.put(key,value);
            return this;
        }

        public Builder  addHeaderParams(String key, int value){
            return addHeaderParams(key, String.valueOf(value));
        }

        public Builder  addHeaderParams(String key, float value){
            return addHeaderParams(key, String.valueOf(value));
        }

        public Builder  addHeaderParams(String key, long value){
            return addHeaderParams(key, String.valueOf(value));
        }

        public Builder  addHeaderParams(String key, double value){
            return addHeaderParams(key, String.valueOf(value));
        }

        public HttpCommonInterceptor build(){
            return mHttpCommonInterceptor;
        }

    }
}

三:Retrofit單例初始化類

package io.dcloud.H56580E2E.util;

import java.util.concurrent.TimeUnit;

import io.dcloud.H56580E2E.service.MainInterfaceService;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;


/**
 * Retrofit單例初始化類
 * Created by Administrator on 2017/10/7.
 * 用於Retrofit的初始化
 * 該類為單例模式
 */

public class RetrofitHelper {
    private static final int DEFAULT_TIME_OUT = 5;//超時時間 5s
    private static final int DEFAULT_READ_TIME_OUT = 10;
    private Retrofit mRetrofit;

    private RetrofitHelper() {
        // 建立 OKHttpClient
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(DEFAULT_TIME_OUT, TimeUnit.SECONDS);//連線超時時間
        builder.writeTimeout(DEFAULT_READ_TIME_OUT, TimeUnit.SECONDS);//寫操作 超時時間
        builder.readTimeout(DEFAULT_READ_TIME_OUT, TimeUnit.SECONDS);//讀操作超時時間

        // 新增公共引數攔截器,將每次請求的公共引數寫到這裡面
       HttpCommonInterceptor commonInterceptor = new HttpCommonInterceptor.Builder()
                .addHeaderParams("", "")
                .build();
        builder.addInterceptor(commonInterceptor);

        // 建立Retrofit
        mRetrofit = new Retrofit.Builder()
                .client(builder.build())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //配置支援RJava2的 Observable
                .addConverterFactory(GsonConverterFactory.create()) //設定資料解析器,會將返回的資料自動轉換為 對應的 class
                .baseUrl(APIURL.bangni)
                .build();
    }

    private static class SingletonHolder {
        private static final RetrofitHelper INSTANCE = new RetrofitHelper();
    }

    /**
     * 獲取RetrofitServiceManager
     *
     * @return
     */
    public static RetrofitHelper getInstance() {
        return SingletonHolder.INSTANCE;
    }

    /**
     * 獲取對應的Service
     *
     * @param service Service 的 class
     * @param <T>   使用方法:   mMovieService = RetrofitServiceManager.getInstance().create(MovieService.class);
     * @return
     */
    public <T> T create(Class<T> service) {
        return mRetrofit.create(service);
    }


    /**
     * 首頁資料的服務
     *
     * @return
     */
    /*public MainInterfaceService getMain_interfaceService() {
        return mRetrofit.create(MainInterfaceService.class);
    }*/

}

使用

mvp 模式

請求所用的實體類

package io.dcloud.H56580E2E.info;

import java.util.List;

/**
 * Created by Administrator on 2018/1/25.
 * 首頁輪播圖的的類
 */

public class ImageInfo {

    private List<ImgsBean> imgs;

    public List<ImgsBean> getImgs() {
        return imgs;
    }

    public void setImgs(List<ImgsBean> imgs) {
        this.imgs = imgs;
    }

    public static class ImgsBean {
        @Override
        public String toString() {
            return "ImgsBean{" +
                    "src='" + src + '\'' +
                    ", href='" + href + '\'' +
                    ", id='" + id + '\'' +
                    ", roles='" + roles + '\'' +
                    ", verify='" + verify + '\'' +
                    '}';
        }

        /**
         * src : /static/banner/c.png
         * href : custom-service.html
         * id : spring-festival
         * roles :
         * verify :
         */

        private String src;
        private String href;
        private String id;
        private String roles;
        private String verify;

        public String getSrc() {
            return src;
        }

        public void setSrc(String src) {
            this.src = src;
        }

        public String getHref() {
            return href;
        }

        public void setHref(String href) {
            this.href = href;
        }

        public String getId() {
            return id;
        }

        public void setId(String id) {
            this.id = id;
        }

        public String getRoles() {
            return roles;
        }

        public void setRoles(String roles) {
            this.roles = roles;
        }

        public String getVerify() {
            return verify;
        }

        public void setVerify(String verify) {
            this.verify = verify;
        }
    }
    @Override
    public String toString() {
        return "ImageInfo{" +
                "imgs=" + imgs +
                '}';
    }
}

一:建立 Service 服務介面

package io.dcloud.H56580E2E.service;

import io.dcloud.H56580E2E.info.ImageInfo;
import io.reactivex.Observable;
import retrofit2.http.GET;

/**
 * Created by Administrator on 2018/2/23.
 */

public interface MainInterfaceService {
    /**
     * 獲取首頁的圖片輪播資料
     * @return
     */
    @GET("/static/lunbo")
    Observable<ImageInfo> getShufflingImage();
}

二:建立 View 檢視介面

package io.dcloud.H56580E2E.view;

import io.dcloud.H56580E2E.info.ImageInfo;

/**
 * Created by Administrator on 2018/2/23.
 * 首頁資料載入
 */

public interface MainListenerView extends View{
    /**
     * 請求輪播的圖片成功返回的資料
     * @param datas
     */
    void request_ShufflingImageSuccess(ImageInfo datas);

    /**
     * 請求輪播的圖片異常返回的資料
     * @param datas
     */
    void requeste_ShufflingImageError(String datas);


}

三:建立 業務處理 Presenter 類

package io.dcloud.H56580E2E.presenter;

import android.content.Context;
import android.util.Log;

import io.dcloud.H56580E2E.info.ImageInfo;
import io.dcloud.H56580E2E.service.MainInterfaceService;
import io.dcloud.H56580E2E.util.BaseObserver;
import io.dcloud.H56580E2E.util.RetrofitHelper;
import io.dcloud.H56580E2E.view.MainListenerView;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;


/**
 * Created by Administrator on 2018/2/23.
 * 首頁的資料請求
 */

public class MainInterfacePresenter implements Presenter{
    private MainInterfaceService main_interfaceService;
    private Context mcontext;
    private MainListenerView main_interfaceView;


    public MainInterfacePresenter(Context mcontext, MainListenerView main_interfaceView) {
        this.mcontext = mcontext;
        this.main_interfaceView = main_interfaceView;
        //通過 RetrofitHelper 單例類獲取 服務介面
        main_interfaceService= RetrofitHelper.getInstance().create(MainInterfaceService.class);
    }

    /**
     * 獲取首頁圖片輪播的資料
     */
    public void getShufflingImage(){
        main_interfaceService.getShufflingImage()
                .subscribeOn(Schedulers.io()) //在io執行緒中請求
                .observeOn(AndroidSchedulers.mainThread())//請求完成後在主線成請求
                .subscribe(new BaseObserver<ImageInfo>() {
                    @Override
                    protected void onFailure(Throwable e, boolean isNetWorkError) throws Exception {
                        if(isNetWorkError){
                            //網路錯誤
                        }else{
                            //其他錯誤
                        }
                    }

                    @Override
                    protected void onSuccees(ImageInfo imageInfo) throws Exception {
                        main_interfaceView.request_ShufflingImageSuccess(imageInfo);
                    }

                });
    }
}

四:在 Activity 中使用

package io.dcloud.H56580E2E;

import androidx.appcompat.app.AppCompatActivity;
import io.dcloud.H56580E2E.info.ImageInfo;
import io.dcloud.H56580E2E.presenter.MainInterfacePresenter;
import io.dcloud.H56580E2E.view.MainListenerView;

import android.os.Bundle;

//實現 服務請求完成的View操作介面(MainListenerView)
public class MainActivity extends AppCompatActivity implements MainListenerView {
   //建立 Presenter 業務類物件
    private MainInterfacePresenter mainInterfacePresenter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //例項化物件
        mainInterfacePresenter=new MainInterfacePresenter(this,this);
        //開始請求獲取首頁圖片輪播的資料
        mainInterfacePresenter.getShufflingImage();
    }
    
    
    //請求輪播圖成功的
    @Override
    public void request_ShufflingImageSuccess(ImageInfo datas) {
        
    }
    //請求輪播圖失敗的
    @Override
    public void requeste_ShufflingImageError(String datas) {

    }
}

幾種請求的方式:

    /**
     * 請求登入(傳參方法1)
     * @param userphone
     * @param useerpwd
     * @return
     * (@Query)相當於 ?a=1&b=2;
     *  http://102.10.10.132/user/login?userphone=123&userpwd=123
     */

    @GET("user/login/")
    Observable<UserInfo> getUserInfoLogin2();
                                           
    /**
     * 請求登入(傳參方法2)引數動態的
     * @param userphone
     * @param useerpwd
     * @return
     * (@Query)相當於 ?a=1&b=2;
     *  http://102.10.10.132/user/login?userphone={userphone}&userpwd={userpwd}
     */

    @GET("user/login/")
    Observable<UserInfo> getUserInfoLogin2(@Query("userphone") String userphone,
                                           @Query("userpwd") String useerpwd);

    /**
     * 新增(傳參方法3)
     * @param map
     * @return
     * (@Querymap)多個引數在URL問號之後,且個數不確定;
     */

    @GET("user/insert/")
    Observable<UserInfo> insertUserInfo(@QueryMap Map<String, String> map);

   /**
     * 新增(傳參方法4)
     * @param userphone
     * @param map
     * @return
     * (@Querymap)多個引數在URL問號之後,且個數不確定;混合傳參
     */

    @GET("user/insert/")
    Observable<UserInfo> insertUserInfo2(@Query("userphone") String userphone, @QueryMap Map<String, String> map);

    /**
     * 請求登入(傳參方法Post請求1)
     * @param userphone
     * @param useerpwd
     * @return
     * @Field,@FieldMap:Post方式傳遞簡單的鍵值對,@FieldMap用法和@QueryMap的格式相同(用於POST請求,提交單個數據)
     * 新增@FormUrlEncoded表示表單提交 (Content-Type:application/x-www-form-urlencoded)
     */

    @FormUrlEncoded
    @POST("user/login/")
    Observable<UserInfo> getUserInfoLogin3(@Field("userphone") String userphone, @Field("userpwd") String useerpwd);
   
  
   /**
     * 請求登入(傳參方法Post請求2)
     * @return
     * @Body:用於POST請求體,將例項物件根據轉換方式轉換為對應的json字串引數,
     *              提交到後臺,傳遞的類中的屬性名必須和伺服器端接收的引數名要相同
     * 這個轉化方式是GsonConverterFactory定義的。
     */
    @Headers({"Content-Type:application/json;charset=utf-8", "Accept:application/json;"})
    @POST("user/login/") 
    Observable<UserInfo> postAgencyLogin(@Body RequestBody route);//實現json格式的傳輸,例項下面會有介紹

   /**
     * 請求登入(傳參方法Post請求3)
     * @return
     * 鍵值對方式傳輸,引數組合一個 Map 集合傳遞就行
     */
    @POST("user/login/")
    @FormUrlEncoded
    Observable<UserInfo> doLogin(@FieldMap Map<String,String> params);
    

    /**
     * method:網路請求的方法(區分大小寫)
     * path:網路請求地址路徑
     * hasBody:是否有請求體.。
     * 作用:替換@GET、@POST、@PUT、@DELETE、@HEAD註解的作用 及 更多功能拓展
     * 具體使用:通過屬性method、path、hasBody進行設定
     */
    @HTTP(method = "GET", path = "user/login/{userphone}/{userpwd}", hasBody = false)
    Call<ResponseBody> getCall(@Path("userphone") String userphone, @Path("userpwd") String useerpwd);

post服務請求傳遞 json型別資料

import com.google.gson.Gson;
 
/**
 * Created by Administrator on 2016/4/15.
 *
 * Gson封裝類
 */
public class GsonUtils {
    private static Gson gson;
 
    static {
        if (gson == null) {
            gson = new Gson();
        }
    }
 
    /**
     * 物件轉Json字串
     *
     * @param object
     * @return
     */
    public static String toJson(Object object) {
        checkGson();
        return gson.toJson(object);
    }
 
    /**
     * 字串轉Json物件
     *
     * @param json
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T fromJson(String json, Class<T> clazz) {
        checkGson();
        return gson.fromJson(json, clazz);
    }
 
    private static void checkGson() {
        if (gson == null) {
            gson = new Gson();
        }
    }
 
}

post請求 json 型別資料傳遞

    @Headers({"Content-Type:application/json;charset=utf-8", "Accept:application/json;"})
    @POST("user/login/") 
    Observable<LoginBean> postAgencyLogin(@Body RequestBody route);
   
   //組合引數
    Map<String, String> map = new HashMap<>();
    map.put("phone", "15888888888");
    map.put("password", CommonUtils.encodeMD5("123456").toUpperCase());
    RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), CommonUtils.convertPostJson(map));
        

   
    

擴充套件 OKHttp工具類(上傳,下載檔案)

package io.dcloud.H56580E2E.upload.listener.impl.handler;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;

import io.dcloud.H56580E2E.upload.listener.impl.UIProgressListener;
import io.dcloud.H56580E2E.upload.listener.impl.model.ProgressModel;

import java.lang.ref.WeakReference;



public abstract class ProgressHandler extends Handler {
    public static final int UPDATE = 0x01;
    public static final int START = 0x02;
    public static final int FINISH = 0x03;
    //弱引用
    private final WeakReference<UIProgressListener> mUIProgressListenerWeakReference;

    public ProgressHandler(UIProgressListener uiProgressListener) {
        super(Looper.getMainLooper());
        mUIProgressListenerWeakReference = new WeakReference<UIProgressListener>(uiProgressListener);
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case UPDATE: {
                UIProgressListener uiProgessListener = mUIProgressListenerWeakReference.get();
                if (uiProgessListener != null) {
                    //獲得進度實體類
                    ProgressModel progressModel = (ProgressModel) msg.obj;
                    //回撥抽象方法
                    progress(uiProgessListener, progressModel.getCurrentBytes(), progressModel.getContentLength(), progressModel.isDone());
                }
                break;
            }
            case START: {
                UIProgressListener uiProgressListener = mUIProgressListenerWeakReference.get();
                if (uiProgressListener != null) {
                    //獲得進度實體類
                    ProgressModel progressModel = (ProgressModel) msg.obj;
                    //回撥抽象方法
                    start(uiProgressListener, progressModel.getCurrentBytes(), progressModel.getContentLength(), progressModel.isDone());

                }
                break;
            }
            case FINISH: {
                UIProgressListener uiProgressListener = mUIProgressListenerWeakReference.get();
                if (uiProgressListener != null) {
                    //獲得進度實體類
                    ProgressModel progressModel = (ProgressModel) msg.obj;
                    //回撥抽象方法
                    finish(uiProgressListener, progressModel.getCurrentBytes(), progressModel.getContentLength(), progressModel.isDone());
                }
                break;
            }
            default:
                super.handleMessage(msg);
                break;
        }
    }

    public abstract void start(UIProgressListener uiProgressListener,long currentBytes, long contentLength, boolean done);
    public abstract void progress(UIProgressListener uiProgressListener,long currentBytes, long contentLength, boolean done);
    public abstract void finish(UIProgressListener uiProgressListener,long currentBytes, long contentLength, boolean done);
}


package io.dcloud.H56580E2E.upload.listener.impl.model;

import java.io.Serializable;

/**
 * UI進度回撥實體類
 */
public class ProgressModel implements Serializable {
    //當前讀取位元組長度
    private long currentBytes;
    //總位元組長度
    private long contentLength;
    //是否讀取完成
    private boolean done;

    public ProgressModel(long currentBytes, long contentLength, boolean done) {
        this.currentBytes = currentBytes;
        this.contentLength = contentLength;
        this.done = done;
    }

    public long getCurrentBytes() {
        return currentBytes;
    }

    public void setCurrentBytes(long currentBytes) {
        this.currentBytes = currentBytes;
    }

    public long getContentLength() {
        return contentLength;
    }

    public void setContentLength(long contentLength) {
        this.contentLength = contentLength;
    }

    public boolean isDone() {
        return done;
    }

    public void setDone(boolean done) {
        this.done = done;
    }

    @Override
    public String toString() {
        return "ProgressModel{" +
                "currentBytes=" + currentBytes +
                ", contentLength=" + contentLength +
                ", done=" + done +
                '}';
    }
}

package io.dcloud.H56580E2E.upload.listener.impl;

import android.os.Handler;
import android.os.Message;

import io.dcloud.H56580E2E.upload.listener.ProgressListener;
import io.dcloud.H56580E2E.upload.listener.impl.handler.ProgressHandler;
import io.dcloud.H56580E2E.upload.listener.impl.model.ProgressModel;


/**
 * 請求體回撥實現類,用於UI層回撥,下載上傳進度類
 */
public abstract class UIProgressListener implements ProgressListener {
    private boolean isFirst = false;

    //處理UI層的Handler子類
    private static class UIHandler extends ProgressHandler {
        public UIHandler(UIProgressListener uiProgressListener) {
            super(uiProgressListener);
        }

        @Override
        public void start(UIProgressListener uiProgressListener, long currentBytes, long contentLength, boolean done) {
            if (uiProgressListener!=null) {
                uiProgressListener.onUIStart(currentBytes, contentLength, done);
            }
        }

        @Override
        public void progress(UIProgressListener uiProgressListener, long currentBytes, long contentLength, boolean done) {
            if (uiProgressListener!=null){
                uiProgressListener.onUIProgress(currentBytes, contentLength, done);
            }
        }

        @Override
        public void finish(UIProgressListener uiProgressListener, long currentBytes, long contentLength, boolean done) {
            if (uiProgressListener!=null){
                uiProgressListener.onUIFinish(currentBytes, contentLength,done);
            }
        }
    }

    //主執行緒Handler
    private final Handler mHandler = new UIHandler(this);

    @Override
    public void onProgress(long bytesWrite, long contentLength, boolean done) {
        //如果是第一次,傳送訊息
        if (!isFirst) {
            isFirst = true;
            Message start = Message.obtain();
            start.obj = new ProgressModel(bytesWrite, contentLength, done);
            start.what = ProgressHandler.START;
            mHandler.sendMessage(start);
        }

        //通過Handler傳送進度訊息
        Message message = Message.obtain();
        message.obj = new ProgressModel(bytesWrite, contentLength, done);
        message.what = ProgressHandler.UPDATE;
        mHandler.sendMessage(message);

        if(done) {
            Message finish = Message.obtain();
            finish.obj = new ProgressModel(bytesWrite, contentLength, done);
            finish.what = ProgressHandler.FINISH;
            mHandler.sendMessage(finish);
        }
    }

    /**
     * UI層回撥抽象方法
     *
     * @param currentBytes    當前的位元組長度
     * @param contentLength 總位元組長度
     * @param done          是否寫入完成
     */
    public abstract void onUIProgress(long currentBytes, long contentLength, boolean done);

    /**
     * UI層開始請求回撥方法
     * @param currentBytes 當前的位元組長度
     * @param contentLength 總位元組長度
     * @param done 是否寫入完成
     */
    public void onUIStart(long currentBytes, long contentLength, boolean done) {

    }

    /**
     * UI層結束請求回撥方法
     * @param currentBytes 當前的位元組長度
     * @param contentLength 總位元組長度
     * @param done 是否寫入完成
     */
    public void onUIFinish(long currentBytes, long contentLength, boolean done) {

    }
}

package io.dcloud.H56580E2E.upload.listener;

/**
 * 進度回撥介面,比如用於檔案上傳與下載
 */
public interface ProgressListener {
    void onProgress(long currentBytes, long contentLength, boolean done);
}
package io.dcloud.H56580E2E.upload.progress;


import io.dcloud.H56580E2E.upload.listener.ProgressListener;

import java.io.IOException;

import okhttp3.MediaType;
import okhttp3.RequestBody;
import okio.Buffer;
import okio.BufferedSink;
import okio.ForwardingSink;
import okio.Okio;
import okio.Sink;

/**
 * 包裝的請求體,處理進度
 */
public  class ProgressRequestBody extends RequestBody {
    //實際的待包裝請求體
    private final RequestBody requestBody;
    //進度回撥介面
    private final ProgressListener progressListener;
    //包裝完成的BufferedSink
    private BufferedSink bufferedSink;

    /**
     * 建構函式,賦值
     * @param requestBody 待包裝的請求體
     * @param progressListener 回撥介面
     */
    public ProgressRequestBody(RequestBody requestBody, ProgressListener progressListener) {
        this.requestBody = requestBody;
        this.progressListener = progressListener;
    }

    /**
     * 重寫呼叫實際的響應體的contentType
     * @return MediaType
     */
    @Override
    public MediaType contentType() {
        return requestBody.contentType();
    }

    /**
     * 重寫呼叫實際的響應體的contentLength
     * @return contentLength
     * @throws IOException 異常
     */
    @Override
    public long contentLength() throws IOException {
        return requestBody.contentLength();
    }

    /**
     * 重寫進行寫入
     * @param sink BufferedSink
     * @throws IOException 異常
     */
    @Override
    public void writeTo(BufferedSink sink) throws IOException {
        if (bufferedSink == null) {
            //包裝
            bufferedSink = Okio.buffer(sink(sink));
        }
        //寫入
        requestBody.writeTo(bufferedSink);
        //必須呼叫flush,否則最後一部分資料可能不會被寫入
        bufferedSink.flush();

    }

    /**
     * 寫入,回撥進度介面
     * @param sink Sink
     * @return Sink
     */
    private Sink sink(Sink sink) {
        return new ForwardingSink(sink) {

            //當前寫入位元組數
            long bytesWritten = 0L;
            //總位元組長度,避免多次呼叫contentLength()方法
            long contentLength = 0L;

            @Override
            public void write(Buffer source, long byteCount) throws IOException {
                super.write(source, byteCount);
                if (contentLength == 0) {
                    //獲得contentLength的值,後續不再呼叫
                    contentLength = contentLength();
                }
                //增加當前寫入的位元組數
                bytesWritten += byteCount;
                //回撥
                if (progressListener!=null) {
                    progressListener.onProgress(bytesWritten, contentLength, bytesWritten == contentLength);
                }
            }
        };
    }
}

package io.dcloud.H56580E2E.upload.progress;


import io.dcloud.H56580E2E.upload.listener.ProgressListener;

import java.io.IOException;

import okhttp3.MediaType;
import okhttp3.ResponseBody;
import okio.Buffer;
import okio.BufferedSource;
import okio.ForwardingSource;
import okio.Okio;
import okio.Source;


/**
 * 包裝的響體,處理進度
 */
public class ProgressResponseBody extends ResponseBody {
    //實際的待包裝響應體
    private final ResponseBody responseBody;
    //進度回撥介面
    private final ProgressListener progressListener;
    //包裝完成的BufferedSource
    private BufferedSource bufferedSource;

    /**
     * 建構函式,賦值
     * @param responseBody 待包裝的響應體
     * @param progressListener 回撥介面
     */
    public ProgressResponseBody(ResponseBody responseBody, ProgressListener progressListener) {
        this.responseBody = responseBody;
        this.progressListener = progressListener;
    }


    /**
     * 重寫呼叫實際的響應體的contentType
     * @return MediaType
     */
    @Override public MediaType contentType() {
        return responseBody.contentType();
    }

    /**
     * 重寫呼叫實際的響應體的contentLength
     * @return contentLength
     * @throws IOException 異常
     */
    @Override public long contentLength() {
        return responseBody.contentLength();
    }

    /**
     * 重寫進行包裝source
     * @return BufferedSource
     * @throws IOException 異常
     */
    @Override public BufferedSource source() {
        if (bufferedSource == null) {
            //包裝
            bufferedSource = Okio.buffer(source(responseBody.source()));
        }
        return bufferedSource;
    }

    /**
     * 讀取,回撥進度介面
     * @param source Source
     * @return Source
     */
    private Source source(Source source) {

        return new ForwardingSource(source) {
            //當前讀取位元組數
            long totalBytesRead = 0L;
            @Override public long read(Buffer sink, long byteCount) throws IOException {
                long bytesRead = super.read(sink, byteCount);
                //增加當前讀取的位元組數,如果讀取完成了bytesRead會返回-1
                totalBytesRead += bytesRead != -1 ? bytesRead : 0;
                //回撥,如果contentLength()不知道長度,會返回-1
                if (progressListener!=null) {
                    progressListener.onProgress(totalBytesRead, responseBody.contentLength(), bytesRead == -1);
                }
                return bytesRead;
            }
        };
    }
}

package io.dcloud.H56580E2E.upload.helper;

import io.dcloud.H56580E2E.upload.listener.ProgressListener;
import io.dcloud.H56580E2E.upload.progress.ProgressRequestBody;
import io.dcloud.H56580E2E.upload.progress.ProgressResponseBody;

import java.io.IOException;

import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import okhttp3.Response;


/**
 * 進度回撥輔助類
 */
public class ProgressHelper {
    /**
     * 包裝OkHttpClient,用於下載檔案的回撥
     *
     * @param client           待包裝的OkHttpClient
     * @param progressListener 進度回撥介面
     * @param storePath        下載的檔案的儲存路徑
     * @return 包裝後的OkHttpClient,使用clone方法返回
     */
    public static OkHttpClient addProgressResponseListener(OkHttpClient client, final ProgressListener progressListener, String storePath) {
        Interceptor interceptor = new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                //攔截
                Response originalResponse = chain.proceed(chain.request());
                //包裝響應體並返回
                return originalResponse.newBuilder()
                        .body(new ProgressResponseBody(originalResponse.body(), progressListener))
                        .build();
            }
        };
        return client.newBuilder()
                .addInterceptor(interceptor)
                .build();
    }

    /**
     * 包裝請求體用於上傳檔案的回撥
     *
     * @param requestBody             請求體RequestBody
     * @param progressRequestListener 進度回撥介面
     * @return 包裝後的進度回撥請求體
     */
    public static ProgressRequestBody addProgressRequestListener(RequestBody requestBody, ProgressListener progressRequestListener) {
        //包裝請求體
        return new ProgressRequestBody(requestBody, progressRequestListener);
    }
}

package io.dcloud.H56580E2E.upload.utils;


import android.app.Activity;
import android.util.Log;
import android.widget.Toast;

import io.dcloud.H56580E2E.upload.helper.ProgressHelper;
import io.dcloud.H56580E2E.upload.listener.ProgressListener;
import io.dcloud.H56580E2E.upload.listener.impl.UIProgressListener;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.FileNameMap;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.FormBody;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;

/**
 * OKHttp工具類(上傳,下載檔案)
 */

public class OKHttpUtils {

    private static OkHttpClient client;

    /**
     * 建立一個OkHttpClient的物件的單例
     *
     * @return
     */
    public synchronized static OkHttpClient getOkHttpClientInstance() {
        if (client == null) {
            OkHttpClient.Builder builder = new OkHttpClient.Builder()
                    //設定連線超時等屬性,不設定可能會報異常
                    .connectTimeout(120, TimeUnit.SECONDS)
                    .readTimeout(120, TimeUnit.SECONDS)
                    .writeTimeout(120, TimeUnit.SECONDS);

            client = builder.build();
        }
        return client;
    }


    /**
     * 獲取檔案MimeType
     *
     * @param filename 檔名
     * @return
     */
    private static String getMimeType(String filename) {
        FileNameMap filenameMap = URLConnection.getFileNameMap();
        String contentType = filenameMap.getContentTypeFor(filename);
        if (contentType == null) {
            contentType = "application/octet-stream"; //* exe,所有的可執行程式
        }
        return contentType;
    }

    /**
     * 上傳檔案
     * 獲得Request例項(不帶進度)
     *
     * @param url       上傳檔案到伺服器的地址
     * @param fileNames 完整的檔名(帶完整路徑)
     * @return
     */
    private static Request getRequest(String url, List<String> fileNames) {
        Request.Builder builder = new Request.Builder();
        builder.url(url)
                .post(getRequestBody(fileNames))
                .tag(url) //設定請求的標記,可在取消時使用
        ;
        return builder.build();
    }

    /**
     * 上傳檔案
     * 獲得Request例項(帶進度)
     *
     * @param url                       上傳檔案到伺服器的地址
     * @param fileNames                 完整的檔名(帶完整路徑)
     * @param uiProgressRequestListener 上傳進度的監聽器
     * @return
     */
    private static Request getRequest(String url, List<String> fileNames, ProgressListener uiProgressRequestListener) {
        Request.Builder builder = new Request.Builder();
        builder.url(url)
                .post(ProgressHelper.addProgressRequestListener(
                        OKHttpUtils.getRequestBody(fileNames),
                        uiProgressRequestListener));
        return builder.build();
    }

    /**
     * 通過Url地址和表單的鍵值對來建立Request例項
     *
     * @param url 上傳表單資料到伺服器的地址
     * @param map 由提交的表單的每一項組成的HashMap
     *            (如使用者名稱,key:username,value:zhangsan)
     * @return
     */
    private static Request getRequest(String url, HashMap<String, String> map) {
        Request.Builder builder = new Request.Builder();
        builder.url(url)
                .post(getRequestBody(map))
                .tag(url) //設定請求的標記,可在取消時使用
        ;
        return builder.build();
    }

    /**
     * 通過Url地址和表單的鍵值對來建立Request例項
     *
     * @param url       上傳表單資料到伺服器的地址
     * @param map       由提交的表單的每一項組成的HashMap
     *                  (如使用者名稱,key:username,value:zhangsan)
     * @param fileNames 完整的檔案路徑名
     * @return
     */
    private static Request getRequest(String url, HashMap<String, String> map, List<String> fileNames) {
        Request.Builder builder = new Request.Builder();
        builder.url(url)
                .post(getRequestBody(map, fileNames))
                .tag(url) //設定請求的標記,可在取消時使用
        ;
        return builder.build();
    }

    /**
     * 通過下載的URL地址構建equest例項
     *
     * @param downloadUrl 檔案下載的地址
     * @return
     */
    private static Request getRequest(String downloadUrl) {
        Request.Builder builder = new Request.Builder();
        builder.url(downloadUrl).tag(downloadUrl);
        return builder.build();
    }

    /**
     * 通過鍵值對(表單中的name-value)建立RequestBody
     *
     * @param map 由提交的表單的每一項組成的HashMap
     *            (如使用者名稱,key:username,value:zhangsan)
     * @return
     */
    private static RequestBody getRequestBody(HashMap<String, String> map) {
        FormBody.Builder builder = new FormBody.Builder();
        for (HashMap.Entry<String, String> entry : map.entrySet()) {
            builder.add(entry.getKey(), entry.getValue());
        }
        return builder.build();
    }

    /**
     * 根據表單的鍵值對和上傳的檔案生成RequestBody
     *
     * @param map       由提交的表單的每一項組成的HashMap
     *                  (如使用者名稱,key:username,value:zhangsan)
     * @param fileNames 完整的檔案路徑名
     * @return
     */
    private static RequestBody getRequestBody(HashMap<String, String> map, List<String> fileNames) {
        MultipartBody.Builder builder = new MultipartBody.Builder(); //建立MultipartBody.Builder,用於新增請求的資料
        for (HashMap.Entry<String, String> entry : map.entrySet()) { //對鍵值對進行遍歷
            builder.addFormDataPart(entry.getKey(), entry.getValue()); //把鍵值對新增到Builder中
        }
        for (int i = 0; i < fileNames.size(); i++) { //對檔案進行遍歷
            File file = new File(fileNames.get(i)); //生成檔案
            String fileType = getMimeType(file.getName()); //根據檔案的字尾名,獲得檔案型別
            builder.addFormDataPart( //給Builder新增上傳的檔案
                    "image",  //請求的名字
                    file.getName(), //檔案的文字,伺服器端用來解析的
                    RequestBody.create(MediaType.parse(fileType), file) //建立RequestBody,把上傳的檔案放入
            );
        }
        return builder.build(); //根據Builder建立請求
    }


    /**
     * 通過上傳的檔案的完整路徑生成RequestBody
     *
     * @param fileNames 完整的檔案路徑
     * @return
     */
    private static RequestBody getRequestBody(List<String> fileNames) {
        //建立MultipartBody.Builder,用於新增請求的資料
        MultipartBody.Builder builder = new MultipartBody.Builder();
        for (int i = 0; i < fileNames.size(); i++) { //對檔案進行遍歷
            File file = new File(fileNames.get(i)); //生成檔案
            //根據檔案的字尾名,獲得檔案型別
            String fileType = getMimeType(file.getName());
            builder.addFormDataPart( //給Builder新增上傳的檔案
                    "image",  //請求的名字
                    file.getName(), //檔案的文字,伺服器端用來解析的
                    RequestBody.create(MediaType.parse(fileType), file) //建立RequestBody,把上傳的檔案放入
            );
        }
        return builder.build(); //根據Builder建立請求
    }

    /**
     * 只上傳檔案
     * 根據url,傳送非同步Post請求(帶進度)
     *
     * @param url                       提交到伺服器的地址
     * @param fileNames                 完整的上傳的檔案的路徑名
     * @param uiProgressRequestListener 上傳進度的監聽器
     * @param callback                  OkHttp的回撥介面
     */
    public static void doPostRequest(String url, List<String> fileNames, ProgressListener uiProgressRequestListener, Callback callback) {
        Call call = getOkHttpClientInstance().newCall(getRequest(url, fileNames, uiProgressRequestListener));
        call.enqueue(callback);
    }

    /**
     * 只上傳檔案
     * 根據url,傳送非同步Post請求(不帶進度)
     *
     * @param url       提交到伺服器的地址
     * @param fileNames 完整的上傳的檔案的路徑名
     * @param callback  OkHttp的回撥介面
     */
    public static void doPostRequest(String url, List<String> fileNames, Callback callback) {
        Call call = getOkHttpClientInstance().newCall(getRequest(url, fileNames));
        call.enqueue(callback);
    }

    /**
     * 只提交表單
     * 根據url和鍵值對,傳送非同步Post請求
     *
     * @param url      提交到伺服器的地址
     * @param map      提交的表單的每一項組成的HashMap
     *                 (如使用者名稱,key:username,value:zhangsan)
     * @param callback OkHttp的回撥介面
     */
    public static void doPostRequest(String url, HashMap<String, String> map, Callback callback) {
        Call call = getOkHttpClientInstance().newCall(getRequest(url, map));
        call.enqueue(callback);
    }


    /**
     * 可同時提交表單,和多檔案
     * 根據url和鍵值對,傳送非同步Post請求
     *
     * @param url       提交到伺服器的地址
     * @param map       提交的表單的每一項組成的HashMap
     *                  (如使用者名稱,key:username,value:zhangsan)
     * @param fileNames 完整的上傳的檔案的路徑名
     * @param callback  OkHttp的回撥介面
     */
    public static void doPostRequest(String url, HashMap<String, String> map, List<String> fileNames, Callback callback) {
        Call call = getOkHttpClientInstance().newCall(getRequest(url, map, fileNames));
        call.enqueue(callback);
    }


    /**
     * 檔案下載(帶進度)
     *
     * @param downloadUrl                檔案的下載地址
     * @param savePath                   下載後的檔案的儲存路徑
     * @param uiProgressResponseListener 下載進度的監聽器
     */
    public static void downloadAndSaveFile(final Activity activity, String downloadUrl, final String savePath, UIProgressListener uiProgressResponseListener) {
        //包裝Response使其支援進度回撥
        ProgressHelper.addProgressResponseListener(OKHttpUtils.getOkHttpClientInstance(), uiProgressResponseListener, savePath)
                .newCall(getRequest(downloadUrl))
                .enqueue(new Callback() {
                    @Override
                    public void onFailure(Call call, final IOException e) {
                        Log.i("TAG", "下載錯誤: " + e.getMessage());
                        activity.runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                Toast.makeText(activity, "下載錯誤"+e.getMessage(), Toast.LENGTH_SHORT).show();
                            }
                        });
                    }

                    @Override
                    public void onResponse(Call call, Response response) throws IOException {
                        Log.i("TAG", "伺服器響應成功");
                          //在本地儲存檔案
                        OKHttpUtils.saveDownloadFile(response, savePath);
                    }
                });
    }

    //在本地儲存下載的檔案
    private static void saveDownloadFile(Response response, String savePath) throws IOException {
        InputStream inputStream = getInputStreamFromResponse(response);
        BufferedInputStream bis = new BufferedInputStream(inputStream);
        FileOutputStream fos = new FileOutputStream(savePath);
        byte[] data = new byte[10 * 1024];
        int len;
        while ((len = bis.read(data)) != -1) {
            fos.write(data, 0, len);
        }
        Log.i("TAG", "儲存檔案"+savePath+"成功");
        fos.flush();
        fos.close();
        bis.close();
    }

    //獲取字串
    public static String getString(Response response) throws IOException {
        if (response != null && response.isSuccessful()) {
            return response.body().string();
        }
        return null;
    }


    /**
     * 根據響應獲得位元組陣列
     *
     * @param response
     * @return
     * @throws IOException
     */
    public static byte[] getBytesFromResponse(Response response) throws IOException {
        if (response != null && response.isSuccessful()) {
            ResponseBody responseBody = response.body();
            if (responseBody != null) {
                return responseBody.bytes();
            }
        }
        return null;
    }


    /**
     * 根據響應獲得輸入流
     *
     * @param response
     * @return
     * @throws IOException
     */
    public static InputStream getInputStreamFromResponse(Response response) throws IOException {
        if (response != null && response.isSuccessful()) {
            ResponseBody responseBody = response.body();
            if (responseBody != null) {
                return responseBody.byteStream();
            }
        }
        return null;
    }


    /**
     * 取消所有為tag的Call
     *
     * @param tag 請求的標記
     */
    public static void cancelCallsWithTag(Object tag) {

        if (tag == null) {
            return;
        }

        synchronized (client.dispatcher().getClass()) {
            for (Call call : client.dispatcher().queuedCalls()) {
                if (tag.equals(call.request().tag())) call.cancel();
            }

            for (Call call : client.dispatcher().runningCalls()) {
                if (tag.equals(call.request().tag())) call.cancel();
            }
        }
    }
}