前言
為何自己動(dòng)手?jǐn)]了一個(gè)呢,不使用流行的Retrofit+RxJava + OkHttp 方式。首先是因?yàn)閷?duì)RxJava不了解,在平常項(xiàng)目中沒有使用過,其次就為了一個(gè)網(wǎng)絡(luò)框架引入RxJava RxAndroid 等,得不嘗試,增加apk體積。以上觀點(diǎn)僅代表個(gè)人觀點(diǎn),不喜勿噴。
介紹
LSHttp庫(kù),就僅僅是對(duì)OkHttp進(jìn)行了一些封裝,采用鏈?zhǔn)秸{(diào)用的方法,一行代碼實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求,支持綁定當(dāng)前Activity、Fragment,在銷毀時(shí)自動(dòng)取消請(qǐng)求,讓開發(fā)者使用起來(lái)更方便。
使用
compile 'com.lishang.http:LSHttp:1.0.1'
支持以下操作
- get請(qǐng)求
- post表單、json
- 上傳(單文件或多文件,支持進(jìn)度監(jiān)聽)
- 下載(斷點(diǎn)下載)
- 頁(yè)面銷毀時(shí)自動(dòng)取消請(qǐng)求
- 自定義轉(zhuǎn)換器,默認(rèn)提供了StringCallBack,JsonCallBack
看到這里有些小伙伴就要說了,put、delete、head、options、trace、patch 這些請(qǐng)求為何不支持呢?這里分實(shí)話假話,實(shí)話就是水平有限我不會(huì),工作了幾年了沒有用過。假話就是,在實(shí)際開發(fā)中Get/Post兩種請(qǐng)求就可以滿足99%的場(chǎng)景了。當(dāng)然如果你的業(yè)務(wù)需求要用到的話,也可以繼承BaseRequest,實(shí)現(xiàn)generateRequest方法就可以了。
各位大佬注意了,接下來(lái)就要開始介紹使用方法了
配置 在Application里面:
LSHttp.init(this); //初始化,使用默認(rèn)配置
//配置自己的OkHttpClient
OkHttpClient mClient = new OkHttpClient.Builder()
.writeTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.connectTimeout(15, TimeUnit.SECONDS)
.addInterceptor(new LSHttpLoggingInterceptor().setLevel(LSHttpLoggingInterceptor.Level.BODY))
//其它配置
.build();
LSHttp.init(this, mClient)
.showLog(true) //是否顯示日志
.addHeader("key", "value") //全局添加header
.addHeaders(map) //添加多個(gè)headers
.baseUrl("https://wanandroid.com/"); //baseurl
baseUrl說明:
如果配置了baseUrl,在request時(shí)可以直接傳入路徑,例如:user/login,如果某次請(qǐng)求的baseUrl不是當(dāng)前配置的,直接傳入完整的url就可以了,例如:https://www.33lc.com/article/UploadPic/2012-7/201272510182494484.jpg
如何做到Activity、Fragment銷毀,請(qǐng)求自動(dòng)取消
LSHttp.xxx(url).
execute(this); //this表示Activity或者Fragment
LSHttp.xxx(url).
execute(); //不傳 Activity或者Fragment,在頁(yè)面銷毀時(shí)請(qǐng)求不會(huì)取消
CallBack介紹,打交道最多的,最終請(qǐng)求數(shù)據(jù)都會(huì)回到CallBack,默認(rèn)提供了兩種CallBack:
StringCallBack 請(qǐng)求返回字符串
LSHttp.xxx(url).
.callback(new StringCallBack(){
@Override
public void onSuccess(String string) {
Log.e("onSuccess", string);
}
@Override
public void onFail(LSHttpException e) {
}
});
JsonCallBack 返回實(shí)體類 內(nèi)部使用了Gson
LSHttp.xxx(url).
.callback(new JsonCallBack<JsonData>(){
@Override
public void onSuccess(JsonData data) {
Log.e("onSuccess", data.toString());
}
@Override
public void onFail(LSHttpException e) {
}
});
如果StringCallBack和JsonCallBack不能滿足你的使用,還可以自定義轉(zhuǎn)換器,需要三步:
1.定義接口,繼承ResponseCallBack
public interface BitmapCallBack extends ResponseCallBack {
void onSuccess(Bitmap bitmap);
}
2.創(chuàng)建Class,實(shí)現(xiàn)接口IConvertResponse
public class BitmapConvertResponse implements IConvertResponse {
BitmapCallBack callBack;
@Override
public void convert(Response response) {
InputStream inputStream = response.body().byteStream();//得到圖片的流
final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
//子線程中
LSHttp.getInstance().runOnMainThread(new Runnable() {
@Override
public void run() {
if (callBack != null) {
callBack.onSuccess(bitmap);
}
}
});
}
@Override
public void setCallBack(ResponseCallBack callBack) {
this.callBack = (BitmapCallBack) callBack;
}
}
3.請(qǐng)求
LSHttp.get("https://www.33lc.com/article/UploadPic/2012-7/201272510182494484.jpg")
.convert(new BitmapConvertResponse())
.callback(new BitmapCallBack() {
@Override
public void onSuccess(Bitmap bitmap) {
if (bitmap != null) {
mImage.setImageBitmap(bitmap);
}
}
@Override
public void onFail(LSHttpException e) {
}
}).execute(this);
4.如何知道,請(qǐng)求是onSuccess或onFail
@Override
public void onResponse(Call call, final Response response) throws IOException {
if (response.isSuccessful()) {
//請(qǐng)求成功
onConvertCallBack(response);
} else {
runOnMainThread(new Runnable() {
@Override
public void run() {
if (callBack != null) {
callBack.onFail(new LSHttpException(LSHttpException.ERROR.HTTP_ERROR, "請(qǐng)求失敗,服務(wù)器開小差...", response.code()));
}
}
});
}
removeLifecycle(obj, call);
}
response.isSuccessful() 判斷服務(wù)端返回的status是否在200~300,如果是,進(jìn)行onConvertCallBack,將結(jié)果轉(zhuǎn)化成對(duì)應(yīng)的數(shù)據(jù)。
如果status沒有在200~300之間,就進(jìn)行onFail處理,將status拋給上層。
-
onConvertCallBack方法,進(jìn)行區(qū)分使用自定義的Convert還是默認(rèn)的JSONCallBack/StringCallBack
public void onConvertCallBack(final Response response) { //用戶是否需要自定義Convert if (convertResponse != null) { convertResponse.setCallBack(callBack); convertResponse.convert(response); } else if (callBack != null) { if (callBack instanceof JsonCallBack) { JsonConvertResponse convertResponse = new JsonConvertResponse(); convertResponse.setCallBack(callBack); convertResponse.convert(response); } else { StringConvertResponse convertResponse = new StringConvertResponse(); convertResponse.setCallBack(callBack); convertResponse.convert(response); } } }
如何發(fā)起一個(gè)請(qǐng)求呢?
LSHttp.xxx(url) //請(qǐng)求方法,get/post/json/download/multipart
.xxx()//不同請(qǐng)求支持的方法
.addHeader(key,value)//添加header
.addHeaders(map)//添加多個(gè)header
.tag(tag)//指定tag,用于取消
.convert(IConvertResponse)//轉(zhuǎn)換器,使用方法上面有介紹
.callback(ResponseCallBack)//請(qǐng)求回調(diào)
.execute(null/Activity/Fragment);//開始執(zhí)行,傳入Activity和Fragment,就會(huì)在頁(yè)面銷毀時(shí)自動(dòng)取消請(qǐng)求
Get請(qǐng)求
LSHttp.get("wxarticle/chapters/json")
.callback(new JsonCallBack<JsonData>() {
@Override
public void onFail(LSHttpException e) {
mText.setText("請(qǐng)求失敗:\n" + e.message);
}
@Override
public void onSuccess(JsonData data) {
Log.e("onSuccess", data.toString());
mText.setText("請(qǐng)求成功:\n" + data.toString());
}
}).execute();
Post請(qǐng)求
//表單請(qǐng)求 application/x-www-form-urlencoded
LSHttp.post("user/login")
.addParams("username", "xxxx") //請(qǐng)求參數(shù)
.addParams("password", "xxxxx")
.callback(new StringCallBack() {
@Override
public void onSuccess(String string) {
Log.e("onSuccess", string);
mText.setText("請(qǐng)求成功:\n" + string);
}
@Override
public void onFail(LSHttpException e) {
mText.setText("請(qǐng)求失敗:\n" + e.message);
}
}).execute();
//json請(qǐng)求 json application/json; charset=utf-8
LSHttp.json("https://www.wanandroid.com/user/login")
.setJson(json) //json字符串
.callback(new StringCallBack() {
@Override
public void onSuccess(String string) {
Log.e("onSuccess", string);
mText.setText("請(qǐng)求成功:\n" + string);
}
@Override
public void onFail(LSHttpException e) {
mText.setText("請(qǐng)求失敗:\n" + e.message);
}
}).execute();
下載
String url = "https://6082fcfd683b09af1a65ea78561240f8.dd.cdntips.com/download.sj.qq.com/upload/connAssitantDownload/upload/MobileAssistant_1.apk";
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/abcd/apk.apk";
LSHttp.download(url).path(path).tag("down").callback(new DownloadCallBack() {
@Override
public void onStart() {
Log.e("onStart", "開始下載");
mTxtResult.setText("開始下載...");
}
@Override
public void onLoading(int progress) {
Log.e("onLoading", "下載中" + progress);
mProgressHorizontal.setProgress(progress);
mTxtProgress.setText("" + progress + "%");
}
@Override
public void onSuccess(String path) {
Log.e("onSuccess", "下載成功" + path);
mTxtResult.setText("下載成功" + path);
}
@Override
public void onFail(LSHttpException e) {
mTxtResult.setText("請(qǐng)求失敗:\n" + e.message);
}
}).execute(DownloadActivity.this);
//取消下載
LSHttp.cancel("down");
上傳
//替換成自己的
String url = "https://www.wanandroid.com/image/upload";
String path = Environment.getExternalStorageDirectory().getAbsolutePath();
LSHttp.multipart(url).addFile("image_1", new File(path + "/123.jpg"))
.addFile("image_2", new File(path + "/456.jpg"))
.addHeader("Authorization", "3c4dfe6f7a7caaa7ccca39ec157554f5")
.progress(new UploadCallBack() {
@Override
public void onLoading(int progress) {
mProgressHorizontal.setProgress(progress);
mTxtProgress.setText("" + progress + "%");
}
})
.callback(new StringCallBack() {
@Override
public void onSuccess(String string) {
Log.e("onSuccess", string);
mTxtResult.setText("請(qǐng)求成功:\n" + string);
}
@Override
public void onFail(LSHttpException e) {
mTxtResult.setText("請(qǐng)求失敗:\n" + e.message);
}
}).execute(UploadFragment.this);
上傳下載需要讀寫權(quán)限,推薦 LSPermissions
如何擴(kuò)展請(qǐng)求,例如put請(qǐng)求
1.繼承BaseRequest
public class PutRequest extends BaseRequest <PutRequest>{
@Override
public Request generateRequest(Request.Builder builder) {
return builder.put(requestbody).build();
}
}
2.執(zhí)行
new PutRequest().url()
.xxx()
.xxx()
.execute();
以上使用方法已經(jīng)介紹完畢,接下來(lái)開始看下源碼
源碼
BaseRequest
定義了請(qǐng)求需要的公用參數(shù),以及請(qǐng)求邏輯,綁定Acitivy、Fragment生命周期
public abstract class BaseRequest<T extends BaseRequest> {
protected OkHttpClient mClient;
protected LSHttpActivityLifecycleCallBacks lifecycleCallBacks;
protected String url; //請(qǐng)求url
protected Map<String, String> headers = new HashMap<>(); //請(qǐng)求頭參數(shù)
protected String tag;//請(qǐng)求tag
protected ResponseCallBack callBack;//請(qǐng)求回調(diào)
protected IConvertResponse convertResponse; //用于自定義轉(zhuǎn)換器
public BaseRequest() {
mClient = LSHttp.getInstance().getClient();
lifecycleCallBacks = LSHttp.getInstance().getLifecycleCallBacks();
}
public ResponseCallBack getCallBack() {
return callBack;
}
//添加請(qǐng)求header
public T addHeader(String key, String value) {
headers.put(key, value);
return (T) this;
}
//添加多個(gè)header
public T addHeaders(Map<String, String> map) {
if (map != null) {
headers.putAll(map);
}
return (T) this;
}
//url
public T url(String url) {
this.url = url;
return (T) this;
}
//tag
public T tag(String tag) {
this.tag = tag;
return (T) this;
}
//請(qǐng)求結(jié)果返回
public T callback(ResponseCallBack callBack) {
this.callBack = callBack;
return (T) this;
}
//轉(zhuǎn)換器
public T convert(IConvertResponse convertResponse) {
this.convertResponse = convertResponse;
return (T) this;
}
//創(chuàng)建headers
public Headers createHeaders() {
Headers.Builder builder = new Headers.Builder();
for (String key : headers.keySet()) {
builder.add(key, headers.get(key));
}
return builder.build();
}
//構(gòu)建基礎(chǔ)request.builder
public Request.Builder request() {
Request.Builder builder = new Request.Builder()
.url(url)
.tag(tag)
.headers(createHeaders());
return builder;
}
//用于定制request 差異
public abstract Request generateRequest(Request.Builder builder);
//異步執(zhí)行網(wǎng)絡(luò)請(qǐng)求
public void execute() {
execute(null);
}
//異步執(zhí)行網(wǎng)絡(luò)請(qǐng)求 obj 支持Activity、Fragment
public void execute(final Object obj) {
checkUrl();
OkHttpClient client = mClient;
Call call = client.newCall(generateRequest(request()));
//與當(dāng)前Acitivy、Fragment綁定關(guān)系
bindLifecycle(obj, call);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, final IOException e) {
e.printStackTrace();
if (call.isCanceled()) {
System.out.println("call is canceled");
return;
}
runOnMainThread(new Runnable() {
@Override
public void run() {
if (callBack != null) {
callBack.onFail(LSHttpException.handleException(e));
}
}
});
//請(qǐng)求結(jié)束,移除綁定關(guān)系
removeLifecycle(obj, call);
}
@Override
public void onResponse(Call call, final Response response) throws IOException {
if (response.code() == 200) {
//請(qǐng)求成功
if (convertResponse != null) {
convertResponse.setCallBack(callBack);
convertResponse.convert(response);
} else if (callBack != null) {
if (callBack instanceof JsonCallBack) {
JsonConvertResponse convertResponse = new JsonConvertResponse();
convertResponse.setCallBack(callBack);
convertResponse.convert(response);
} else {
StringConvertResponse convertResponse = new StringConvertResponse();
convertResponse.setCallBack(callBack);
convertResponse.convert(response);
}
}
} else {
runOnMainThread(new Runnable() {
@Override
public void run() {
if (callBack != null) {
callBack.onFail(new LSHttpException(LSHttpException.ERROR.HTTP_ERROR, "請(qǐng)求失敗,服務(wù)器開小差..." + response.code()));
}
}
});
}
//請(qǐng)求結(jié)束,移除綁定關(guān)系
removeLifecycle(obj, call);
}
});
}
//檢查url是否合法
public void checkUrl() {
if (TextUtils.isEmpty(this.url)) {
throw new NullPointerException("url is null");
}
String baseUrl = LSHttp.getInstance().getBaseUrl();
if (!TextUtils.isEmpty(baseUrl)) {
Uri uri = Uri.parse(url);
if ("http".equalsIgnoreCase(uri.getScheme()) || "https".equalsIgnoreCase(uri.getScheme())) {
} else {
url = baseUrl + url;
}
}
}
//運(yùn)行到主線程
public void runOnMainThread(Runnable runnable) {
LSHttp.getInstance().runOnMainThread(runnable);
}
//綁定Activity、Fragment
public void bindLifecycle(Object obj, Call call) {
if (lifecycleCallBacks != null && obj != null) {
if (obj instanceof Activity || obj instanceof Fragment)
lifecycleCallBacks.put(obj.getClass().getName(), call);
}
}
//移除綁定
public void removeLifecycle(Object obj, Call call) {
if (lifecycleCallBacks != null && obj != null) {
lifecycleCallBacks.remove(obj.getClass().getName(), call);
}
}
public OkHttpClient getClient() {
return mClient;
}
public LSHttpActivityLifecycleCallBacks getLifecycleCallBacks() {
return lifecycleCallBacks;
}
public String getUrl() {
return url;
}
public Map<String, String> getHeaders() {
return headers;
}
public String getTag() {
return tag;
}
public IConvertResponse getConvertResponse() {
return convertResponse;
}
}
IConvertResponse
請(qǐng)求結(jié)果轉(zhuǎn)換接口,可以根據(jù)自己需求,實(shí)現(xiàn)該接口,進(jìn)行定制化
public interface IConvertResponse {
/**
* 轉(zhuǎn)換
* @param response
*/
void convert(Response response);
/**
* 回調(diào)
* @param callBack
*/
void setCallBack(ResponseCallBack callBack);
}
ResponseCallBack
和上面的 IConvertResponse 配合使用,只定義了失敗方法,成功方法根據(jù)轉(zhuǎn)換自己定義對(duì)應(yīng)的Success
public interface ResponseCallBack {
void onFail(LSHttpException e);
}
LSHttpException,定義了些常見的錯(cuò)誤處理,在三種情況下會(huì)收到
1-請(qǐng)求失敗在如,SockteTimeOutException,等IO異常時(shí),即OkHttp返回onFailure。
2-請(qǐng)求成功,但是服務(wù)端返回http status 不在200~300之間,如404,500
3-請(qǐng)求成功,并且http status在200~300之間,但是convert時(shí)出錯(cuò)。
public class LSHttpException extends Exception {
/**
* 約定異常
*/
public static class ERROR {
/**
* 未知錯(cuò)誤
*/
public static final int UNKNOWN = 1000;
/**
* 連接超時(shí)
*/
public static final int TIMEOUT_ERROR = 1001;
/**
* 空指針錯(cuò)誤
*/
public static final int NULL_POINTER_EXCEPTION = 1002;
/**
* 證書出錯(cuò)
*/
public static final int SSL_ERROR = 1003;
/**
* 類轉(zhuǎn)換錯(cuò)誤
*/
public static final int CAST_ERROR = 1004;
/**
* 解析錯(cuò)誤
*/
public static final int PARSE_ERROR = 1005;
/**
* 非法數(shù)據(jù)異常
*/
public static final int ILLEGAL_STATE_ERROR = 1006;
/**
* 服務(wù)端異常 http code >= 200 && <300
*/
public static final int HTTP_ERROR = 1007;
}
public final int code; //錯(cuò)誤Code
public String message; //描述
public int status; //當(dāng)code == 1007 時(shí) http status;
public LSHttpException(Throwable throwable, int code) {
this(throwable, code, -1);
}
public LSHttpException(Throwable throwable, int code, int status) {
super(throwable);
this.code = code;
this.message = throwable.getMessage();
this.status = status;
}
public LSHttpException(int code, String message) {
this(code, message, -1);
}
public LSHttpException(int code, String message, int status) {
this.code = code;
this.message = message;
this.status = status;
}
public static LSHttpException handleException(Throwable e) {
LSHttpException ex;
if (e instanceof SocketTimeoutException) {
ex = new LSHttpException(e, ERROR.TIMEOUT_ERROR);
ex.message = "網(wǎng)絡(luò)不可用,請(qǐng)稍后再試";
return ex;
} else if (e instanceof ConnectException) {
ex = new LSHttpException(e, ERROR.TIMEOUT_ERROR);
ex.message = "網(wǎng)絡(luò)不可用,請(qǐng)稍后再試";
return ex;
} else if (e instanceof ConnectTimeoutException) {
ex = new LSHttpException(e, ERROR.TIMEOUT_ERROR);
ex.message = "網(wǎng)絡(luò)不可用,請(qǐng)稍后再試";
return ex;
} else if (e instanceof UnknownHostException) {
ex = new LSHttpException(e, ERROR.TIMEOUT_ERROR);
ex.message = "網(wǎng)絡(luò)不可用,請(qǐng)稍后再試";
return ex;
} else if (e instanceof NullPointerException) {
ex = new LSHttpException(e, ERROR.NULL_POINTER_EXCEPTION);
ex.message = "空指針異常";
return ex;
} else if (e instanceof javax.net.ssl.SSLHandshakeException) {
ex = new LSHttpException(e, ERROR.SSL_ERROR);
ex.message = "證書驗(yàn)證失敗";
return ex;
} else if (e instanceof ClassCastException) {
ex = new LSHttpException(e, ERROR.CAST_ERROR);
ex.message = "類型轉(zhuǎn)換錯(cuò)誤";
return ex;
} else if (e instanceof JsonParseException
|| e instanceof JSONException
|| e instanceof JsonSerializer
|| e instanceof NotSerializableException
|| e instanceof ParseException) {
ex = new LSHttpException(e, ERROR.PARSE_ERROR);
ex.message = "解析錯(cuò)誤";
return ex;
} else if (e instanceof IllegalStateException) {
ex = new LSHttpException(e, ERROR.ILLEGAL_STATE_ERROR);
ex.message = e.getMessage();
return ex;
} else {
ex = new LSHttpException(e, ERROR.UNKNOWN);
ex.message = "未知錯(cuò)誤";
return ex;
}
}
}
請(qǐng)求如何綁定Activity、Fragment
這里主要使用了 ActivityLifecycleCallbacks 和 FragmentManager.FragmentLifecycleCallbacks ,監(jiān)聽頁(yè)面銷毀時(shí),將請(qǐng)求取消
LSHttpLifecycleCallBacks
/**
* 綁定生命周期基類、對(duì)外提供put remove
*/
public class LSHttpLifecycleCallBacks {
private ConcurrentMap<String, SparseArray<Call>> map = new ConcurrentHashMap<>();
public void put(String key, Call call) {
SparseArray<Call> calls = map.get(key);
if (calls == null) {
calls = new SparseArray<>();
}
calls.put(call.hashCode(), call);
map.put(key, calls);
LSLog.i("LifecycleCallBacks class:" + key + "call bind success");
}
public void remove(String key, Call call) {
SparseArray<Call> calls = map.get(key);
if (call != null) {
calls.delete(call.hashCode());
LSLog.i("LifecycleCallBacks class:" + key + " call " + call.request().url().toString() + " remove success");
}
}
public void destroyed(String key) {
SparseArray<Call> calls = map.get(key);
if (calls != null) {
for (int i = 0; i < calls.size(); i++) {
Call call = calls.valueAt(i);
if (call != null && !call.isCanceled()) {
call.cancel();
LSLog.i("LifecycleCallBacks class:" + key + " destroyed call " + call.request().url().toString() + " cancel success");
}
}
calls.clear();
map.remove(key);
LSLog.i("LifecycleCallBacks class:" + key + " destroyed call remove success");
}
}
}
LSHttpActivityLifecycleCallBacks
/**
* request 綁定生命周期 Activity銷毀時(shí)自動(dòng)取消
*/
public class LSHttpActivityLifecycleCallBacks extends LSHttpLifecycleCallBacks implements Application.ActivityLifecycleCallbacks {
private LSHttpFragmentLifecycleCallBacks fragmentLifecycleCallBacks;
public LSHttpActivityLifecycleCallBacks() {
this.fragmentLifecycleCallBacks = new LSHttpFragmentLifecycleCallBacks(this);
}
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
if (activity instanceof FragmentActivity) {
FragmentManager fm = ((FragmentActivity) activity).getSupportFragmentManager();
fm.registerFragmentLifecycleCallbacks(fragmentLifecycleCallBacks, true);
}
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
}
@Override
public void onActivityDestroyed(Activity activity) {
String key = activity.getClass().getName();
destroyed(key);
}
}
LSHttpFragmentLifecycleCallBacks
/**
* request 綁定Fragment 生命周期
*/
public class LSHttpFragmentLifecycleCallBacks extends FragmentManager.FragmentLifecycleCallbacks {
private LSHttpLifecycleCallBacks callBacks;
public LSHttpFragmentLifecycleCallBacks(LSHttpLifecycleCallBacks callBacks) {
this.callBacks = callBacks;
}
@Override
public void onFragmentDestroyed(FragmentManager fm, Fragment f) {
super.onFragmentDestroyed(fm, f);
String key = f.getClass().getName();
callBacks.destroyed(key);
fm.unregisterFragmentLifecycleCallbacks(this);
}
}
下載
支持進(jìn)度監(jiān)聽,通過查資料,一般有兩種實(shí)現(xiàn)方式進(jìn)行進(jìn)度監(jiān)聽,一種是在請(qǐng)求成功后,在寫入file時(shí),監(jiān)聽的本地文件寫入進(jìn)度,還有一種就是通過網(wǎng)絡(luò)攔截器包裝ResponseBody,這種是監(jiān)聽網(wǎng)絡(luò)下載進(jìn)度,下載完畢后再寫入file。但是我在寫的時(shí)候發(fā)現(xiàn)了一個(gè)問題,那就是進(jìn)度卡在100%,然后等f(wàn)ile寫入完畢后,才能拿到文件。我在方案二的基礎(chǔ)上進(jìn)行了改造,實(shí)現(xiàn)了邊下邊存,當(dāng)下載完畢時(shí)文件也保存完畢了。
1.定義接口
interface ProgressListener {
//下載進(jìn)度監(jiān)聽
void update(long bytesRead, long contentLength, boolean done);
//寫入文件
void writeTo(byte[] bytes, int off, int len);
}
2.繼承ResponseBody
private static class ProgressResponseBody extends ResponseBody {
private final ResponseBody responseBody;
private final ProgressListener progressListener;
private BufferedSource bufferedSource;
ProgressResponseBody(ResponseBody responseBody, ProgressListener progressListener) {
this.responseBody = responseBody;
this.progressListener = progressListener;
}
@Override
public MediaType contentType() {
return responseBody.contentType();
}
@Override
public long contentLength() {
return responseBody.contentLength();
}
@Override
public BufferedSource source() {
if (bufferedSource == null) {
bufferedSource = Okio.buffer(source(responseBody.source()));
}
return bufferedSource;
}
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);
// read() returns the number of bytes read, or -1 if this source is exhausted.
boolean done = bytesRead == -1;
if (!done) {
byte[] bytes = sink.readByteArray();
progressListener.writeTo(bytes, 0, bytes.length);
}
totalBytesRead += bytesRead != -1 ? bytesRead : 0;
LSLog.i("read size:" + totalBytesRead);
progressListener.update(totalBytesRead, responseBody.contentLength(), done);
return bytesRead;
}
};
}
}
3.添加攔截器
this.mClient = builder
.addNetworkInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
if (originalResponse.isSuccessful()) {
return originalResponse.newBuilder()
.body(new ProgressResponseBody(originalResponse.body(), listener))
.build();
} else {
return originalResponse;
}
}
})
.build();
關(guān)鍵代碼,在第二步
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);
// read() returns the number of bytes read, or -1 if this source is exhausted.
boolean done = bytesRead == -1;
if (!done) {
byte[] bytes = sink.readByteArray();
progressListener.writeTo(bytes, 0, bytes.length);
}
totalBytesRead += bytesRead != -1 ? bytesRead : 0;
LSLog.i("read size:" + totalBytesRead);
progressListener.update(totalBytesRead, responseBody.contentLength(), done);
return bytesRead;
}
};
}
如果沒有下載完,不斷的將讀取的字節(jié)寫入到文件
if (!done) {
byte[] bytes = sink.readByteArray();
progressListener.writeTo(bytes, 0, bytes.length);
}
通過RandomAccessFile 不斷的將字節(jié)寫入到文件
private class DownloadFile {
RandomAccessFile randomAccessFile;
File file;
Call call;
public DownloadFile(String path, String url) throws FileNotFoundException {
...
}
public void setCall(Call call) {
this.call = call;
}
public void seek(long pos) {
try {
randomAccessFile.seek(pos);
} catch (IOException e) {
e.printStackTrace();
}
}
public void write(byte[] bytes, int off, int len) throws IOException {
randomAccessFile.write(bytes, off, len);
}
public void close() {
try {
randomAccessFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public long size() {
if (file != null) {
return file.length();
} else {
return 0;
}
}
public void cancel() {
call.cancel();
}
}
監(jiān)聽
final ProgressListener listener = new ProgressListener() {
boolean firstUpdate = true;
@Override
public void update(long bytesRead, long contentLength, boolean done) {
if (done) {
System.out.println("completed");
success(downloadFile.file.getAbsolutePath());
downloadFile.close();
} else {
if (firstUpdate) {
firstUpdate = false;
if (contentLength == -1) {
System.out.println("content-length: unknown");
} else {
System.out.format("content-length: %d\n", contentLength);
}
start();//開始下載
}
if (contentLength != -1) {
int progress = (int) ((100 * (bytesRead + startPos)) / (contentLength + startPos));
System.out.format("%d%% done\n", progress);
loading(progress);
}
}
}
@Override
public void writeTo(byte[] bytes, int off, int len) {
try {
downloadFile.write(bytes, off, len);
} catch (IOException e) {
e.printStackTrace();
downloadFile.cancel();
error(new LSHttpException(LSHttpException.ERROR.HTTP_ERROR, "文件下載失敗"));
}
}
};
使用到的庫(kù)
com.squareup.okhttp3:okhttp:3.12.3
com.google.code.gson:gson:2.8.2
https內(nèi)容借鑒
本次介紹到此為止,謝謝各位大佬的觀看,有寫的不對(duì)的地方,有寫的不對(duì)的地方希望各位大佬指正。如果覺得寫的可以的話,去GitHub給個(gè)星唄。
更新日志
-
2019.08.05 新增http stauts失敗時(shí)對(duì)應(yīng)status返回