前言:封裝只是加深本身的理解,網上已經有很優秀的封裝,我也是借鑑了okgo和鴻洋的okhttputils。本項目是基於mvc模式下,但這篇只講如何對okhttp進行封裝(這裏我按最基礎步驟來,須要額外功能,看源碼和本文理解,確定能夠實現)java
咱們封裝要有的功能有:android
get請求 | post請求 | 上傳文件 |
---|---|---|
![]() |
![]() |
![]() |
下載文件 | ||
![]() |
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.build();
Request.Builder mBuilder = new Request.Builder();
mBuilder.url("url?parm1=x&parm2=y");
mBuilder.header("head","headValue");
Request okHttpRequest = mBuilder.build();
okHttpClient.newCall(okHttpRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
複製代碼
get請求參數是拼在url後面的,並且上面是異步請求enqueue,這個方法是在子線程裏的。回調onFailure和onResponse也都在子線程,咱們能夠把解析等耗時操做放在這裏,可是這裏不能直接更改UI,要把他切換到主線程裏。git
//tag取消網絡請求
public void cancleOkhttpTag(String tag) {
Dispatcher dispatcher = okHttpClient.dispatcher();
synchronized (dispatcher) {
//請求列表裏的,取消網絡請求
for (Call call : dispatcher.queuedCalls()) {
if (tag.equals(call.request().tag())) {
call.cancel();
}
}
//正在請求網絡的,取消網絡請求
for (Call call : dispatcher.runningCalls()) {
if (tag.equals(call.request().tag())) {
call.cancel();
}
}
}
}
複製代碼
能夠看到,取消代碼請求要用的okHttpClient,因此咱們要保持okHttpClient的惟一性,EasyOk這裏就要用到單例了github
public class EasyOk {
private static EasyOk okHttpUtils;
private OkHttpClient okHttpClient;
//這個handler的做用是把子線程切換主線程。在後面接口中的具體實現,就不須要用handler去回調了
private Handler mDelivery;
private EasyOk() {
mDelivery = new Handler(Looper.getMainLooper());
okHttpClient = new OkHttpClient.Builder()
.hostnameVerifier(new HostnameVerifier() {//證書信任
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
})
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.build();
}
public static EasyOk getInstance() {
if (okHttpUtils == null) {
okHttpUtils = new EasyOk();
}
return okHttpUtils;
}
public OkHttpClient getOkHttpClient() {
return okHttpClient;
}
public Handler getmDelivery() {
return mDelivery;
}
//tag取消網絡請求
public void cancleOkhttpTag(String tag) {
Dispatcher dispatcher = okHttpClient.dispatcher();
synchronized (dispatcher) {
//請求列表裏的,取消網絡請求
for (Call call : dispatcher.queuedCalls()) {
if (tag.equals(call.request().tag())) {
call.cancel();
}
}
//正在請求網絡的,取消網絡請求
for (Call call : dispatcher.runningCalls()) {
if (tag.equals(call.request().tag())) {
call.cancel();
}
}
}
}
}
複製代碼
mDelivery是將子線程切換到主線程的handler,這裏借鑑了鴻洋大神的思路。由於咱們最好把重複性的工做所有都放在封裝裏。封裝最重要的優勢不就是爲了方便嗎。json
根據最原始的okhttp進行的get請求,咱們還缺Request,還有一個網絡請求的回調,那麼請看下面。api
由於每次請求,Request 請求體都是須要new的,因此可想而知這裏不多是單例,並且每次調用請求都是new出來的Request。根據最原始的get請求,咱們知道OkGetBuilder裏須要一、url,二、參數,三、header,四、tag,五、還有本身的網絡回調。緩存
因此咱們得用有個網絡回調接口,這裏我用的是抽象類ResultMyCall,這裏用抽象類的好處是,咱們能夠把統一重複操做放在父類裏,只要不重寫方法,都會按父類方法去實現,因此這裏有時候你只須要重寫一個onSuccess方法便可,不像接口同樣要把方法所有實現。要注意的是,若是要基於mvc,最好用接口ResulCall,這塊到時候介紹mvc的時候回介紹。如今咱們都按抽象類ResultMyCall走。抽象類以下ResultMyCall服務器
public abstract class ResultMyCall<T> {
//請求網絡以前,通常展現loading
public void onBefore() {
}
//請求網絡結束,消失loading
public void onAfter() {
}
//監聽上傳圖片的進度(目前支持圖片上傳,其餘重寫這個方法無效)
public void inProgress(float progress) {
}
//錯誤信息
public void onError(String errorMessage) {
ToastUtils.showToast(errorMessage);
}
public void onSuccess(Object response) {
}
//若是帶了泛型T,這裏個方法會獲取泛型的type,用於解析,若是不帶泛型,默認返回的是String
public Type getSuperclassTypeParameter(Class<?> subclass) {
Type superclass = subclass.getGenericSuperclass();
if (superclass instanceof Class) {
return null;
}
ParameterizedType parameterized = (ParameterizedType) superclass;
return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]);
}
public Type getType() {
return getSuperclassTypeParameter(getClass());
}
}
複製代碼
相信這個類也很容易理解,這裏的OnError我寫了一個Toast。因此每次請求的回調onError能夠不重寫,會自動彈Toast,若是你須要有其餘操做,好比說不彈Toast,是須要打開另一個頁面則能夠重寫這個方法如:cookie
@Override
public void onError(String errorMessage) {
super.onError(errorMessage);
//註釋super.onError(errorMessage);那麼不會走父類方法,
}
複製代碼
public class OkGetBuilder {
private String url;
private String tag;
private Map<String, String> headers;
private Map<String, String> params;
private OkHttpClient okHttpClient;
private Context context;
private Handler mDelivery;
private Request okHttpRequest;
public OkGetBuilder() {
this.okHttpClient = EasyOk.getInstance().getOkHttpClient();
this.context = MyApplication.getContext();
this.mDelivery = EasyOk.getInstance().getmDelivery();
}
public OkGetBuilder build() {
Request.Builder mBuilder = new Request.Builder();
if (params != null) {
mBuilder.url(appendParams(url, params));
} else {
mBuilder.url(url);
}
if (!TextUtils.isEmpty(tag)) {
mBuilder.tag(tag);
}
if (headers != null) {
mBuilder.headers(appendHeaders(headers));
}
okHttpRequest = mBuilder.build();
return this;
}
public void enqueue(final ResultMyCall resultMyCall) {
if (resultMyCall != null) {
mDelivery.post(new Runnable() {
@Override
public void run() {
resultMyCall.onBefore();
}
});
}
okHttpClient.newCall(okHttpRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, final IOException e) {
if (resultMyCall != null) {
mDelivery.post(new Runnable() {
@Override
public void run() {
resultMyCall.onAfter();
String errorMsg;
if (e instanceof SocketException) {
} else {
if (e instanceof ConnectException) {
errorMsg = context.getString(R.string.network_unknow);
} else if (e instanceof SocketTimeoutException) {
errorMsg = context.getString(R.string.network_overtime);
} else {
errorMsg = context.getString(R.string.server_error);
}
resultMyCall.onError(errorMsg);
}
}
});
}
}
@Override
public void onResponse(Call call, final Response response) throws IOException {
//網絡請求成功
if (response.isSuccessful()) {
if (resultMyCall != null) {
String result = response.body().string();
Object successObject = null;
try {
if (resultMyCall.getType() == null) {
successObject = result;
} else {
successObject = GsonUtil.deser(result, resultMyCall.getType());
}
} catch (Throwable e) {
mDelivery.post(new Runnable() {
@Override
public void run() {
resultMyCall.onAfter();
resultMyCall.onError("數據解析出錯了");
}
});
return;
}
if (successObject == null) {
successObject = result;
}
final Object finalSuccessObject = successObject;
mDelivery.post(new Runnable() {
@Override
public void run() {
resultMyCall.onAfter();
resultMyCall.onSuccess(finalSuccessObject);
}
});
}
} else {
//接口請求確實成功了,code 不是 200
if (resultMyCall != null) {
final String errorMsg = response.body().string();
mDelivery.post(new Runnable() {
@Override
public void run() {
resultMyCall.onAfter();
resultMyCall.onError(errorMsg);
}
});
}
}
}
});
}
private Headers appendHeaders(Map<String, String> headers) {
Headers.Builder headerBuilder = new Headers.Builder();
if (headers == null || headers.isEmpty()) return null;
for (String key : headers.keySet()) {
headerBuilder.add(key, headers.get(key));
}
return headerBuilder.build();
}
//get 參數拼在url後面
private String appendParams(String url, Map<String, String> params) {
StringBuilder sb = new StringBuilder();
if (url.indexOf("?") == -1) {
sb.append(url + "?");
} else {
sb.append(url + "&");
}
if (params != null && !params.isEmpty()) {
for (String key : params.keySet()) {
sb.append(key).append("=").append(params.get(key)).append("&");
}
}
sb = sb.deleteCharAt(sb.length() - 1);
LogUtils.i("網絡請求", "請求接口 ==>> " + sb.toString());
return sb.toString();
}
}
複製代碼
首先咱們new這個類的時候把惟一的okHttpClient拿到用來進行請求,把mDelivery拿到,用於子線程切換主線程,那麼在後面的回調方法裏能夠直接進行UI操做。OkGetBuilder裏要作的是把heads用map傳入,那麼要進行一個循環add到head上去如:appendHeaders方法,參數也用map傳入,拼接參數如:appendParams方法。網絡
那麼接下來就是請求這塊
public static OkGetBuilder get() {
return new OkGetBuilder();
}
複製代碼
//這些是所有方法,沒有用到的不使用
//paramsBuilder 是我封裝用的一個傳遞參數的類。有要用的參數一致點下去就行了...
EasyOk.get().url("http://gank.io/api/xiandu/category/wow")
.tag("cancleTag")
//內部已經作了null處理,請求頭部
//.headers(paramsBuilder.getHeads())
//內部已經作了null處理,請求參數
//.params(paramsBuilder.getParams())
.build().enqueue(new ResultMyCall<T>() {
@Override
public void onBefore() {
super.onBefore();
}
@Override
public void onAfter() {
super.onAfter();
}
@Override
public void onError(String errorMessage) {
super.onError(errorMessage);
}
@Override
public void onSuccess(Object response) {
super.onSuccess(response);
//若是你再new ResultMyCall的時候帶了泛型,那麼這裏只須要
//T bean = (T)response ;
//若是沒有帶泛型,那麼默認返回的string類型,
//Sring bean = (String)response;
}
});
複製代碼
若是onBefore和onAfter還有onError,都把統一操做封裝好了而且不須要重寫沒有特殊操做的你能夠這樣:
EasyOk.get().url("http://gank.io/api/xiandu/category/wow")
.tag("cancleTag")
.build().enqueue(new ResultMyCall<T>() {
@Override
public void onSuccess(Object response) {
super.onSuccess(response);
//若是你再new ResultMyCall的時候帶了泛型,那麼這裏只須要
//T bean = (T)response ;
//若是沒有帶泛型,那麼默認返回的string類型,
//Sring bean = (String)response;
}
});
複製代碼
上面介紹我我把緩存和重連等其餘功能沒有說,由於加進去太複雜也不清晰,等後面單獨拿出來說,經過本文的理解,你會知道怎麼去封裝,加到什麼地方去。
通過上述介紹,我們直接看OkDownloadBuilder裏的內容(我把多餘部分去掉了,便於理解):
public class OkDownloadBuilder {
//斷點續傳的長度
private long currentLength;
private String url;
private String tag;
//文件路徑(不包括文件名)
private String path;
//文件名
private String fileName;
//是否開啓斷點續傳
private boolean resume;
private OkHttpClient okHttpClient;
private Handler mDelivery;
private Request.Builder mBuilder;
public OkDownloadBuilder() {
this.okHttpClient = EasyOk.getInstance().getOkHttpClient();
this.mDelivery = EasyOk.getInstance().getmDelivery();
}
public OkDownloadBuilder build() {
mBuilder = new Request.Builder();
mBuilder.url(url);
if (!TextUtils.isEmpty(tag)) {
mBuilder.tag(tag);
}
//這裏只要斷點上傳,總會走緩存。。因此強制網絡下載
mBuilder.cacheControl(CacheControl.FORCE_NETWORK);
return this;
}
public void enqueue(final OnDownloadListener listener) {
if (resume) {
File exFile = new File(path, fileName);
if (exFile.exists()) {
currentLength = exFile.length();
mBuilder.header("RANGE", "bytes=" + currentLength + "-");
}
}
Request okHttpRequest = mBuilder.build();
okHttpClient.newCall(okHttpRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, final IOException e) {
//下載失敗監聽回調
mDelivery.post(new Runnable() {
@Override
public void run() {
listener.onDownloadFailed(e);
}
});
}
@Override
public void onResponse(Call call, Response response) throws IOException {
InputStream is = null;
byte[] buf = new byte[1024];
int len = 0;
FileOutputStream fos = null;
//儲存下載文件的目錄
File dir = new File(path);
if (!dir.exists()) {
dir.mkdirs();
}
final File file = new File(dir, fileName);
try {
is = response.body().byteStream();
//總長度
final long total;
//若是當前長度就等於要下載的長度,那麼此文件就是下載好的文件
//前提是這裏是默認下載的贊成文件,要判斷是否能夠斷點續傳,最好在開啓網絡的時候判斷是不是贊成版本號
if (currentLength == response.body().contentLength()) {
mDelivery.post(new Runnable() {
@Override
public void run() {
listener.onDownloadSuccess(file);
}
});
return;
}
if (resume) {
total = response.body().contentLength() + currentLength;
} else {
total = response.body().contentLength();
}
mDelivery.post(new Runnable() {
@Override
public void run() {
listener.onDownLoadTotal(total);
}
});
if (resume) {
//這個方法是文件開始拼接
fos = new FileOutputStream(file, true);
} else {
//這個是不拼接,從頭開始
fos = new FileOutputStream(file);
}
long sum;
if (resume) {
sum = currentLength;
} else {
sum = 0;
}
while ((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
sum += len;
final int progress = (int) (sum * 1.0f / total * 100);
//下載中更新進度條
mDelivery.post(new Runnable() {
@Override
public void run() {
listener.onDownloading(progress);
}
});
}
fos.flush();
//下載完成
mDelivery.post(new Runnable() {
@Override
public void run() {
listener.onDownloadSuccess(file);
}
});
} catch (final Exception e) {
mDelivery.post(new Runnable() {
@Override
public void run() {
listener.onDownloadFailed(e);
}
});
} finally {
try {
if (is != null) {
is.close();
}
if (fos != null) {
fos.close();
}
} catch (IOException e) {
}
}
}
});
}
}
複製代碼
若是你不看斷點續傳這塊,在onResponse裏其實就是輸入流和文件流,把流寫進文件裏的操做;斷點的續傳的關鍵點是哪些?要知道斷點續傳其實就是接着上次未下載的文件繼續下載(固然這裏要確保下載的是同一文件,以下載更新要保證是同一版本號,這裏在得到版本更新內容的時候判斷是不是同一版本)。關鍵有2點:
mBuilder.header("RANGE", "bytes=" + currentLength + "-");
複製代碼
fos = new FileOutputStream(file, true);//沒錯就是這個true
複製代碼
場景以下:假如首頁廣告,get請求下來的數據,並且這個可能1個星期纔會換一次數據,這個時候若是沒有這個功能,每次進首頁都會去請求網絡,若是有這功能,那麼若是緩存內容在有效期就會跳過網絡請求,直接取緩存。這樣節約流量之餘還能減輕服務器壓力。固然要實現緩存,要設置緩存文件,在初始化okHttpClient時候設置緩存文件
//設置緩存文件路徑,和文件大小
okHttpClent.cache(new Cache(new File(Environment.getExternalStorageDirectory() +"/okhttp_cache/"), 50 * 1024 * 1024))
複製代碼
查閱大量的資料,大量博客都很坑。坑的你懵逼這裏給你們看下正解:okhttp緩存正解,文字地址
看到這篇的時候,你就知道其餘地方均可以不用管,用攔截器能夠實現,可是要清楚什麼是在線緩存,什麼是離線緩存。這是2個概念。在線緩存須要加網絡攔截器
okHttpClient.addNetworkInterceptor(NetCacheInterceptor.getInstance())
複製代碼
這裏用的攔截器,我使用了單例,這樣便於經過改變參數,能夠達到是否使用緩存;NetCacheIntertor代碼以下:
/** * Created by leo * on 2019/7/25. * 在有網絡的狀況下 * 若是還在網絡有效期呢則取緩存,不然請求網絡 * 重點 : 通常okhttp只緩存不大改變的數據適合get。(我的理解 : 例如你設置了個人方案列表接口的緩存後,你刪除了一條方案,刷新下。 * 他取的是緩存,結果那條刪除的數據會出來。這個時候這個接口,不適合用緩存了) * (這裏注意,若是一個接口設置了緩存30秒,下次請求這個接口的30秒內都會去取緩存,即便你設置0也不起效。由於緩存文件裏的標識裏已經有30秒的有效期) */
public class NetCacheInterceptor implements Interceptor {
private static NetCacheInterceptor cacheInterceptor;
//30在線的時候的緩存過時時間,若是想要不緩存,直接時間設置爲0
private int onlineCacheTime;
public static NetCacheInterceptor getInstance() {
if (cacheInterceptor == null) {
cacheInterceptor = new NetCacheInterceptor();
}
return cacheInterceptor;
}
private NetCacheInterceptor() {
}
public void setOnlineTime(int time) {
this.onlineCacheTime = time;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request.Builder builder1 = request.newBuilder();
//這裏咱們登錄,在head裏獲取令牌token存起來,網絡請求的時候把令牌加入head,用於身份區分
String token = (String) PreferenceUtil.get("USER_TOKEN", "");
if (!TextUtils.isEmpty(token)) {
builder1.addHeader("Token", token)
.build();
}
request = builder1.build();
Response response = chain.proceed(request);
List<String> list = response.headers().values("Token");
if (list.size() > 0) {
PreferenceUtil.put("USER_TOKEN", list.get(0));
}
//這裏是設置緩存的操做
if (onlineCacheTime != 0) {
//若是有時間就設置緩存
int temp = onlineCacheTime;
Response response1 = response.newBuilder()
.header("Cache-Control", "public, max-age=" + temp)
.removeHeader("Pragma")
.build();
onlineCacheTime = 0;
return response1;
} else {
//若是沒有時間就不緩存
Response response1 = response.newBuilder()
.header("Cache-Control", "no-cache")
.removeHeader("Pragma")
.build();
return response1;
}
// return response;
}
}
複製代碼
max-age是在線緩存的有效時間,若是我設置了max-age = 3600,那麼意思是在首次請求網絡緩存下來的數據後,在1小時以內都將會直接取緩存,跳過網絡請求。這裏我獲取token是經過攔截器獲取的,response.headers()能夠得到,服務器返回的全部head信息,包括set-cookie信息。並且okhttp提供了.cookjar()。能夠經過cookie持久化等自定義
這些設置完怎麼驗證呢?回到你原始網絡請求onResponse回調裏;經過:
if (response.networkResponse()!=null){
LogUtils.i("內容來源","來自網絡請求");
}
if (response.cacheResponse()!=null){
LogUtils.i("內容來源","來自緩存");
}
複製代碼
固然這裏只是驗證,在onResponse不須要改變,okhttp內部已經作好了全部的工做。
例如騰訊新聞等,在你手機開啓飛行模式的時候,在進app的時候,仍是會依舊顯示以前加載的數據。這個是就是離線緩存,離線緩存和在線緩存最大的區別,在線緩存即便有條件請求網絡也能夠跳過網絡取緩存。一樣經過攔截器添加,在無網絡的時候,是不會走addNetworkInterceptor方法的。可是經過addInterceptor,有沒有網都會走,並且addInterceptor會先於addNetworkInterceptor運行
okHttpClient.addInterceptor(OfflineCacheInterceptor.getInstance());
複製代碼
一樣用了單例,具體以下:
/** * Created by leo * on 2019/7/25. * 這個會比網絡攔截器先 運行 * 在沒有網絡鏈接的時候,會取的緩存 * 重點 : 通常okhttp只緩存不大改變的數據適合get。(我的理解,無網絡的時候能夠將無網絡有效期改長點) * 這裏和前面的不一樣,當即設置,當即生效。例,你一個接口設置1個小時的離線緩存有效期,當即設置0.下次進入後,則無效 */
public class OfflineCacheInterceptor implements Interceptor {
private static OfflineCacheInterceptor offlineCacheInterceptor;
//離線的時候的緩存的過時時間
private int offlineCacheTime;
private OfflineCacheInterceptor() {
}
public static OfflineCacheInterceptor getInstance() {
if (offlineCacheInterceptor == null) {
offlineCacheInterceptor = new OfflineCacheInterceptor();
}
return offlineCacheInterceptor;
}
public void setOfflineCacheTime(int time) {
this.offlineCacheTime = time;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!NetWorkUtils.isNetworkConnected(MyApplication.getContext())) {
if (offlineCacheTime != 0) {
int temp = offlineCacheTime;
request = request.newBuilder()
// .cacheControl(new CacheControl
// .Builder()
// .maxStale(60,TimeUnit.SECONDS)
// .onlyIfCached()
// .build()
// ) 兩種方式結果是同樣的,寫法不一樣
.header("Cache-Control", "public, only-if-cached, max-stale=" + temp)
.build();
offlineCacheTime = 0;
} else {
request = request.newBuilder()
.header("Cache-Control", "no-cache")
.build();
}
}
return chain.proceed(request);
}
}
複製代碼
NetWorkUtils是一個判斷有沒有網絡的工具類。你能夠看到這裏是max-stale,這裏我理解就是要設置的離線緩存有效期,若是設置爲max-stale=3600就是離線緩存1個小時。若是你去深山老林,在長達1小時01分的時候,離線緩存失效,那麼你在此進app,頁面將空白。固然你也能夠設置離線緩存一直有效,Integer.MAX_VALUE。
這裏固然你能夠利用tag來作,若是當前正在請求的池裏的call.request.tag()或等待請求隊列裏的tag,包含你當前請求網絡tag時,則不請求網絡,只顯示loading。可是這樣的話必須每次都要加上tag。因此我直接在EasyOk里加上了一個
//防止網絡重複請求的tagList;
private ArrayList<String> onesTag;
複製代碼
若是沒有tag的時候,這裏我能夠用請求url。在網絡請求成功和結束的時候我從這個集合裏remove掉這個元素。固然你會說,當咱們取消網絡請求的時候呢,其實取消網絡請求的時候會走onFailure(Call call,IOException e)。這個時候錯誤類型是SocketException。因此你取消網絡的時候是會走onFailure,一樣會remove掉、代碼大體以下(只放相關代碼):
public void enqueue(final ResultCall resultMyCall) {
if (resultMyCall != null) {
//這裏是子線程切換到主線程的操做,只要請求網絡,咱們都調onBefore,這裏就是展現loading
mDelivery.post(new Runnable() {
@Override
public void run() {
resultMyCall.onBefore();
}
});
}
//這裏是否帶了onlyOneNet參數,默認是不開啓的,return後將不繼續往下走,就不會開啓網絡了
if (onlyOneNet) {
if (!TextUtils.isEmpty(tag)) {
if (EasyOk.getInstance().getOnesTag().contains(tag)) {
return;
}
EasyOk.getInstance().getOnesTag().add(tag);
} else {
if (EasyOk.getInstance().getOnesTag().contains(url)) {
return;
}
EasyOk.getInstance().getOnesTag().add(url);
}
}
...
}
複製代碼
這裏也查閱了大量博客,都說okhttp有設置重連,設置okHttpClient.retryOnConnectionFailure(true)既可用重連,可是我大量測試發現,然並軟(有明白的小夥伴,求告知)。這裏我用了本身的方式,只要走onFailure,那麼咱們看看有沒有設置重連和重連次數,代碼以下,此代碼都在builder下,每次網絡請求都會new一個builder,以OkGetBuilder爲例:
okHttpClient.newCall(okHttpRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, final IOException e) {
//這裏是取消網絡請求,那麼不用重連了
if (e instanceof SocketException) {
} else {
//tryAgainCount是重連,不設置,默認是0.不開啓重連功能
//currentAgainCount,是OkGetBuilder裏的屬性,每次開啓網絡都會new一個Builder
//固然currentAgainCount初始是0
if (currentAgainCount < tryAgainCount && tryAgainCount > 0) {
currentAgainCount++;
//這裏就是重連操做,call.request會獲取你最開始的request
//this這裏就是你當前new Callback,因此網絡回調還會走這裏
okHttpClient.newCall(call.request()).enqueue(this);
return;
}
}
}
複製代碼
我定義了NetWorListener(get,post,上傳文件網絡回調),OnDownloadListener(文件下載網絡回調),PermissionListener(權限申請結果回調),若是你想請求網絡,只要實現這個接口,而後經過ModelSuperImpl去調用網絡請求,就能在任何你實現這個接口的頁面拿到網絡請求回調,請求只須要這樣:
//在外面調用只須要傳入參數,把url和解析類什麼的都放在ModelSuperImpl裏
ModelSuperImpl.netWork().gankGet(ParamsBuilder.build().params(PARAMS.gank("android")) .command(GANK_COMMAND), this);
複製代碼
ModelSuperImpl大體以下
public class ModelSuperImpl extends ModelBase {
private static final ModelSuperImpl ourInstance = new ModelSuperImpl();
public static ModelSuperImpl netWork() {
return ourInstance;
}
public static ModelPermissionImpl permission() {
return new ModelPermissionImpl();
}
private ModelSuperImpl() {
}
public void gankGet(ParamsBuilder paramsBuilder, NetWorkListener netWorkListener) {
paramsBuilder.url(SystemConst.GANK_GET)
.type(new TypeToken<ResponModel<User>>() {
}.getType())
;
sendOkHttpGet(paramsBuilder, netWorkListener);
}
}
複製代碼
在Activity/Fragment裏或是隻要實現接口(NetWorListener)的地方拿到回調就是這樣,command是爲了區分一個頁面可能請求多個網絡請求:
@Override
public void onNetCallBack(int command, Object object) {
switch (command) {
case GANK_COMMAND:
Response<User> userModel = (Response<User>)object;
break;
}
}
複製代碼
這裏的ParamsBuilder有多個參數具體以下(去掉部分代碼,偏於理解):
public class ParamsBuilder {
//請求網絡的url(必填)
private String url;
//網絡回調的int值(必填)
private int command;
//網絡返回的type類型(選填)不填,則會返回string類型
private Type type;
//網絡請求須要帶的頭部信息(選填,不填爲null)
private HashMap<String, String> heads;
//網絡請求須要帶的參數(選填,不填爲null)
private HashMap<String, String> params;
//網絡loading須要帶的文字信息(選填,不填爲null)
private String loadMessage;
//是否顯示網絡loading(默認爲顯示loading)
private boolean isShowDialog = true;
//網絡請求的tag,可根據tag取消網絡請求(選填,不填:默認當前宿主類名,退出後自動取消)
private String tag;
//是否重寫網絡問題仍是超時問題對回調進行一個重寫
//若是是true,則在回調的時候可對那部分額外操做,除了彈提示還能夠作別的操做
//(選填,不填:重寫不了且只彈提示)
private boolean overrideError;
//json上傳要帶的參數
private String json;
//網絡接口code=200, 但沒有成功,此用戶已關注
//須要重寫帶true,重寫能夠寫邏輯包括彈提示
//不須要重寫只彈提示
private boolean successErrorOverrid;
//離線緩存時間 單位秒
private int cacheOfflineTime;
//有網絡請求時緩存最大時間
private int cacheOnlineTime;
//屢次點擊按鈕,只進行一次聯網請求
//場景:網絡還在loading,又點了一次請求,那麼不發送新請求,只顯示loading
private boolean onlyOneNet = true;
//聯網失敗,重試次數
private int tryAgainCount;
//若是是在網絡請求接口回調不是activity,也不是fragment,用於傳context
//用於showdialog,當請求網絡的頁面不是Activity或是Fragment時必傳
private Context context;
/** * 下載文件才用的到 */
private String path;
private String fileName;
//是否開啓斷點續傳,要注意的是開啓斷點續傳,要保證下載的是同一文件
//默認是不開啓斷點續傳,除非判斷要下載文件和當前未下載文件屬於同一文件
//若是不是那麼從新下載,會清掉以前的文件。
private boolean resume;
}
複製代碼
看到上面,具體設計的參數都寫上了。可是我沒有封裝的很完美,固然必傳的字段,沒傳時,你能夠throw new NullPonintException("參數沒傳");把異常拋出去,一旦不傳,程序就崩潰了。
一樣在下載文件只須要實現OnDownliadListener,便可。調用只須要這樣:
ModelSuperImpl.netWork().downApk(ParamsBuilder.build().path(path)
.fileName(fileName).tag("downApk"), this);
複製代碼
這裏我把請求權限全部邏輯封裝在了 ModelPerissionImpl裏,只要實現PerimissionListener便可拿到網絡請求回調,調用只需這樣:
/RESUME_COMMAND一個頁面可能要請求多個權限,用於區分,this便是PerimissionListener實現類,後面權限參數是可變的若是有多個權限能夠一直逗號加下去
ModelSuperImpl.permission().requestPermission(RESUME_COMMAND, this, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE);
複製代碼
回調只需這樣:
//回調只須要這樣;command是區分一個頁面多個權限申請,若是一個頁面只有1個申請那麼能夠不傳commad
@Override
public void permissionSuccess(int command) {
switch (command) {
case NORMAL_COMMAND:
ModelSuperImpl.netWork().downApk(ParamsBuilder.build().path(path)
.fileName(fileName).tag("downApk"), this);
break;
}
}
複製代碼
大體思路:我把具體的聯網操做和具體的解析封裝在了抽象類ModelBase,把具體的權限申請邏輯封裝在了ModelPerissionImpl,ModelSuperImpl只負責調用網絡請求方法,和權限申請方法,這樣代碼完美分隔了代碼。視圖層View收到了用戶的操做或點擊請求,響應controller,controller通知model處理邏輯業務,拿到結果經過接口告訴controller去更新Ui。加入你多個頁面須要請求同一個url,你只須要經過ModelSuperImpl去調用方法就Ok了。
這裏確定不少人對ParamBuilder裏的overrideError和successErrorOverrid不是很理解。其實這裏默認不是重寫方法,例如網絡請求失敗,我在ModelBase默認是彈出toast,若是是code=200,可是有可能接口走的錯誤方法,如關注失敗,我也是默認不重寫彈出toast。有可能實際操做須要咱們作別的,如網絡請求錯誤,須要咱們跳另一個頁面,那麼這個時候就須要你帶.overrideError(true)和.successErrorOverrid(true)代碼以下:
@Override
public void onNetCallBack(int command, Object object) {
switch (command) {
case GANK_COMMAND:
/** * 請求接口失敗(網絡錯誤,或超時等形成) * 若是須要重寫則請求接口的時候加上.overrideError(true) * 那麼在下面代碼寫上邏輯,若是不須要重寫,已經封住了會自動彈出錯誤提示,並且重 寫會無效 * */
// if (obj instanceof NetFail) {
// NetFail netFailBean = (NetFail) obj;
// 處理邏輯
// return;
// }
/** * 這是接口請求成功 * 若是請求接口,寫了.successErrorOverrid(true) * 說明重寫了雖然code=200,返回的result 不是1;可是須要作其餘邏輯不僅是Toast才須要true * 若是隻是須要Toast錯誤信息,那麼能夠不寫,下面的就不用重寫了。封裝已經默認彈提示 * */
// if (obj instanceof ErrorBean) {
// ErrorBean errorBean = (ErrorBean) obj;
// 處理邏輯
// return;
// }
ResponModel<User> detailModel = (ResponModel<User>) obj;
//更新UI
break;
}
}
複製代碼
記得把builder裏的EventBus所有刪除,我以前沒考慮太多,因此在展現效果的時候用EventBus傳遞了p.p 固然這裏用的泛型類ResponModel,ErrorBean,NetFail都是我根據個人項目來定義的,記得若有數據結構不一樣請修改爲須要的