許多不管怎麼做、怎麼想都沒結果的事,要懂得交給時間。有些事無論你怎麼努力怎麼勉強,時間不夠,還是耐心的等待吧。
1.序言
本文章項目地址:Mvp-RxJava-Retrofit
2016年安卓熱門詞彙MVP,RxJava,Retrofit。時隔一年這些框架依然是很常用的,現在來把這幾個關鍵詞整合起來,自我規範,搭建一個規範型開發框架。。。
選擇MVP框架的原因之一也是google官方的示例中MVP sample已經是完成,證明google官方對於MVP的承認度。
這裏我放上一個谷歌官方demo地址,和一個源碼解析文章
谷歌官方MVPdemo
一個較爲詳細的官方項目源碼解析的文章開始前還是要說說基礎的東西,這裏我講的不是很詳細,如果不懂可以加我qq:958460248一起交流交流
MVP
對於一些剛學安卓的朋友們應該還不是太熟悉,我們先來溫習一下吧!
MVP的思想是將activity作爲view層,只負責與xml的渲染和監聽事件,具體處理數據邏輯放到一個新定義的Presenter層。減少了activity負責的事情。並且可以強迫開發者養成分模塊功能開發的思想。開發前設計好功能模塊,而不是像以前一樣一個Activity中寫流水賬一樣寫代碼。從頭寫到尾。
這張圖可以說是看爛了,這張圖對於懂了點MVP的人可以說是把中間幾個字去掉,都能一眼看穿。這張圖到底是什麼意思呢?
舉個例子:
需求:需要點擊一個按鈕通過訪問網絡獲取一條數據展示在頁面上
普通做法:
一個Activity中寫一個方法訪問網絡獲取數據,點擊按鈕調用它,然後獲取數據完成了再拿到對應的控件設置數據,完事了。。。
MVP:
在圖中有三個模塊view(界面),presenter(控制層),model(數據源)。他們在這個需求中需要做什麼呢?
view(界面):顯示數據
presenter(控制層):1.通知model我要取數據 2.取到了數據再傳遞給view
model(數據源):訪問網絡獲取數據
它的過程是這樣的,
- view告訴presenter我要數據
- presenter告訴model我要數據
- model訪問網絡得到了數據再通知presenter給你我取到的數據
- presenter 處理好數據 再把數據傳遞給view
- 最後view顯示出來用戶可以觀看。
有些人說這不是脫了褲子放屁啊?一點代碼能寫完的東西爲啥分了這麼多東西?
這確實有點複雜,在面向對象中有幾個原則 單一職責原則,開閉原則,里氏代換原則,依賴倒轉原則,接口隔離原則,合成複用原則,迪米特法則。這我就不一一介紹了,自行百度。。。普通做法中一個Activity即有訪問網絡,又有更新界面,第一條單一職責原則就違背了,然而在mvp中view只做和界面相關的事情。
再者一個Activity中如果邏輯太多了。一個Activity幾千行代碼,邏輯判斷,更新界面,查詢數據庫,訪問網絡,如果第二個人需要修改,怎麼看??
這時候再看看mvp 邏輯在P裏面一個類,數據在Model層,界面相關的在V層。清晰明瞭,也方便單元測試。作爲程序猿如果不最求代碼質量,那和鹹魚有什麼區別?
RxJava2+Retrofit2整合
1.玩框架第一步compile :
compile 'io.reactivex.rxjava2:rxjava:2.1.1'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.squareup.retrofit2:converter-scalars:2.3.0'
compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'//配合rxjava2
compile 'com.squareup.okhttp3:logging-interceptor:3.8.1'//攔截器
2.創建service
public interface RequestService {
String BASE_URL = "https://news-at.zhihu.com/api/4/";
/**
* 測試接口
*
* @return
*/
@GET("news/latest")
Observable<TestBean> test();
}
單獨使用retrofit是返回call,配合RxJava這裏我們返回Observable
3.封裝一個工具類
public class RetrofitFactory {
//訪問超時
private static final long TIMEOUT = 30;
// Retrofit是基於OkHttpClient的,可以創建一個OkHttpClient進行一些配置
private static OkHttpClient httpClient = new OkHttpClient.Builder()
//打印接口信息,方便接口調試
.addInterceptor(new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
@Override
public void log(String message) {
Log.e("TAG", "log: " + message);
}
}).setLevel(HttpLoggingInterceptor.Level.BASIC))
.connectTimeout(TIMEOUT, TimeUnit.SECONDS)
.readTimeout(TIMEOUT, TimeUnit.SECONDS)
.build();
private static RetrofitService retrofitService = new Retrofit.Builder()
.baseUrl(RetrofitService.BASE_URL)
// 添加Gson轉換器
.addConverterFactory(GsonConverterFactory.create(new GsonBuilder()
.setLenient()
.create()
))
// 添加Retrofit到RxJava的轉換器
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(httpClient)
.build()
.create(RetrofitService.class);
//獲得RetrofitService對象
public static RetrofitService getInstance() {
return retrofitService;
}
}
使用
我們整合好了,最後我們看下怎麼使用吧!訪問個網絡獲取一個數據
RetrofitFactory.getInstance()//獲取retrofitService對象
.test()//測試接口
.subscribeOn(Schedulers.io())
.doOnSubscribe(new Consumer<Disposable>() {
@Override
public void accept(@NonNull Disposable disposable) throws Exception {
//將這個請求的Disposable添加進入CompositeDisposable同一管理(在封裝的presenter中)
addDisposable(disposable);
//訪問網絡顯示dialog
view.showLoadingDialog("");
}
})
.map(new Function<TestBean, List<TestBean.StoriesBean>>() {
@Override
public List<TestBean.StoriesBean> apply(@NonNull TestBean testBean) throws Exception {
//轉化數據
return testBean.getStories();
}
})
//獲得的數據返回主線程去更新界面
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<List<TestBean.StoriesBean>>() {
@Override
public void accept(@NonNull List<TestBean.StoriesBean> storiesBeen) throws Exception {
//消失dialog
view.dismissLoadingDialog();
//設置數據
view.setData(storiesBeen);
}
}, new Consumer<Throwable>() {
@Override
public void accept(@NonNull Throwable throwable) throws Exception {
view.dismissLoadingDialog();
String exception = ExceptionHelper.handleException(throwable);
//打印出錯誤信息
Log.e("TAG", "exception: " + exception);
}
});
好我們來分析一下,
- 首先先獲得一個retrofitService對象
- 然後調用test接口。
- 訪問網絡在子線程
- 在訪問網絡的時候顯示等待對話框,將這個請求加入CompositeDisposable中(在basePresenter封裝了統一管理的方法,調用addDisposable(disposable);最後Activity關閉,取消所有網絡請求,防止內存泄漏)
- 將網絡獲取的數據轉換成你需要的數據
- 線程卡點結果返回主線程
- 訂閱得到數據更新界面,處理錯誤信息
注意
RxJava2+retrofit2就是這麼簡單封裝好了一條線路下來非常清晰。沒用過的朋友看下有可能一臉懵逼,不過沒關係,你只要拿着我的項目看下就能懂了。
當然我們這是簡單的封裝了一下,但是在框架中,我把一些base類全部抽成一個model,所以代碼寫法會有些不一樣。
打造MVP
先看下我們的成果裏面有什麼東西吧!沒錯 就是下面幾個類就ok
1.分析
好我們來分析一下mvp
1.view需要找presenter拿數據,那麼view裏面需要一個presenter對象。
2.presenter需要給view數據,那麼presenter也需要一個view對象。
3.model層訪問網絡使用RxJava+retrofit,數據回調給presenter(後面分析)
2.思考
所有的view裏面都需要什麼操作呢? 所有的presenter裏面都需要什麼操作呢?
暫時在我的需求中view和presenter只有如下這麼幾個功能,當然,如果你還有其他的功能可以再加上去。
public interface BaseView {
//顯示dialog
void showLoadingDialog(String msg);
//取消dialog
void dismissLoadingDialog();
}
public interface BasePresenter {
//默認初始化
void start();
//Activity關閉把view對象置爲空
void detach();
//將網絡請求的每一個disposable添加進入CompositeDisposable,再退出時候一併註銷
void addDisposable(Disposable subscription);
//註銷所有請求
void unDisposable();
}
3.接下來編寫view和presenter的實現類
由於每一個view都對應不同的presenter。當然對應的每個presenter也同樣對應一個view。所有我們使用接口和泛型來封裝了。
所以我們先看下代碼:
public abstract class BaseActivity<P extends BasePresenter> extends AppCompatActivity implements BaseView {
protected P presenter;
public Context context;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
context = this;
ActivityManager.getAppInstance().addActivity(this);//將當前activity添加進入管理棧
presenter = initPresenter();
}
@Override
protected void onDestroy() {
ActivityManager.getAppInstance().removeActivity(this);//將當前activity移除管理棧
if (presenter != null) {
presenter.detach();//在presenter中解綁釋放view
presenter = null;
}
super.onDestroy();
}
/**
* 在子類中初始化對應的presenter
*
* @return 相應的presenter
*/
public abstract P initPresenter();
@Override
public void dismissLoadingDialog() {
}
@Override
public void showLoadingDialog(String msg) {
}
}
public abstract class BasePresenterImpl<V extends BaseView> implements BasePresenter {
public BasePresenterImpl(V view) {
this.view = view;
start();
}
protected V view;//給子類使用view
@Override
public void detach() {
this.view = null;
unDisposable();
}
@Override
public void start() {
}
/////////////////////////////////////////////////////////////////////////////////
//以下下爲配合RxJava2+retrofit2使用的
//將所有正在處理的Subscription都添加到CompositeSubscription中。統一退出的時候註銷觀察
private CompositeDisposable mCompositeDisposable;
/**
* 將Disposable添加
*
* @param subscription
*/
@Override
public void addDisposable(Disposable subscription) {
//csb 如果解綁了的話添加 sb 需要新的實例否則綁定時無效的
if (mCompositeDisposable == null || mCompositeDisposable.isDisposed()) {
mCompositeDisposable = new CompositeDisposable();
}
mCompositeDisposable.add(subscription);
}
/**
* 在界面退出等需要解綁觀察者的情況下調用此方法統一解綁,防止Rx造成的內存泄漏
*/
@Override
public void unDisposable() {
if (mCompositeDisposable != null) {
mCompositeDisposable.dispose();
}
}
}
- 創建activity中泛型傳入相應的view接口,presenter中泛型傳入相應的presenter接口
activity中onCreate中初始化presenter,onDestroy中調用detach,將presenter中正在執行的任務取消,將view對象置爲空。
presenter中通過構造傳遞參數。將view的實例傳遞進入presenter
4.使用
好的接下來我們來使用一下吧
首先我們先來個簡單的需求:
打開一個頁面請求網絡獲取數據,將數據顯示在界面上
1.創建Contact管理接口
首先先思考view需要設置數據所有view中需要一個setData方法
presenter需要去訪問網絡所以需要一個getData方法。代碼如下:
public interface TestContact {
interface view extends BaseView {
/**
* 設置數據
*
* @param dataList
*/
void setData(List<TestBean.StoriesBean> dataList);
}
interface presenter extends BasePresenter {
/**
* 獲取數據
*/
void getData();
}
}
創建Activity和presenter
創建一個Activity繼承BaseActivity它的泛型對應presenter的接口。實現對應的view接口
創建一個TestPresenter繼承BasePresenterImpl,泛型對應view的接口。並實現對應的presenter接口
代碼如下:
public class TestActivity extends BaseActivity<TestContact.presenter> implements TestContact.view {
private List<TestBean.StoriesBean> list = new ArrayList<>();//數據
private RecyclerView recyclerView;
private TestAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
init();
presenter.getData();
}
/**
* 初始化界面
*/
private void init() {
recyclerView = (RecyclerView) findViewById(R.id.recycleview);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
adapter = new TestAdapter(list);
recyclerView.setAdapter(adapter);
}
/**
* 初始化presenter
*
* @return 對應的presenter
*/
@Override
public TestContact.presenter initPresenter() {
return new TestPresenter(this);
}
/**
* 設置數據
* 刷新界面
*
* @param dataList 數據源
*/
@Override
public void setData(List<TestBean.StoriesBean> dataList) {
list.addAll(dataList);
adapter.notifyDataSetChanged();
}
}
public class TestPresenter extends BasePresenterImpl<TestContact.view> implements TestContact.presenter {
public TestPresenter(TestContact.view view) {
super(view);
}
/**
* 獲取數據
*/
@Override
public void getData() {
Api.getInstance()
.test()//測試接口
.subscribeOn(Schedulers.io())
.doOnSubscribe(new Consumer<Disposable>() {
@Override
public void accept(@NonNull Disposable disposable) throws Exception {
addDisposable(disposable);//請求加入管理
view.showLoadingDialog("");
}
})
.map(new Function<TestBean, List<TestBean.StoriesBean>>() {
@Override
public List<TestBean.StoriesBean> apply(@NonNull TestBean testBean) throws Exception {
return testBean.getStories();//轉換數據
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<List<TestBean.StoriesBean>>() {
@Override
public void accept(@NonNull List<TestBean.StoriesBean> storiesBeen) throws Exception {
view.dismissLoadingDialog();
view.setData(storiesBeen);
}
}, new Consumer<Throwable>() {
@Override
public void accept(@NonNull Throwable throwable) throws Exception {
view.dismissLoadingDialog();
ExceptionHelper.handleException(throwable);
}
});
}
}
分析
好了相信大部分朋友看了代碼都看懂了,簡要的分析一下過程吧
- 創建對應的類,實現對應的方法
- Activity中只有一個recyclerView初始化它。
- 在onCreate中調用presenter中的getData()方法
- 在presenter中使用RxJava2+retrofit2訪問網絡。獲取數據返回給view
- view拿到數據更新界面
結束語
到這裏我們的框架簡單的搭建好了算是,如果有什麼錯誤或者問題希望朋友們可以提醒下,讓我們互相學習。
適合自己的纔是最好的。這只是一個最基本的框架,相信許多朋友或許有更多的各種各樣的需求,所以我這裏提供的最簡單的框,方便修改整理,成爲最適合你自己的框架。