優(yōu)雅的使用MVP + RxJava +改造框架

image.png

最近看了不少M(fèi)VP的文章和項(xiàng)目,整合了一個(gè)關(guān)于MVP+retrofit2+RxJava的項(xiàng)目,話不多說(shuō)直接上代碼

前言

首先摘昌,閱讀本篇文章前,建議你先去了解一下MVP這個(gè)設(shè)計(jì)模式高蜂。當(dāng)然聪黎,也可以先看看我前面的文章_ 傳送門(mén)

另外,還用到了RxJava备恤、Retrofit稿饰、Okhttp。如果你已經(jīng)了解了他們的基本用法露泊,請(qǐng)直接忽略這段喉镰,接著往下看~

不想看長(zhǎng)長(zhǎng)的文章的,可以直接看代碼惭笑。代碼地址已經(jīng)上傳到Github,https://github.com/jjxs/MvpPackage

工程結(jié)構(gòu)

按照慣例侣姆,先看看工程的主要結(jié)構(gòu):

image.png

簡(jiǎn)單說(shuō)一下幾個(gè)主要包下的功能。首先是api包沉噩,這是存放對(duì)Retrofit進(jìn)行包裝的類(lèi)捺宗。Base包當(dāng)然是放各種Base類(lèi)啦~ mvp包是將契約類(lèi)Contract、Model的實(shí)現(xiàn)類(lèi)和Presenter的實(shí)現(xiàn)類(lèi)放一起川蒙,方便管理蚜厉。其實(shí)你也可以按功能分包,個(gè)人喜好吧畜眨。ui包放一些界面的類(lèi)昼牛,如Activity和Fragment。

下面正式開(kāi)始~

契約類(lèi)

同樣也是從Contract契約類(lèi)開(kāi)始說(shuō)起:

public interface MainContract {

    interface View extends BaseView {

        void showDialog();

        void onSucceed(Gank data);

        void onFail(String err);

        void hideDialog();

    }

    interface Model extends BaseModel {
        Observable<Gank> getGank();
    }

    abstract class Presenter extends BasePresenter<View, Model> {
        public abstract void getGank();
    }
}

我們可以看到胶果,整體上和Google的Demo差不多匾嘱,都是把View和Presenter放到Contract類(lèi)里面統(tǒng)一管理,我這里多加了個(gè)Model接口早抠,我不推薦在Presenter進(jìn)行Model操作霎烙,本來(lái)很優(yōu)雅的一件事,在Presenter進(jìn)行Model操作的話蕊连,感覺(jué)就差了很多悬垃,要做一個(gè)優(yōu)雅的程序員。不同的地方是Model和View接口繼承了BaseModel接口和BaseView接口甘苍,Presenter變成了一個(gè)抽象類(lèi)尝蠕,繼承于BasePresenter抽象類(lèi),傳入兩個(gè)泛型View载庭、Model看彼。為啥呢廊佩?我們接著看Base包下的三個(gè)Base類(lèi)。

Base類(lèi)

BaseView:

public interface BaseView {
}

BaseModel;

public interface BaseModel {
}

BasePresenter:

public class BasePresenter<V extends BaseView,M extends BaseModel> {
    protected V mView;
    protected M mModel;

    private CompositeSubscription mCompositeSubscription;

    protected void addSubscribe(Subscription subscription) {
        if (mCompositeSubscription == null) {
            mCompositeSubscription = new CompositeSubscription();
        }
        mCompositeSubscription.add(subscription);
    }

    public void unSubscribe() {
        if (mView != null) {
            mView = null;
        }
        if (mCompositeSubscription != null && mCompositeSubscription.hasSubscriptions()) {
            mCompositeSubscription.clear();
        }
    }

BaseModel和BaseView接口里面是空的靖榕,在這里我只是為了在BasePresenter中提供一個(gè)約束标锄。當(dāng)然,如果你有其它全局的需求茁计,可以在里面添加一些方法料皇。重點(diǎn)是BasePresenter這個(gè)抽象類(lèi),傳入一個(gè)View和Model星压,并將其用protected關(guān)鍵字修飾践剂,這樣,在它的子類(lèi)中就可以直接對(duì)其賦值和使用了娜膘。加入CompositeSubscription變量逊脯,是為了對(duì)RxJava進(jìn)行管理。unSubscribe方法對(duì)View進(jìn)行null賦值和清除Rx的Subscription(訂閱)竣贪,防止內(nèi)存泄漏男窟。

Presnter橋梁:

接下來(lái)看看這個(gè)很重要的類(lèi),作為連接Model和View的橋梁贾富,這里又是怎么做的呢?

public class MainPresenter extends MainContract.Presenter {
    public MainPresenter(MainContract.View view) {
        mView = view;
        mModel = new MainModel();
    }

    @Override
    public void getGank() {

        Subscription subscribe = mModel.getGank()
                .subscribe(new Subscriber<Gank>() {

                    @Override
                    public void onStart() {
                        mView.showDialog();
                    }

                    @Override
                    public void onCompleted() {
                        mView.hideDialog();
                    }

                    @Override
                    public void onError(Throwable e) {
                        mView.onFail(e.getMessage());
                        onCompleted();
                    }

                    @Override
                    public void onNext(Gank gank) {
                        mView.onSucceed(gank);
                    }
                });


        addSubscribe(subscribe);
    }
}

構(gòu)造方法傳進(jìn)一個(gè)View牺六,并且new了一個(gè)Model對(duì)象颤枪,直接賦值給父類(lèi)中的View和Model。然后下面復(fù)寫(xiě)的方法中調(diào)用Model中的方法淑际,再將結(jié)果通過(guò)View中的方法傳出去畏纲,這是很原始的MVP方式。最后addSubscribe添加到訂閱隊(duì)列中春缕。

Model處理數(shù)據(jù)

Model分出來(lái)盗胀,而不在Presenter處理,其實(shí)也是為了簡(jiǎn)潔锄贼,當(dāng)你要處理很多數(shù)據(jù)的時(shí)候票灰,Presenter就會(huì)變得很亂了。

public class MainModel implements MainContract.Model {

    @Override
    public Observable<Gank> getGank() {
        return ApiEngine.getInstance().getApiService()
                .getGank("1")
                .compose(RxSchedulers.<Gank>switchThread());
    }
}

我這里很簡(jiǎn)單宅荤,就獲取ApiService對(duì)象屑迂,然后調(diào)用API。最后compose傳進(jìn)我自己定義的線程切換器:


public class RxSchedulers {

    public static <T> Observable.Transformer<T, T> switchThread() {
        return new Observable.Transformer<T, T>() {
            @Override
            public Observable<T> call(Observable<T> tObservable) {
                return tObservable
                        .subscribeOn(Schedulers.io())
                        .unsubscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread());
            }
        };
    }
}

將網(wǎng)絡(luò)請(qǐng)求的IO線程切換回Android的UI主線程冯键,才能繼續(xù)進(jìn)行在Presenter中的操作惹盼。

BaseActivity中的封裝

在前面我們可以看到BasePresenter中有兩個(gè)方法,一個(gè)是添加訂閱addSubscribe惫确,另一個(gè)是unSubscribe解除訂閱手报。我們只看到了在Presenter中使用了addSubscribe蚯舱,而沒(méi)有看到unSubscribe在哪使用了。因?yàn)橐乐箖?nèi)存泄漏掩蛤,所以當(dāng)然要在和生命周期相關(guān)的地方進(jìn)行釋放資源枉昏,這個(gè)地方只有我們所說(shuō)的View了,也就是Activity和Fragment中盏档。我們先開(kāi)看一下相關(guān)代碼:

public abstract class BaseActivity<P extends BasePresenter> extends AppCompatActivity {

    protected P mPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (onCreatePresenter() != null) {
            mPresenter = onCreatePresenter();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mPresenter != null) {
            mPresenter.unSubscribe();
        }
    }

    protected abstract P onCreatePresenter();
}

通過(guò)泛型確定一個(gè)Presenter的類(lèi)型凶掰,然后使用抽象方法onCreatePresenter對(duì)其進(jìn)行賦值,最后在onDestroy方法中進(jìn)行資源的釋放蜈亩。繼承這個(gè)BaseActivity類(lèi)的Activity懦窘,就不用每次都在onDestroy進(jìn)行同樣的操作啦~達(dá)到簡(jiǎn)潔的目的。同理稚配,F(xiàn)ragment中也是同樣的畅涂,只是在生命周期的onResume和onPause中分別進(jìn)行Presenter的賦值和資源的釋放。這里我就不貼代碼道川,可以上我的Github看午衰。\

Retrofit引擎封裝

/**
 * Created by Fangzheng on 2017/9/21.
 */

public class ApiEngine {
    private volatile static ApiEngine apiEngine;
    private Retrofit retrofit;

    private ApiEngine() {

        //日志攔截器
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

        //緩存
        int size = 1024 * 1024 * 100;
        File cacheFile = new File(App.getContext().getCacheDir(), "OkHttpCache");
        Cache cache = new Cache(cacheFile, size);

        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(12, TimeUnit.SECONDS)
                .writeTimeout(12, TimeUnit.SECONDS)
                .writeTimeout(12, TimeUnit.SECONDS)
                .addNetworkInterceptor(new NetworkInterceptor())
                .addInterceptor(loggingInterceptor)
                .cache(cache)
                .build();

        retrofit = new Retrofit.Builder()
                .baseUrl(ApiService.BASE_URL)
                .client(client)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();
    }

    public static ApiEngine getInstance() {
        if (apiEngine == null) {
            synchronized (ApiEngine.class) {
                if (apiEngine == null) {
                    apiEngine = new ApiEngine();
                }
            }
        }
        return apiEngine;
    }

    public ApiService getApiService() {
        return retrofit.create(ApiService.class);
    }

}

用了單例模式,在構(gòu)造方法中只初始化一次Retrofit和Okhttp冒萄。雙重鎖的方式獲取單例臊岸,然后再根據(jù)需要獲取ApiService,如果你有很多個(gè)不同源的API尊流,那就可以創(chuàng)建多個(gè)getXXXXApiService帅戒。

結(jié)語(yǔ)

看下運(yùn)行效果

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市崖技,隨后出現(xiàn)的幾起案子逻住,更是在濱河造成了極大的恐慌,老刑警劉巖迎献,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瞎访,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡吁恍,警方通過(guò)查閱死者的電腦和手機(jī)扒秸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)践盼,“玉大人鸦采,你說(shuō)我怎么就攤上這事」净茫” “怎么了渔伯?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)肄程。 經(jīng)常有香客問(wèn)我锣吼,道長(zhǎng)选浑,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任玄叠,我火速辦了婚禮古徒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘读恃。我一直安慰自己隧膘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布寺惫。 她就那樣靜靜地躺著疹吃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪西雀。 梳的紋絲不亂的頭發(fā)上萨驶,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音艇肴,去河邊找鬼腔呜。 笑死,一個(gè)胖子當(dāng)著我的面吹牛再悼,可吹牛的內(nèi)容都是我干的核畴。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼冲九,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼膛檀!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起娘侍,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎泳炉,沒(méi)想到半個(gè)月后憾筏,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡花鹅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年氧腰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片刨肃。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡古拴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出真友,到底是詐尸還是另有隱情黄痪,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布盔然,位于F島的核電站桅打,受9級(jí)特大地震影響是嗜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜挺尾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一鹅搪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧遭铺,春花似錦丽柿、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至锰蓬,卻和暖如春幔睬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背芹扭。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工麻顶, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人舱卡。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓辅肾,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親轮锥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子矫钓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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

  • 前言 首先,閱讀本篇文章前舍杜,建議你先去了解一下MVP這個(gè)設(shè)計(jì)模式新娜。當(dāng)然,也可以先看看我前面的文章_ 傳送門(mén) 另外既绩,...
    Fi7z閱讀 3,910評(píng)論 4 37
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,527評(píng)論 25 707
  • 青石板街的邂逅 你一襲青衣概龄,面紗微揚(yáng) 拔劍,銀蛇游動(dòng) 割裂的頸項(xiàng)饲握,你踩著鮮血而過(guò) 虛空顫動(dòng)私杜,幾里外的紫竹林 我翻轉(zhuǎn)...
    蘇格拉風(fēng)掠影閱讀 723評(píng)論 43 30
  • 1、感恩父母給予我生命救欧。 2衰粹、感恩祖輩將我養(yǎng)育。 3笆怠、感恩母親铝耻、祖母為家的巨大付出。 4蹬刷、感恩妻子對(duì)我一直的包容與...
    朱曉軍閱讀 143評(píng)論 0 0
  • Hi田篇,你好哇展姐,暖先生抛寝。請(qǐng)?jiān)试S我這么稱(chēng)呼你,在遇見(jiàn)你之前。 今天是個(gè)特別的日子芝发,5月20號(hào)溜哮,上午9點(diǎn)21分掸屡,并沒(méi)有刻...
    趙無(wú)稽閱讀 274評(píng)論 0 2