溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

android MVP示例代碼分析

發(fā)布時間:2022-03-30 10:40:45 來源:億速云 閱讀:270 作者:iii 欄目:移動開發(fā)

這篇文章主要講解了“android MVP示例代碼分析”,文中的講解內(nèi)容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“android MVP示例代碼分析”吧!

上項目結(jié)構(gòu)圖:

android MVP示例代碼分析

從包名上很容易分辨出功能:addedittask是添加任務(wù),data是數(shù)據(jù)管理,statistics是統(tǒng)計,taskdetail是任務(wù)詳情,tasks是任務(wù)瀏覽之類的。事實上這個項目的關(guān)鍵也就是:  Tasks 、 TaskDetail 、 AddEditTask 、 Statistics 。

這四個關(guān)鍵的地方都有相同之處:

  • 定義了view和presenter的契約

  • Activity負責fragment和presenter的創(chuàng)建

  • Fragment實現(xiàn)了view接口

  • presenter實現(xiàn)了presenter接口

也就是說,幾個功能每一個都是MVP的模式,只不過Model層是公用的。而且這個項目里View層都是Fragment,果然google推薦用Fragment自己的項目里也給我們做個示范……其實關(guān)于到底是不是要用Fragment,還是有些爭議的,那么到底要不要用呢?我覺得對于個體而言,不管你喜不喜歡,都要用一用,試一試,因為人要成長,必須踩坑。對于正式項目而言,則需要綜合考量,使用Fragment的利是否大于弊。

扯遠了,接下來看一下他代碼倉庫給的一張結(jié)構(gòu)圖:

android MVP示例代碼分析

可以看出來左邊是數(shù)據(jù)管理,典型的Model層。而右邊呢,你可能認為Activity是Presenter,事實上并不是,Presenter在Activity內(nèi),F(xiàn)ragment是View無疑。到這,我覺得關(guān)于這個項目結(jié)構(gòu)的簡介已經(jīng)足夠了,接下來看代碼。

我覺得看一個Android項目的正確姿勢應(yīng)該是先把玩一下app,看一下功能。貼幾張app的圖:

android MVP示例代碼分析

android MVP示例代碼分析

android MVP示例代碼分析

android MVP示例代碼分析

接著就該上入口的Activity看一下了,這個項目的入口Activity是TasksActivity,所在的包是tasks,看一下有哪些東西:

android MVP示例代碼分析

***個是自定義View,第二個就是入口Activity了,第三個即上面所說的“契約”,里面包含了View接口和Presenter接口。TasksFilterType則是一個枚舉,里面有三個過濾類型:所有,進行中的,完成的。TasksFragment就是MVP中的View了,TasksPresenter則是MVP中的Presenter了??匆幌耇asksActivity中的初始化代碼:

  protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.tasks_act);         Log.e(getClass().getSimpleName(),"onCreate");          // Set up the toolbar.         Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);         setSupportActionBar(toolbar);         ActionBar ab = getSupportActionBar();         ab.setHomeAsUpIndicator(R.drawable.ic_menu);         ab.setDisplayHomeAsUpEnabled(true);          /**          * 以下的DrawerLayout暫時不看了          */         // Set up the navigation drawer.         mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);         mDrawerLayout.setStatusBarBackground(R.color.colorPrimaryDark);         NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);         if (navigationView != null) {             setupDrawerContent(navigationView);         }          // 獲取fragment并將之添加到視圖上         // 懸浮按鈕在這個taksFragment里設(shè)置的點擊事件         TasksFragment tasksFragment =                 (TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame); //        getSupportFragmentManager().findFragmentById()         if (tasksFragment == null) {             // Create the fragment             tasksFragment = TasksFragment.newInstance();             // 提供方法幫助activity加載ui             // 這個方法其實就是拿到一個事務(wù),然后把這個fragment add到對應(yīng)的id上了             ActivityUtils.addFragmentToActivity(                     getSupportFragmentManager(), tasksFragment, R.id.contentFrame);         }          // Create the presenter         mTasksPresenter = new TasksPresenter(                 Injection.provideTasksRepository(getApplicationContext()), tasksFragment);          // Load previously saved state, if available.         if (savedInstanceState != null) {             TasksFilterType currentFiltering =                     (TasksFilterType) savedInstanceState.getSerializable(CURRENT_FILTERING_KEY);             mTasksPresenter.setFiltering(currentFiltering);         }     }

首先是初始化toolbar和側(cè)滑,這里不必深入細節(jié),可以跳過這倆。之后初始化fragment和presenter,初始化Fragment先是嘗試通過id尋找可能已經(jīng)存在的Fragment對象,如果沒有,則重新創(chuàng)建一個Fragment對象。下一步則是創(chuàng)建一個presenter,***則是讓應(yīng)用在橫豎屏狀態(tài)切換的情況下恢復數(shù)據(jù)。

接下來看一下View和Presenter的“契約”:

public interface TasksContract {      interface View extends BaseView<Presenter> {          void setLoadingIndicator(boolean active);          void showTasks(List<Task> tasks);          void showAddTask();          void showTaskDetailsUi(String taskId);          void showTaskMarkedComplete();          void showTaskMarkedActive();          void showCompletedTasksCleared();          void showLoadingTasksError();          void showNoTasks();          void showActiveFilterLabel();          void showCompletedFilterLabel();          void showAllFilterLabel();          void showNoActiveTasks();          void showNoCompletedTasks();          void showSuccessfullySavedMessage();          boolean isActive();          void showFilteringPopUpMenu();     }      interface Presenter extends BasePresenter {          void result(int requestCode, int resultCode);          void loadTasks(boolean forceUpdate);          void addNewTask();          void openTaskDetails(@NonNull Task requestedTask);          void completeTask(@NonNull Task completedTask);          void activateTask(@NonNull Task activeTask);          void clearCompletedTasks();          void setFiltering(TasksFilterType requestType);          TasksFilterType getFiltering();     } }

這個接口里包含了View和Presenter,可以看到View和Presenter里的方法比較多,事實上這是應(yīng)該的。因為在MVP架構(gòu)里,View只負責根據(jù)Presenter的指示繪制UI,View將所有的用戶交互交給Presenter處理。所以Presenter的很多方法可能就是對用戶的輸入的處理,而有輸入必然有輸出,View接口定義的各個方法便是給Presenter回調(diào)的。Presenter通過回調(diào)函數(shù)將對用戶的輸入的處理結(jié)果推到View中,View再根據(jù)這個結(jié)果對UI進行相應(yīng)的更新。而在此項目中,F(xiàn)ragment就是View,在Fragment的各個點擊事件中都調(diào)用了Presenter的對應(yīng)方法,將業(yè)務(wù)邏輯交給Presenter處理。這看起來比傳統(tǒng)的MVC強上很多,因為傳統(tǒng)MVC中Activity既可以認為是Controller亦可以認為是View,職責難以分離,寫到后面可能一個Activity就有上千行的代碼,這會為后續(xù)的維護帶來不少麻煩。而MVP則將業(yè)務(wù)邏輯抽取到了Presenter中,作為View的Fragment或者Activity職責更加單一,無疑為后續(xù)的開發(fā)維護帶來了便利。

接下來詳細的看Presenter的初始化,Presenter的創(chuàng)建是在TasksActivity中完成的,查看其構(gòu)造函數(shù):

public TasksPresenter(@NonNull TasksRepository tasksRepository, @NonNull TasksContract.View tasksView) {       mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null");       mTasksView = checkNotNull(tasksView, "tasksView cannot be null!");        mTasksView.setPresenter(this);   }

前兩個檢查傳入的參數(shù)是否為空,接著將其賦值給TasksPresenter內(nèi)的引用,調(diào)用view的setPresenter方法,將自身傳入,這樣view中就可以使用presenter對象了,比直接從activity中拿看起來要優(yōu)雅了不少。Presenter具體的邏輯就不看了,都是一些比較簡單的代碼,回顧一下打開這個app所發(fā)生的事件的流程:創(chuàng)建TasksActivity  -> 初始化Toolbar -> 初始化側(cè)滑 -> 創(chuàng)建TasksFragment對象 -> 創(chuàng)建TaskPresenter對象  -> 給Fragment設(shè)置Presenter對象 ->  初始化Fragment布局,這樣一套流程下來,整個流程就理清了,接下來只是等待用戶的輸入了。

接下來要看的是從本文開始到現(xiàn)在都一直忽略了的Model:TasksRepository。不過在分析TasksRepository之前,安利一下這個項目里的實體類,寫的比較優(yōu)雅,我們平時寫實體類時***也能按照他的套路來寫。我為什么說他寫的比較優(yōu)雅呢?因為各個屬性或者是帶返回值的方法都打上了@Nullable或者@NoNull注解來說明是否可以為空,事實上空指針這個錯可以算是平時經(jīng)常遇到的錯了&hellip;&hellip;不過如果你有良好的設(shè)計和編碼習慣,是可以避免的,帶上這兩個注解可以在編譯期給你相關(guān)的提示。不僅如此,這個實體類還復寫了equals()、hashCode()和toString()方法,而且實現(xiàn)的方式也符合規(guī)范,關(guān)于如何復寫這三個方法,在《effective  java》上有很好的總結(jié),各位可以去讀一下。

/*  * Copyright 2016, The Android Open Source Project  *  * Licensed under the Apache License, Version 2.0 (the "License");  * you may not use this file except in compliance with the License.  * You may obtain a copy of the License at  *  *      http://www.apache.org/licenses/LICENSE-2.0  *  * Unless required by applicable law or agreed to in writing, software  * distributed under the License is distributed on an "AS IS" BASIS,  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  * See the License for the specific language governing permissions and  * limitations under the License.  */  package com.example.android.architecture.blueprints.todoapp.data;  import android.support.annotation.NonNull; import android.support.annotation.Nullable;  import com.google.common.base.Objects; import com.google.common.base.Strings;  import java.util.UUID;  /**  * Immutable model class for a Task.  */ public final class Task {      @NonNull     private final String mId;      @Nullable     private final String mTitle;      @Nullable     private final String mDescription;      private final boolean mCompleted;      /**      * Use this constructor to create a new active Task.      *      * @param title       title of the task      * @param description description of the task      */     public Task(@Nullable String title, @Nullable String description) {         this(title, description, UUID.randomUUID().toString(), false);     }      /**      * Use this constructor to create an active Task if the Task already has an id (copy of another      * Task).      *      * @param title       title of the task      * @param description description of the task      * @param id          id of the task      */     public Task(@Nullable String title, @Nullable String description, @NonNull String id) {         this(title, description, id, false);     }      /**      * Use this constructor to create a new completed Task.      *      * @param title       title of the task      * @param description description of the task      * @param completed   true if the task is completed, false if it's active      */     public Task(@Nullable String title, @Nullable String description, boolean completed) {         this(title, description, UUID.randomUUID().toString(), completed);     }      /**      * Use this constructor to specify a completed Task if the Task already has an id (copy of      * another Task).      *      * @param title       title of the task      * @param description description of the task      * @param id          id of the task      * @param completed   true if the task is completed, false if it's active      */     public Task(@Nullable String title, @Nullable String description,                 @NonNull String id, boolean completed) {         mId = id;         mTitle = title;         mDescription = description;         mCompleted = completed;     }      @NonNull     public String getId() {         return mId;     }      @Nullable     public String getTitle() {         return mTitle;     }      @Nullable     public String getTitleForList() {         if (!Strings.isNullOrEmpty(mTitle)) {             return mTitle;         } else {             return mDescription;         }     }      @Nullable     public String getDescription() {         return mDescription;     }      public boolean isCompleted() {         return mCompleted;     }      public boolean isActive() {         return !mCompleted;     }      public boolean isEmpty() {         return Strings.isNullOrEmpty(mTitle) &amp;&amp;                Strings.isNullOrEmpty(mDescription);     }      @Override     public boolean equals(Object o) {         if (this == o) return true;         if (o == null || getClass() != o.getClass()) return false;         Task task = (Task) o;         return Objects.equal(mId, task.mId) &amp;&amp;                Objects.equal(mTitle, task.mTitle) &amp;&amp;                Objects.equal(mDescription, task.mDescription);     }      @Override     public int hashCode() {         return Objects.hashCode(mId, mTitle, mDescription);     }      @Override     public String toString() {         return "Task with title " + mTitle;     } }

先看一下TasksRepository所在的包的結(jié)構(gòu):

android MVP示例代碼分析

可以從包名上看出local是從本地讀取數(shù)據(jù),remote是遠程讀取,當然了,這里只是模擬遠程讀取。本地采用了數(shù)據(jù)庫存取的方式。在TasksRepository(下文簡稱TR)內(nèi)部有兩個TasksDataSource的引用:

private final TasksDataSource mTasksRemoteDataSource;   private final TasksDataSource mTasksLocalDataSource;

TasksDataSource是data包內(nèi)的一個接口,使用接口引用,無非是想解耦,就算以后需求變更,不想采用數(shù)據(jù)庫的方式存儲數(shù)據(jù),只要實現(xiàn)了這個接口,TR內(nèi)部的代碼也無需變更。TR用了單例,實現(xiàn)方式并不是線程安全的:

/**     * Returns the single instance of this class, creating it if necessary.     *     * @param tasksRemoteDataSource the backend data source     * @param tasksLocalDataSource  the device storage data source     * @return the {@link TasksRepository} instance     */    public static TasksRepository getInstance(TasksDataSource tasksRemoteDataSource,                                              TasksDataSource tasksLocalDataSource) {        if (INSTANCE == null) {            INSTANCE = new TasksRepository(tasksRemoteDataSource, tasksLocalDataSource);        }        return INSTANCE;    }

說到底,他根本沒有線程安全的必要,至少在這個app里,沒有并發(fā)創(chuàng)建這個對象的場景,所以夠用就行了。在TR內(nèi)部使用了一個LinkedHashMap作為容器來保存Tasks,主要看一下兩個方法,首先是存儲:

public void saveTask(@NonNull Task task) {        checkNotNull(task);        mTasksRemoteDataSource.saveTask(task);        mTasksLocalDataSource.saveTask(task);         // Do in memory cache update to keep the app UI up to date        if (mCachedTasks == null) {            mCachedTasks = new LinkedHashMap<>();        }        mCachedTasks.put(task.getId(), task);    }

會將傳入的task存儲到遠程數(shù)據(jù)源和本地數(shù)據(jù)源(本地數(shù)據(jù)庫)中,然后將這個task傳到mCachedTasks(LinkedHashMap)中。代碼比較簡單,不做更多的分析,接下來看一下讀取Task:

public void getTasks(@NonNull final LoadTasksCallback callback) {        checkNotNull(callback);         // Respond immediately with cache if available and not dirty        if (mCachedTasks != null &amp;&amp; !mCacheIsDirty) {            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);                }            });        }    }

這個taskId是需要獲取Task的id,也是唯一標識,GetTaskCallback則是負責傳遞數(shù)據(jù)的接口回調(diào)。首先是從內(nèi)存中讀取數(shù)據(jù),getTaskWithId方法就是,看一下代碼:

private Task getTaskWithId(@NonNull String id) {        checkNotNull(id);        if (mCachedTasks == null || mCachedTasks.isEmpty()) {            return null;        } else {            return mCachedTasks.get(id);        }    }

就從保存task的LinkedHashMap中讀取數(shù)據(jù)。如果這個過程讀取不到數(shù)據(jù)那么接著從本地數(shù)據(jù)源中讀取數(shù)據(jù),如果本地數(shù)據(jù)源也沒有拿到這個數(shù)據(jù),那么最終就從遠程數(shù)據(jù)源中讀取數(shù)據(jù)。

感謝各位的閱讀,以上就是“android MVP示例代碼分析”的內(nèi)容了,經(jīng)過本文的學習后,相信大家對android MVP示例代碼分析這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI