搭建屬于自己的MVP+RXJAVA2+Retroft快速開(kāi)發(fā)框架

許多不管怎么做、怎么想都沒(méi)結(jié)果的事,要懂得交給時(shí)間。有些事無(wú)論你怎么努力怎么勉強(qiáng),時(shí)間不夠,還是耐心的等待吧。

1.序言

本文章項(xiàng)目地址:Mvp-RxJava-Retrofit

2016年安卓熱門(mén)詞匯MVP,RxJava,Retrofit。時(shí)隔一年這些框架依然是很常用的,現(xiàn)在來(lái)把這幾個(gè)關(guān)鍵詞整合起來(lái),自我規(guī)范,搭建一個(gè)規(guī)范型開(kāi)發(fā)框架。。。
選擇MVP框架的原因之一也是google官方的示例中MVP sample已經(jīng)是完成,證明google官方對(duì)于MVP的承認(rèn)度。
這里我放上一個(gè)谷歌官方demo地址,和一個(gè)源碼解析文章
谷歌官方MVPdemo
一個(gè)較為詳細(xì)的官方項(xiàng)目源碼解析的文章

開(kāi)始前還是要說(shuō)說(shuō)基礎(chǔ)的東西,這里我講的不是很詳細(xì),如果不懂可以加我qq:957250254一起交流交流


MVP

對(duì)于一些剛學(xué)安卓的朋友們應(yīng)該還不是太熟悉,我們先來(lái)溫習(xí)一下吧!

MVP的思想是將activity作為view層,只負(fù)責(zé)與xml的渲染和監(jiān)聽(tīng)事件,具體處理數(shù)據(jù)邏輯放到一個(gè)新定義的Presenter層。減少了activity負(fù)責(zé)的事情。并且可以強(qiáng)迫開(kāi)發(fā)者養(yǎng)成分模塊功能開(kāi)發(fā)的思想。開(kāi)發(fā)前設(shè)計(jì)好功能模塊,而不是像以前一樣一個(gè)Activity中寫(xiě)流水賬一樣寫(xiě)代碼。從頭寫(xiě)到尾。

這里寫(xiě)圖片描述

這張圖可以說(shuō)是看爛了,這張圖對(duì)于懂了點(diǎn)MVP的人可以說(shuō)是把中間幾個(gè)字去掉,都能一眼看穿。這張圖到底是什么意思呢?

舉個(gè)例子:

需求:需要點(diǎn)擊一個(gè)按鈕通過(guò)訪問(wèn)網(wǎng)絡(luò)獲取一條數(shù)據(jù)展示在頁(yè)面上

普通做法:
一個(gè)Activity中寫(xiě)一個(gè)方法訪問(wèn)網(wǎng)絡(luò)獲取數(shù)據(jù),點(diǎn)擊按鈕調(diào)用它,然后獲取數(shù)據(jù)完成了再拿到對(duì)應(yīng)的控件設(shè)置數(shù)據(jù),完事了。。。

MVP:
在圖中有三個(gè)模塊view(界面),presenter(控制層),model(數(shù)據(jù)源)。他們?cè)谶@個(gè)需求中需要做什么呢?
view(界面):顯示數(shù)據(jù)
presenter(控制層):1.通知model我要取數(shù)據(jù) 2.取到了數(shù)據(jù)再傳遞給view
model(數(shù)據(jù)源):訪問(wèn)網(wǎng)絡(luò)獲取數(shù)據(jù)

它的過(guò)程是這樣的,

  1. view告訴presenter我要數(shù)據(jù)
  2. presenter告訴model我要數(shù)據(jù)
  3. model訪問(wèn)網(wǎng)絡(luò)得到了數(shù)據(jù)再通知presenter給你我取到的數(shù)據(jù)
  4. presenter 處理好數(shù)據(jù) 再把數(shù)據(jù)傳遞給view
  5. 最后view顯示出來(lái)用戶可以觀看。

有些人說(shuō)這不是脫了褲子放屁?。恳稽c(diǎn)代碼能寫(xiě)完的東西為啥分了這么多東西?
這確實(shí)有點(diǎn)復(fù)雜,在面向?qū)ο笾杏袔讉€(gè)原則 單一職責(zé)原則,開(kāi)閉原則,里氏代換原則,依賴倒轉(zhuǎn)原則,接口隔離原則,合成復(fù)用原則,迪米特法則。這我就不一一介紹了,自行百度。。。普通做法中一個(gè)Activity即有訪問(wèn)網(wǎng)絡(luò),又有更新界面,第一條單一職責(zé)原則就違背了,然而在mvp中view只做和界面相關(guān)的事情。
再者一個(gè)Activity中如果邏輯太多了。一個(gè)Activity幾千行代碼,邏輯判斷,更新界面,查詢數(shù)據(jù)庫(kù),訪問(wèn)網(wǎng)絡(luò),如果第二個(gè)人需要修改,怎么看??
這時(shí)候再看看mvp 邏輯在P里面一個(gè)類,數(shù)據(jù)在Model層,界面相關(guān)的在V層。清晰明了,也方便單元測(cè)試。作為程序猿如果不最求代碼質(zhì)量,那和咸魚(yú)有什么區(qū)別?

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.創(chuàng)建service

public interface RequestService {

    String BASE_URL = "https://news-at.zhihu.com/api/4/";

    /**
     * 測(cè)試接口
     *
     * @return
     */
    @GET("news/latest")
    Observable<TestBean> test();
}

單獨(dú)使用retrofit是返回call,配合RxJava這里我們返回Observable

3.封裝一個(gè)工具類

public class RetrofitFactory {

    //訪問(wèn)超時(shí)
    private static final long TIMEOUT = 30;

    // Retrofit是基于OkHttpClient的,可以創(chuàng)建一個(gè)OkHttpClient進(jìn)行一些配置
    private static OkHttpClient httpClient = new OkHttpClient.Builder()
            //打印接口信息,方便接口調(diào)試
            .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轉(zhuǎn)換器
            .addConverterFactory(GsonConverterFactory.create(new GsonBuilder()
                    .setLenient()
                    .create()
            ))
            // 添加Retrofit到RxJava的轉(zhuǎn)換器
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .client(httpClient)
            .build()
            .create(RetrofitService.class);
    
    //獲得RetrofitService對(duì)象
    public static RetrofitService getInstance() {
        return retrofitService;
    }
}

使用

我們整合好了,最后我們看下怎么使用吧!訪問(wèn)個(gè)網(wǎng)絡(luò)獲取一個(gè)數(shù)據(jù)

 RetrofitFactory.getInstance()//獲取retrofitService對(duì)象
                .test()//測(cè)試接口
                .subscribeOn(Schedulers.io())
                .doOnSubscribe(new Consumer<Disposable>() {
                    @Override
                    public void accept(@NonNull Disposable disposable) throws Exception {
                        //將這個(gè)請(qǐng)求的Disposable添加進(jìn)入CompositeDisposable同一管理(在封裝的presenter中)
                        addDisposable(disposable);
                        //訪問(wèn)網(wǎng)絡(luò)顯示dialog
                        view.showLoadingDialog("");
                    }
                })
                .map(new Function<TestBean, List<TestBean.StoriesBean>>() {
                    @Override
                    public List<TestBean.StoriesBean> apply(@NonNull TestBean testBean) throws Exception {
                        //轉(zhuǎn)化數(shù)據(jù)
                        return testBean.getStories();
                    }
                })
                //獲得的數(shù)據(jù)返回主線程去更新界面
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<List<TestBean.StoriesBean>>() {
                    @Override
                    public void accept(@NonNull List<TestBean.StoriesBean> storiesBeen) throws Exception {
                        //消失dialog
                        view.dismissLoadingDialog();
                        //設(shè)置數(shù)據(jù)
                        view.setData(storiesBeen);
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(@NonNull Throwable throwable) throws Exception {
                        view.dismissLoadingDialog();
                        String exception = ExceptionHelper.handleException(throwable);
                        //打印出錯(cuò)誤信息
                        Log.e("TAG", "exception: " + exception);
                    }
                });

好我們來(lái)分析一下,

  1. 首先先獲得一個(gè)retrofitService對(duì)象
  2. 然后調(diào)用test接口。
  3. 訪問(wèn)網(wǎng)絡(luò)在子線程
  4. 在訪問(wèn)網(wǎng)絡(luò)的時(shí)候顯示等待對(duì)話框,將這個(gè)請(qǐng)求加入CompositeDisposable中(在basePresenter封裝了統(tǒng)一管理的方法,調(diào)用addDisposable(disposable);最后Activity關(guān)閉,取消所有網(wǎng)絡(luò)請(qǐng)求,防止內(nèi)存泄漏)
  5. 將網(wǎng)絡(luò)獲取的數(shù)據(jù)轉(zhuǎn)換成你需要的數(shù)據(jù)
  6. 線程卡點(diǎn)結(jié)果返回主線程
  7. 訂閱得到數(shù)據(jù)更新界面,處理錯(cuò)誤信息

注意

RxJava2+retrofit2就是這么簡(jiǎn)單封裝好了一條線路下來(lái)非常清晰。沒(méi)用過(guò)的朋友看下有可能一臉懵逼,不過(guò)沒(méi)關(guān)系,你只要拿著我的項(xiàng)目看下就能懂了。
當(dāng)然我們這是簡(jiǎn)單的封裝了一下,但是在框架中,我把一些base類全部抽成一個(gè)model,所以代碼寫(xiě)法會(huì)有些不一樣。


打造MVP

先看下我們的成果里面有什么東西吧!沒(méi)錯(cuò) 就是下面幾個(gè)類就ok

這里寫(xiě)圖片描述

1.分析

好我們來(lái)分析一下mvp

1.view需要找presenter拿數(shù)據(jù),那么view里面需要一個(gè)presenter對(duì)象。
2.presenter需要給view數(shù)據(jù),那么presenter也需要一個(gè)view對(duì)象。
3.model層訪問(wèn)網(wǎng)絡(luò)使用RxJava+retrofit,數(shù)據(jù)回調(diào)給presenter(后面分析)

2.思考

所有的view里面都需要什么操作呢? 所有的presenter里面都需要什么操作呢?
暫時(shí)在我的需求中view和presenter只有如下這么幾個(gè)功能,當(dāng)然,如果你還有其他的功能可以再加上去。

public interface BaseView {

    //顯示dialog
    void showLoadingDialog(String msg);

    //取消dialog
    void dismissLoadingDialog();
}

public interface BasePresenter {
    //默認(rèn)初始化
    void start();

    //Activity關(guān)閉把view對(duì)象置為空
    void detach();

    //將網(wǎng)絡(luò)請(qǐng)求的每一個(gè)disposable添加進(jìn)入CompositeDisposable,再退出時(shí)候一并注銷
    void addDisposable(Disposable subscription);

    //注銷所有請(qǐng)求
    void unDisposable();
    
}

3.接下來(lái)編寫(xiě)view和presenter的實(shí)現(xiàn)類

由于每一個(gè)view都對(duì)應(yīng)不同的presenter。當(dāng)然對(duì)應(yīng)的每個(gè)presenter也同樣對(duì)應(yīng)一個(gè)view。所有我們使用接口和泛型來(lái)封裝了。

所以我們先看下代碼:

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);//將當(dāng)前activity添加進(jìn)入管理?xiàng)?        presenter = initPresenter();
    }

    @Override
    protected void onDestroy() {
        ActivityManager.getAppInstance().removeActivity(this);//將當(dāng)前activity移除管理?xiàng)?        if (presenter != null) {
            presenter.detach();//在presenter中解綁釋放view
            presenter = null;
        }
        super.onDestroy();
    }

    /**
     * 在子類中初始化對(duì)應(yīng)的presenter
     *
     * @return 相應(yīng)的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中。統(tǒng)一退出的時(shí)候注銷觀察
    private CompositeDisposable mCompositeDisposable;

    /**
     * 將Disposable添加
     *
     * @param subscription
     */
    @Override
    public void addDisposable(Disposable subscription) {
        //csb 如果解綁了的話添加 sb 需要新的實(shí)例否則綁定時(shí)無(wú)效的
        if (mCompositeDisposable == null || mCompositeDisposable.isDisposed()) {
            mCompositeDisposable = new CompositeDisposable();
        }
        mCompositeDisposable.add(subscription);
    }

    /**
     * 在界面退出等需要解綁觀察者的情況下調(diào)用此方法統(tǒng)一解綁,防止Rx造成的內(nèi)存泄漏
     */
    @Override
    public void unDisposable() {
        if (mCompositeDisposable != null) {
            mCompositeDisposable.dispose();
        }
    }
    
}

  • 創(chuàng)建activity中泛型傳入相應(yīng)的view接口,presenter中泛型傳入相應(yīng)的presenter接口

  • activity中onCreate中初始化presenter,onDestroy中調(diào)用detach,將presenter中正在執(zhí)行的任務(wù)取消,將view對(duì)象置為空。

  • presenter中通過(guò)構(gòu)造傳遞參數(shù)。將view的實(shí)例傳遞進(jìn)入presenter


4.使用

好的接下來(lái)我們來(lái)使用一下吧
首先我們先來(lái)個(gè)簡(jiǎn)單的需求:

打開(kāi)一個(gè)頁(yè)面請(qǐng)求網(wǎng)絡(luò)獲取數(shù)據(jù),將數(shù)據(jù)顯示在界面上

1.創(chuàng)建Contact管理接口

首先先思考view需要設(shè)置數(shù)據(jù)所有view中需要一個(gè)setData方法
presenter需要去訪問(wèn)網(wǎng)絡(luò)所以需要一個(gè)getData方法。代碼如下:

public interface TestContact {
    interface view extends BaseView {
        /**
         * 設(shè)置數(shù)據(jù)
         *
         * @param dataList
         */
        void setData(List<TestBean.StoriesBean> dataList);
    }

    interface presenter extends BasePresenter {
        /**
         * 獲取數(shù)據(jù)
         */
        void getData();
    }
}

創(chuàng)建Activity和presenter

創(chuàng)建一個(gè)Activity繼承BaseActivity它的泛型對(duì)應(yīng)presenter的接口。實(shí)現(xiàn)對(duì)應(yīng)的view接口
創(chuàng)建一個(gè)TestPresenter繼承BasePresenterImpl,泛型對(duì)應(yīng)view的接口。并實(shí)現(xiàn)對(duì)應(yīng)的presenter接口

代碼如下:

public class TestActivity extends BaseActivity<TestContact.presenter> implements TestContact.view {

    private List<TestBean.StoriesBean> list = new ArrayList<>();//數(shù)據(jù)
    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 對(duì)應(yīng)的presenter
     */
    @Override
    public TestContact.presenter initPresenter() {
        return new TestPresenter(this);
    }

    /**
     * 設(shè)置數(shù)據(jù)
     * 刷新界面
     *
     * @param dataList 數(shù)據(jù)源
     */
    @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);
    }

    /**
     * 獲取數(shù)據(jù)
     */
    @Override
    public void getData() {
        Api.getInstance()
                .test()//測(cè)試接口
                .subscribeOn(Schedulers.io())
                .doOnSubscribe(new Consumer<Disposable>() {
                    @Override
                    public void accept(@NonNull Disposable disposable) throws Exception {
                        addDisposable(disposable);//請(qǐng)求加入管理
                        view.showLoadingDialog("");
                    }
                })
                .map(new Function<TestBean, List<TestBean.StoriesBean>>() {
                    @Override
                    public List<TestBean.StoriesBean> apply(@NonNull TestBean testBean) throws Exception {
                        return testBean.getStories();//轉(zhuǎn)換數(shù)據(jù)
                    }
                })
                .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);
                    }
                });
    }
}

分析

好了相信大部分朋友看了代碼都看懂了,簡(jiǎn)要的分析一下過(guò)程吧

  • 創(chuàng)建對(duì)應(yīng)的類,實(shí)現(xiàn)對(duì)應(yīng)的方法
  • Activity中只有一個(gè)recyclerView初始化它。
  • 在onCreate中調(diào)用presenter中的getData()方法
  • 在presenter中使用RxJava2+retrofit2訪問(wèn)網(wǎng)絡(luò)。獲取數(shù)據(jù)返回給view
  • view拿到數(shù)據(jù)更新界面

結(jié)束語(yǔ)

到這里我們的框架簡(jiǎn)單的搭建好了算是,如果有什么錯(cuò)誤或者問(wèn)題希望朋友們可以提醒下,讓我們互相學(xué)習(xí)。
適合自己的才是最好的。這只是一個(gè)最基本的框架,相信許多朋友或許有更多的各種各樣的需求,所以我這里提供的最簡(jiǎn)單的框,方便修改整理,成為最適合你自己的框架。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容