Google TODO-MVP詳解

簡(jiǎn)單介紹

在日常的開(kāi)發(fā)當(dāng)中贡定,經(jīng)常會(huì)遇到需求的變動(dòng)沐扳。這個(gè)東西是真的難以避免的莉掂,所以對(duì)于產(chǎn)品的基礎(chǔ)框架就比較重要了皇型。Google大大在I/O大會(huì)上提出來(lái)了Android開(kāi)發(fā)方式是屬于MVP的,即Model+View+Presenter型檀。

另外在GitHub上面放出來(lái)了一個(gè)樣例命浴,供開(kāi)發(fā)者學(xué)習(xí),這里可以看到整個(gè)項(xiàng)目的簡(jiǎn)介贱除。

Google通過(guò)一個(gè)TODO類(lèi)的APP講解了如何使用一些第三方庫(kù),已經(jīng)如何基于他們構(gòu)建自己的APP的框架媳溺。

目前的狀態(tài)

Stable samples

Sample Description
todo?mvp Demonstrates a basic Model?View?Presenter (MVP) architecture and provides a foundation on which the other samples are built. This sample also acts as a reference point for comparing and contrasting the other samples in this project.
todo?mvp?loaders Fetches data using the Loaders API.
todo?databinding Uses the Data Binding Library.
todo?mvp?clean Uses concepts from Clean Architecture.
todo?mvp?dagger Uses Dagger2 to add support for dependency injection.
todo?mvp?contentproviders Based on the todo-mvp-loaders sample, this version fetches data using the Loaders API, and also makes use of content providers.
todo?mvp?rxjava Uses RxJava to implement concurrency, and abstract the data layer.
todo?mvvm?databinding Based on the todo-databinding sample, this version incorporates the Model?View?ViewModel pattern.

Samples in progress

Sample Description
dev?todo?mvp?tablet Adds a master and detail view for tablets.
dev?todo?mvvm?rxjava Based on the todo-rxjava sample, this version incorporates the Model?View?ViewModel pattern.

可以看到上面的一些庫(kù)在Android中都比較常用月幌,這一次我們先從最基礎(chǔ)的todo-mvp來(lái)看。

可以在上面的地址中獲取悬蔽,也可以直接在命令行中采用

git clone https://github.com/googlesamples/android-architecture.git

的方法獲取扯躺。

需要切換到todo-mvp分支中

git checkout todo-mvp

在一般閱讀代碼的時(shí)候,我會(huì)采取新建一個(gè)read-code的分支蝎困,便于自己看到自己的改動(dòng)录语。

git checkout -b read-code

準(zhǔn)備工作已經(jīng)OK,我們來(lái)看看該項(xiàng)目的模塊結(jié)構(gòu)禾乘。

模塊結(jié)構(gòu)

整體來(lái)看Google大法是通過(guò)實(shí)際的業(yè)務(wù)模塊來(lái)劃分包的澎埠,當(dāng)然你要是通過(guò)UI、Util始藕、Data來(lái)劃分也一點(diǎn)沒(méi)有問(wèn)題蒲稳,個(gè)人習(xí)慣而已。如下:

模塊結(jié)構(gòu)
  • BasePresenter & BaseView是所有的P和V的基礎(chǔ)接口伍派,BasePresenter中有一個(gè)start()方法江耀,V可以通過(guò)改方法調(diào)用P中的業(yè)務(wù)邏輯。BaseView中有一個(gè)setPresenter()方法诉植,通過(guò)該方法祥国,在P的構(gòu)造函數(shù)中將V關(guān)聯(lián)起來(lái)。
  • data中的數(shù)據(jù)源分為了遠(yuǎn)程TasksRemoteDataSource和本地TasksLocalDataSource
  • addedittask顧名思義晾腔,這個(gè)模塊有兩種狀態(tài)(添加和修改)在這個(gè)包里分了四塊
    • AddEditTaskContract即合約類(lèi)舌稀,在其中有兩個(gè)內(nèi)部接口,分別定義了V所要控制的UI邏輯和P所控制的業(yè)務(wù)邏輯灼擂。
    • AddEditTaskPresenter則為實(shí)現(xiàn)了Contract內(nèi)部接口的P扩借,在其中有業(yè)務(wù)的邏輯。
    • AddEditTaskFragment則為實(shí)現(xiàn)了Contract內(nèi)部接口的V缤至,
      在其中有UI的部分操作潮罪。
    • 肯定有人就會(huì)有疑問(wèn)了康谆,為何上面MVP都已經(jīng)有了,那AddEditTaskActivity的作用又是什么呢嫉到?我所理解的Activity應(yīng)該屬于一個(gè)容器沃暗,在這個(gè)容器中做了一部分簡(jiǎn)單的初始化UI操作,如控制ToolBar的樣式何恶、加載Fragment等孽锥。

代碼分析

AddEditTaskActivity分析

先來(lái)看看AddEditTaskActivity,顧名思義有「添加任務(wù)」和「編輯任務(wù)」的功能细层。這個(gè)Activity不是我們所認(rèn)為的標(biāo)準(zhǔn)意義上的View類(lèi)惜辑,上面提到了,它所做的工作疫赎。

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.addtask_act);

        // Set up the toolbar.
      ...
       // Add Fragment  
      ...
        // Create the presenter
        mAddEditTaskPresenter = new AddEditTaskPresenter(
                taskId,
                Injection.provideTasksRepository(getApplicationContext()),
                addEditTaskFragment,
                shouldLoadDataFromRepo);
    }

這個(gè)Activity處理了部分UI初始化工作盛撑,同時(shí),創(chuàng)建了Presenter和載入了Fragment捧搞,即通過(guò)構(gòu)造函數(shù)將V和P做了聯(lián)系抵卫。

這里需要注意的是,在構(gòu)造函數(shù)的第二個(gè)參數(shù)tasksPepository是通過(guò)Injection注入的胎撇。很多新手看到這個(gè)介粘,就會(huì)有疑問(wèn),怎么找不到這個(gè)類(lèi)晚树。其實(shí)是這樣的姻采,在文件目錄中,我們看到了和app同級(jí)的目錄爵憎,存在mock和prod偎谁。其次在app.gradle里面設(shè)置了productFlavors來(lái)控制發(fā)行的版本。因此我們?nèi)ock文件夾下就能找到Injection類(lèi)了纲堵。

public class Injection {

    public static TasksRepository provideTasksRepository(@NonNull Context context) {
        checkNotNull(context);
        return TasksRepository.getInstance(FakeTasksRemoteDataSource.getInstance(),
                TasksLocalDataSource.getInstance(context));
    }
}

這里通過(guò)注入巡雨,寫(xiě)入了一個(gè)假的遠(yuǎn)程Task數(shù)據(jù)源,用來(lái)做測(cè)試席函。

AddEditTaskContract分析

這是一個(gè)合約類(lèi)铐望,內(nèi)部有兩個(gè)內(nèi)部接口,分別繼承了V和P的基類(lèi)茂附,同時(shí)拓展了部分方法正蛙。

public interface AddEditTaskContract {
    //View中實(shí)現(xiàn)了UI的調(diào)用邏輯
    interface View extends BaseView<Presenter> {

        void showEmptyTaskError();
        ...
    }

    interface Presenter extends BasePresenter {
        //Presenter中實(shí)現(xiàn)了業(yè)務(wù)邏輯
        void saveTask(String title, String description);
        ... 
      }
}

AddEditTaskFragment分析

這個(gè)Fragment實(shí)現(xiàn)了合約類(lèi)中的View,所以通過(guò)覆寫(xiě)B(tài)aseView中的setPresenter()营曼,將Presenter和View關(guān)聯(lián)起來(lái)乒验。

public void setPresenter(@NonNull AddEditTaskContract.Presenter presenter) {
        mPresenter = checkNotNull(presenter);
    }

其次覆寫(xiě)了合約類(lèi)中的UI邏輯,在此我就不詳說(shuō)了蒂阱,比較簡(jiǎn)單锻全,大家可以自己看看狂塘。

AddEditTaskPresenter分析

在這個(gè)Presenter中,通過(guò)構(gòu)造函數(shù)將Presenter和View關(guān)聯(lián)在一起鳄厌。構(gòu)造函數(shù)的調(diào)用發(fā)生在上面的Activity中荞胡。

public AddEditTaskPresenter(@Nullable String taskId, @NonNull TasksDataSource tasksRepository,
            @NonNull AddEditTaskContract.View addTaskView, boolean shouldLoadDataFromRepo) {
        mTaskId = taskId;
        mTasksRepository = checkNotNull(tasksRepository);
        mAddTaskView = checkNotNull(addTaskView);
        mIsDataMissing = shouldLoadDataFromRepo;
        //將Presenter和View關(guān)聯(lián)在一起
        mAddTaskView.setPresenter(this);
    }

這里將數(shù)據(jù)源tasksRepository傳入到Presenter的構(gòu)造函數(shù)中,可以使得Presenter在處理業(yè)務(wù)邏輯的時(shí)候了嚎,將數(shù)據(jù)教由給Model層處理泪漂。

這樣的好處是顯而易見(jiàn)的,徹底的將M-V-P分離開(kāi)來(lái)歪泳,實(shí)現(xiàn)了解耦萝勤,也讓整個(gè)程序的框架變得清楚明了起來(lái)。

TasksRepository分析

TasksRepository繼承自TaskDataSource呐伞,TaskDataSource這個(gè)類(lèi)中定義了兩個(gè)回調(diào)接口敌卓,以及部分實(shí)現(xiàn)方法。

interface LoadTasksCallback {

        void onTasksLoaded(List<Task> tasks);

        void onDataNotAvailable();
    }

    interface GetTaskCallback {

        void onTaskLoaded(Task task);

        void onDataNotAvailable();
    }

而TaskRepository中首先定義了兩個(gè)數(shù)據(jù)源荸哟,一個(gè)是本地?cái)?shù)據(jù)源mTasksLocalDataSource,另一個(gè)是遠(yuǎn)程數(shù)據(jù)源mTasksRemoteDataSoure瞬捕。

下來(lái)我們重點(diǎn)看看獲取任務(wù)的方法getTasks(LoadTasksCallbac callback)

public void getTasks(@NonNull final LoadTasksCallback callback) {
        checkNotNull(callback);

        // Respond immediately with cache if available and not dirty
        if (mCachedTasks != null && !mCacheIsDirty) {
            //TODO: What is Map's function values()
            callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
            return;
        }

        if (mCacheIsDirty) {
            // If the cache is dirty we need to fetch new data from the network.
            getTasksFromRemoteDataSource(callback);
        } else {
            // Query the local storage if available. If not, query the network.
            mTasksLocalDataSource.getTasks(new LoadTasksCallback() {
                @Override
                public void onTasksLoaded(List<Task> tasks) {
                    refreshCache(tasks);
                    callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
                }

                @Override
                public void onDataNotAvailable() {
                    getTasksFromRemoteDataSource(callback);
                }
            });
        }
    }

我們來(lái)看看上面的代碼:

  • 從Cache里面讀并且Cache中數(shù)據(jù)沒(méi)有被污染鞍历,則直接返回
  • 若Cache數(shù)據(jù)已經(jīng)被污染,則獲取遠(yuǎn)程的數(shù)據(jù)源肪虎。否則劣砍,直接從本地?cái)?shù)據(jù)庫(kù)查詢(xún),若查詢(xún)不到扇救,則獲取遠(yuǎn)程數(shù)據(jù)源

以上刑枝,可以看出來(lái),TasksRepository很好的將兩個(gè)數(shù)據(jù)源的調(diào)度策略對(duì)外隱藏了起來(lái)迅腔,使得調(diào)用者不用關(guān)心如何選擇數(shù)據(jù)源調(diào)度装畅。

總結(jié)

再往下面就是一些業(yè)務(wù)實(shí)現(xiàn)邏輯上面的事情了,大家可以自己去看看沧烈。

總的來(lái)說(shuō)掠兄,Google給了我們一個(gè)很好的范例,通過(guò)一個(gè)簡(jiǎn)單的APP锌雀,把MVP結(jié)構(gòu)擺在了我們面前蚂夕。當(dāng)然你可以不認(rèn)可它,而要將Activity去掉腋逆,那也是可以的婿牍。我到覺(jué)得現(xiàn)有的這種結(jié)構(gòu),更加方便的能看出來(lái)MVP各個(gè)角色之間的關(guān)系惩歉。

正如Google在項(xiàng)目介紹中說(shuō)為何使用Fragment的那樣:

Notice also in the following illustration that this version of the app uses fragments, and this is for two reasons:
? The use of both activities and fragments allows for a better separation of concerns which compliments this implementation of MVP. In this version of the app, the Activity is the overall controller which creates and connects views and presenters.
? The use of fragments supports tablet layouts or UI screens with multiple views.

使用activities和framgents使得MVP更好的分離開(kāi)等脂,Activity更多的是相當(dāng)于一個(gè)全局的控制器俏蛮,將Views和Presenters進(jìn)行聯(lián)系起來(lái)。

最后慎菲,讓我們結(jié)合這張圖片來(lái)回顧一下MVP在這個(gè)APP中的應(yīng)用:

  • Activity作為一個(gè)控制類(lèi)嫁蛇,將View和Presenter連接在一起
  • Presenter直接調(diào)用Model層的Repository獲取數(shù)據(jù)
  • Repository中則對(duì)外封裝了數(shù)據(jù)源的獲取邏輯,通過(guò)回調(diào)返回給上層

我的小站歡迎過(guò)來(lái)逛逛露该。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末睬棚,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子解幼,更是在濱河造成了極大的恐慌抑党,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件撵摆,死亡現(xiàn)場(chǎng)離奇詭異底靠,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)特铝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)暑中,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人鲫剿,你說(shuō)我怎么就攤上這事鳄逾。” “怎么了灵莲?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵雕凹,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我政冻,道長(zhǎng)枚抵,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任明场,我火速辦了婚禮汽摹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘苦锨。我一直安慰自己竖慧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布逆屡。 她就那樣靜靜地躺著圾旨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪魏蔗。 梳的紋絲不亂的頭發(fā)上砍的,一...
    開(kāi)封第一講書(shū)人閱讀 51,301評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音莺治,去河邊找鬼廓鞠。 笑死帚稠,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的床佳。 我是一名探鬼主播滋早,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼砌们!你這毒婦竟也來(lái)了杆麸?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤浪感,失蹤者是張志新(化名)和其女友劉穎昔头,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體影兽,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡揭斧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了峻堰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讹开。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖捐名,靈堂內(nèi)的尸體忽然破棺而出旦万,到底是詐尸還是另有隱情,我是刑警寧澤桐筏,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布纸型,位于F島的核電站拇砰,受9級(jí)特大地震影響梅忌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜除破,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一牧氮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瑰枫,春花似錦踱葛、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至盯另,卻和暖如春性含,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鸳惯。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工商蕴, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留叠萍,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓绪商,卻偏偏與公主長(zhǎng)得像苛谷,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子格郁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,108評(píng)論 25 707
  • 轉(zhuǎn)載至:http://www.reibang.com/p/9a6845b26856 “Android MVP 詳解...
    SnowDragonYY閱讀 10,322評(píng)論 5 241
  • 作者:李旺成 時(shí)間:2016年4月3日 “Android MVP 詳解(下)”已經(jīng)發(fā)布腹殿,歡迎大家提建議。 MVP ...
    diygreen閱讀 128,869評(píng)論 86 1,321
  • Android App的設(shè)計(jì)架構(gòu):MVC,MVP,MVVM與架構(gòu)經(jīng)驗(yàn)談1. 架構(gòu)設(shè)計(jì)的目的1.1 通過(guò)設(shè)計(jì)使程序模...
    天空在微笑閱讀 4,147評(píng)論 1 20
  • 事件:老公生氣了理张! 感受:身體緊縮赫蛇,害怕 限制性信念:肯定是我又做錯(cuò)事情惹他生氣了!他生氣了雾叭,又要發(fā)火了悟耘!我不想吵...
    竺子閱讀 206評(píng)論 0 0