在 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();
}
}
}
}