前言:仍是由於公司特別閒,把基礎總結總結。若是對相關知識含糊不清,會致使你沒法隨意擴展你想要的框架和功能。可是以爲做爲程序員這行業,只要踏進來了,不是在學習的路上就是在被淘汰的路上,加油!!html
本文章將針對全部用戶,你們根據本身的所需自行跳過一些章節。首先仍是說說這個框架支持幹一些什麼事:java
get請求 | post請求 | 上傳文件 |
---|---|---|
下載文件 | ||
好了就這麼多,幾乎包括了你能用到的功能了。那麼接下來咱們分別來介紹RxJava是什麼?Retrofit是什麼?爲何使用MVP? 固然我這裏是引導你怎麼去學習RxJava和Retrofit,對這2方便本文章不會細將,只是把我封裝的這些會講的特別細,封裝RxJava結合Retrofit的MVP的實戰框架。正文開始了,初學者得一步一步來哦:git
以觀察者模式爲主的一個響應式編程。響應式編程是一種基於異步數據流概念的編程模式。數據流就像一條河:它能夠被觀測,被過濾,被操做,或者爲新的消費者與另一條流合併爲一條新的流。固然他不僅是異步,也有同步。 它最大的有點是能夠來回切換線程,不得不說很哇塞!!程序員
直接上乾貨,組好都看一遍,花不了多長時間:github
我的建議在學習的時候,最好是手敲代碼。雖然不少重複代碼,但這會讓你更加熟悉,甚至你都能拼出這些單詞。操做符只建議記住前2篇的就好了,由於實在太多了你也記不住,第3篇適合當api,沒事看看,有時候當api查查就行了編程
這裏我好想吐槽,好想吐槽!api
Retrofit就是基於OKHttp封裝的,只不過是原做者封裝的,以註解的方式配置好就能請求網絡了。說實話它或許真的不如一些優秀的OKHttp封裝,可是誰讓他支持RxJava呢。Retrofit結合RxJava使用簡直就是一大利器。剛剛說到了RxJava能夠切換線程,你能夠把全部耗時操做放在 子線程,到更改ui回調的時候切換到主線程就行了。緩存
下面是Retrofit的乾貨:網絡
看完了這些相信你對Retrofit有了大體瞭解。我的建議,最好把get請求,post請求,文件上傳都簡單跑通。這裏稍微說下@Url的用法,裏面是一個完整的url地址,會忽略Retrofit設置的baseUrl。同時,Retrofit是封裝好的。因此一些請求參數是不會打印的,固然能夠經過攔截器打印。
固然在項目中要封裝保持一個Retrofit,其中有個.client的方法,能夠放一個OkhttpClient。這個時候能夠自定義日誌攔截器:多線程
public class HttpLogInterceptor implements Interceptor {
//這裏省略部分代碼,具體看項目
//這裏要注意去掉上傳文件的請求頭部打印,下載文件的返回參數不打印,由於文件太大處理很差會oom,在說是文件內容,不重要
private final Charset UTF8 = Charset.forName("UTF-8");
@Override
public Response intercept(Chain chain) throws IOException {
StringBuffer sbf = new StringBuffer();
Request request = chain.request();
RequestBody requestBody = request.body();
String body = null;
if (requestBody != null) {
Buffer buffer = new Buffer();
requestBody.writeTo(buffer);
Charset charset = UTF8;
MediaType contentType = requestBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF8);
}
body = buffer.clone().readString(charset);
if (!TextUtils.isEmpty(body)) {
//若是是圖片上傳調用URLDecoder會報錯,即便tryCache都沒用,what!!!
String netUrl = request.url().toString();
if (netUrl.contains(SystemConst.DIFFERT_URL)) {
body = "本次請求圖片上傳或下載,沒法打印參數!";
} else {
body = URLDecoder.decode(body, "utf-8");
}
}
}
sbf.append(" \n請求方式:==> " + request.method())
.append("\nurl:" + request.url())
.append("\n請求頭:" + request.headers())
.append("\n請求參數: " + body);
Response response = chain.proceed(request);
String rBody = "";
String netUrl = request.url().toString();
if (!netUrl.contains(SystemConst.QQ_APK)) {
ResponseBody responseBody = response.body();
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE);
Buffer buffer = source.buffer();
Charset charset = UTF8;
MediaType contentType = responseBody.contentType();
if (contentType != null) {
try {
charset = contentType.charset(UTF8);
} catch (UnsupportedCharsetException e) {
e.printStackTrace();
}
}
rBody = buffer.clone().readString(charset);
if (!TextUtils.isEmpty(rBody)) {
rBody = decodeUnicode(rBody);
}
}
sbf.append("\n收到響應: code ==> " + response.code())
.append("\nResponse: " + rBody);
LogUtils.i("網絡請求", sbf.toString());
return response;
}
}
複製代碼
看看打印出來的效果,很清晰哦:
看這一點,要打印請求參數,返回參數,head等信息仍是要經過okhttp的攔截器去作。這是吐槽Retrofit封裝的一點
首先是我我的觀點。此框架適合大型一點項目,且此框架是代碼邏輯簡潔,清晰,分離出視圖層和Model層邏輯處理,適合作單元測試,且使activity裏的代碼減小,並且不會由於有些線程,異步的處理,有activity的引用致使沒法回收,引發一系列問題。仍是要強調一下只是代碼邏輯簡潔,不是代碼簡潔。由於他要增長不少類。那麼來講說怎麼代碼邏輯簡潔(適合其餘人接手項目,和團隊開發):
首先google官方demo,也是把功能頁面模塊化了。這樣你看個人分包,結合個人圖片展現一看就明白
是否是一看就很清楚,好比getfragment的全部東西都在裏面。固然你還能夠在裏面繼續分包
public interface GetContract {
//view只有2個更新ui的方法
interface View extends BaseView {
// 一、獲取get數據,更新ui
void showGetData(String data);
// 二、獲取get數據失敗,更新ui
void showGetError(String msg);
}
//get的prensenter只有一個獲取get數據的數據請求
interface Prensenter {
// 一、開啓get網絡請求
public void getGetData(String params);
}
}
複製代碼
好比你要看一個頁面有什麼更新UI的操做或者有什麼網絡請求,只要看契約類就對這個頁面很瞭解瞭如:
一、view就是至關於MVP的V層,通常被activity或者Fragment實現,能夠看到view裏有2個方法:
二、Prensenter就是MVP的P層須要使用到的方法,要搞清楚,其實還有一個實體類GetPresenter。實體類實現GetContract.Prensenter。能夠看到裏面就一個getGetData。的網絡請求。爲何這麼寫,第一契約類能夠減小類的數目,第二更重要的一點是你能夠很快的清楚你開發模塊的功能和操做
還有一點要注意,由於使用RxJava結合Retrofit,我把通用的方法所有封裝了BasePresenter裏。由於RxJava的鏈式編程,因此我直接省略掉了model層,固然你能夠寫一個接口,而後寫個model層把數據回調給Presenter。這裏要注意MVP,MVP只是開發框架,是咱們借鑑的開發思路。可能100我的有100個不一樣的MVP框架,這裏我是說說個人想法,你們能夠借鑑和學習。
public class RetrofitManager {
//省略部分代碼,便於理解
private static RetrofitManager retrofitManager;
private OkHttpClient okHttpClient;
private Retrofit retrofit;
//這是我retrofit的網絡請求配置
private RetrofitApiService retrofitApiService;
private RetrofitManager() {
//初始化Okhttp
initOkHttpClient();
//初始化retrofit
initRetrofit();
}
public static RetrofitManager getRetrofitManager() {
if (retrofitManager == null) {
synchronized (RetrofitManager.class) {
if (retrofitManager == null) {
retrofitManager = new RetrofitManager();
}
}
}
return retrofitManager;
}
private void initRetrofit() {
retrofit = new Retrofit.Builder()
.baseUrl(SystemConst.DEFAULT_SERVER)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.build();
retrofitApiService = retrofit.create(RetrofitApiService.class);
}
public static RetrofitApiService getApiService() {
return retrofitManager.retrofitApiService;
}
private void initOkHttpClient() {
okHttpClient = new OkHttpClient.Builder()
//設置緩存文件路徑,和文件大小
.cache(new Cache(new File(Environment.getExternalStorageDirectory() + "/okhttp_cache/"), 50 * 1024 * 1024))
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.addInterceptor(new HttpLogInterceptor())
//設置在線和離線緩存
.addInterceptor(OfflineCacheInterceptor.getInstance())
.addNetworkInterceptor(NetCacheInterceptor.getInstance())
.build();
}
}
複製代碼
回想以前咱們有個BaseView。裏面有2個方法showLoading和hideLoading (只要記住你們通用的方法封裝成base,有些頁面不一樣就不調用就好了) 代碼以下:
public interface BaseView {
//顯示正在加載loading
void showLoading(String message);
// 關閉正在加載loading
void hideLoading();
//防止RxJava內存泄漏,能夠暫且忽略
LifecycleTransformer bindLifecycle();
}
複製代碼
那麼GetFragment契約類就是(還要說下這裏的presenter接口只是GetPresenter所要用的方法,放在契約類裏,一目瞭然):
public interface GetContract {
//view只有2個更新ui的方法
interface View extends BaseView {
// 一、獲取get數據,更新ui
void showGetData(String data);
// 二、獲取get數據失敗,更新ui
void showGetError(String msg);
}
//get的prensenter只有一個獲取get數據的數據請求
interface Prensenter {
// 一、開啓get網絡請求
public void getGetData(String params);
}
}
複製代碼
咱們想象有什麼通用方法是在BasePresenter裏的?固然你能夠把一些複雜的寫不少步驟的網絡請求封裝在這裏,好比咱們項目裏的下載文件等,這裏咱們講簡單的通用方法
GetPresenter須要view的引用 因此有:一、setView加入引用 二、view = null置空引用,防止oom
//這個是爲了退出頁面,取消請求的
public CompositeDisposable compositeDisposable;
// 綁定的view
private V mvpView;
//綁定view,通常在初始化中調用該方法
public void attachView(V mvpView) {
this.mvpView = mvpView;
compositeDisposable = new CompositeDisposable();
}
//置空view,通常在onDestroy中調用
public void detachView() {
this.mvpView = null;
//退出頁面的時候移除全部網絡請求
removeDisposable();
}
//須要退出頁面移除網絡請求的加入進來
public void addDisposable(Disposable disposable) {
compositeDisposable.add(disposable);
}
//退出頁面移除全部網絡請求
public void removeDisposable() {
if (compositeDisposable != null) {
compositeDisposable.dispose();
}
}
複製代碼
使用一個方法快速獲取RetrofitManager裏的網絡配置
public RetrofitApiService apiService() {
return RetrofitManager.getRetrofitManager().getApiService();
}
複製代碼
大部分的網絡請求都有showLoading和hideLoading,還有線程切換,封裝進來
//這裏我多加了個是否顯示loading的標識和loading上的文字,想偷懶能夠用方法重載把這2個參數默認
public <T> Observable<T> observe(Observable<T> observable, final boolean showDialog, final String message) {
return observable.subscribeOn(Schedulers.io())
.doOnSubscribe(new Consumer<Disposable>() {
@Override
public void accept(Disposable disposable) throws Exception {
if (showDialog) {
mvpView.showLoading(message);
}
}
})
.subscribeOn(AndroidSchedulers.mainThread())
.doFinally(new Action() {
@Override
public void run() throws Exception {
if (showDialog) {
mvpView.hideLoading();
}
}
})
.observeOn(AndroidSchedulers.mainThread())
//防止RxJava內存泄漏
.compose(mvpView.bindLifecycle());
}
複製代碼
那麼咱們的BasePresenter(移除部分代碼更清晰)
//這裏加上泛型,第一在使用Presenter一眼就看出對應哪一個View,其次肯定咱們V的類型
public abstract class BasePresenter<V extends BaseView> {
public CompositeDisposable compositeDisposable;
private V mvpView;
public void attachView(V mvpView) {
this.mvpView = mvpView;
compositeDisposable = new CompositeDisposable();
}
public void detachView() {
this.mvpView = null;
removeDisposable();
}
//檢查是否有view的引用
public boolean isViewAttached() {
return mvpView != null;
}
//獲取view的引用
public V getView() {
return mvpView;
}
public RetrofitApiService apiService() {
return RetrofitManager.getRetrofitManager().getApiService();
}
//須要退出頁面移除網絡請求的加入進來
public void addDisposable(Disposable disposable) { }
//退出頁面移除全部網絡請求
public void removeDisposable() {}
//省的寫線程切換和showloading和hide的複雜操做
public <T> Observable<T> observe(Observable<T> observable, final boolean showDialog, final String message) {}
}
複製代碼
那麼GetPresenter就是這樣
//省略部分代碼,便於理解。實體GetPresenter實現GetContract.Prensenter接口,裏面就一個getGetData方法
public class GetPresenter extends BasePresenter<GetContract.View> implements GetContract.Prensenter {
@Override
public void getGetData(String params) {
if (!isViewAttached()) {
//若是沒有View引用就不加載數據
return;
}
//BasePresenter裏有了封裝,切換線程和放置內存泄漏的 .compose(mvpView.bindLifecycle())都不用寫了
//代碼越能偷懶,說明框架越是封裝的完美
observe(apiService().getGank(params))
.subscribe(new Consumer<GankFatherBean>() {
@Override
public void accept(GankFatherBean gankFatherBean) throws Exception {
getView().showGetData(gankFatherBean.getTitle());
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
getView().showGetError(throwable.toString());
}
});
}
}
複製代碼
由於使用RxJava,若是使用不當很容易形成RxJava內存泄漏,因此官方也出了方法繼承RxFragment
由於fragment是做爲view是要實現view接口的,同時每一個fragment都要有presenter去調用方法
初步以下(省了部分代碼,便於理解):
public abstract class BaseFragment<T extends BasePresenter> extends RxFragment implements BaseView {
public T mPresenter;
public abstract T cretaePresenter();
protected View mContentView;
private Unbinder mUnbinder;
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
// 避免屢次從xml中加載佈局文件
mPresenter = cretaePresenter();
if (mPresenter != null) {
mPresenter.attachView(this);
}
return mContentView;
}
@Override
public void onDestroy() {
super.onDestroy();
if (mPresenter != null) {
mPresenter.detachView();
mPresenter = null;
}
}
@Override
public void showLoading(String message) {
LoadingDialog.getInstance().show(getActivity(), message);
}
@Override
public void hideLoading() {
LoadingDialog.getInstance().dismiss();
}
//防止Rx內存泄漏
@Override
public LifecycleTransformer bindLifecycle() {
LifecycleTransformer objectLifecycleTransformer = bindToLifecycle();
return objectLifecycleTransformer;
}
}
複製代碼
public class GETFragment extends BaseFragment<GetPresenter> implements GetContract.View {
@BindView(R.id.txt_content) TextView txt_content;
@Override
public GetPresenter cretaePresenter() {
return new GetPresenter();
}
@Override
public int getContentViewId() {
return R.layout.fragment_get;
}
//處理邏輯
@Override
protected void processLogic(Bundle savedInstanceState) {
}
@OnClick(R.id.txt_get)
public void getClick() {
//請求網絡
mPresenter.getGetData("Android");
}
@Override
public void showGetData(String data) {
txt_content.setText(data);
}
@Override
public void showGetError(String msg) {
ToastUtils.showToast(msg);
}
}
複製代碼
由於使用了強大的RxJava,這裏斷網重連用的就是操做符retryWhen,封裝在BasePresenter裏的observeWithRetry。這裏就很少講了,能夠先把操做符retryWhen瞭解清楚就看明白了
這裏的返回值就是一個disposable,若是主動取消就直接調 disposable.dispose();固然我這裏也封裝了離開頁面取消請求你能夠把它加到addDisposable(Disposable disposable)裏。就不用管了
Disposable disposable = observe(apiService().getGank(params))
.subscribe(new Consumer<GankFatherBean>() {
@Override
public void accept(GankFatherBean gankFatherBean) throws Exception {
getView().showGetData(gankFatherBean.getTitle());
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
getView().showGetError(throwable.toString());
}
});
複製代碼
這裏是在RetrofitManage裏放了一個private ArrayList oneNetList;請求網絡的時候把,方法名當成tag加入進去,若是請求的時候判斷下,oneNetList裏若是已經有次tag,則直接return,在請求成功或者失敗的時候移除tag
這裏仍是利用了okhttp的攔截器,因此這裏再次吐槽下Retrofit。
這裏不清楚的能夠去看我以前的一篇文章EasyOk。講的很詳細,簡直就是如出一轍 本項目具體用法在,GetPresenter裏有。
由於用Restrofit上傳文件是這樣的。
@POST
@Multipart
Observable<ResponseBody> uploadPic(@Url String url, @Part MultipartBody.Part file);
複製代碼
參數是MultipartBody.Part。不監聽進度參數就是這樣寫的
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
MultipartBody.Part body = MultipartBody.Part.createFormData("file", file.getName(), requestFile);
複製代碼
若是要監聽進度,就要重寫RequestBody,把文件寫入進度返回出來,其實和EasyOk的思路是如出一轍的,本項目上傳文件相關類在: retrofitwithrxjava.uploadutils的包裏。用法在postfragment裏。其中包括多張圖片監聽,不一樣key,不一樣圖片。和同一key,不一樣圖片
首先咱們看下載文件請求
@GET
@Streaming //10以上用@streaming。不會形成oom,反正你用就是了
Observable<ResponseBody> downloadFile(@Url String url);
複製代碼
返回的是ResponseBody,是否是靈感來了,咱們能夠重寫一個FileDownLoadObserver觀察者,而後利用map操做符,對ResponseBody進行轉換,轉換成File的FileDownLoadObserver,固然有了ResponseBody,文件寫入能夠在FileDownLoadObserver裏。有了文件寫入,那麼進度就來了。本項目下載文件相關類在:retrofitwithrxjava.downloadutils包裏。用法在downloadfragment裏。
斷點下載
咱們知道斷點下載,其實就是head里加個RANG的字段,其餘都是如出一轍的:
@GET
@Streaming
Observable<ResponseBody> downloadFile(@Url String url, @Header("RANGE")String range);
複製代碼
這個字段是這樣的: String range = "bytes=" + currentLength + "-";
currentLength是你下載文件裏,下載部分文件的長度,你能夠經過file.length獲取。而後讀寫的時候fos = new FileOutputStream(file, true),輸出流必須帶第二個參數true,意思就是繼續拼接的意思。所有功能都講完了。沒有講的太細是由於我以爲大部分額外功能都是重寫,並且都是利用okhttp,其實看過了我之前的EasyOk。其實就明白了。這也是我單獨吐槽Retrofit的緣由。我相信封裝的很完美的okhttp,確定比這個用起來好用,惋惜人家支持Rxjava。不過經過這個RANGE,其實要下載一個文件的話能夠分不少段下載,由於看他的字面意思就知道了"bytes=start-end",指的是一段範圍,我上面寫的是"bytes=start-"就是默認下載完,因此有些app開啓多線程下載,包括迅雷開啓vip就下的快,其實就是給你多開了幾條線程下載了。!!