android的MVP模式

来源:https://www.cnblogs.com/ijaye/archive/2018/03/17/8588481.html
-Advertisement-
Play Games

MVP簡介 相信大家對MVC都是比較熟悉了:M-Model-模型、V-View-視圖、C-Controller-控制器,MVP作為MVC的演化版本,那麼類似的MVP所對應的意義:M-Model-模型、V-View-視圖、P-Presenter-表示器。 從MVC和MVP兩者結合來看,Controll ...


MVP簡介

相信大家對MVC都是比較熟悉了:M-Model-模型、V-View-視圖、C-Controller-控制器,MVP作為MVC的演化版本,那麼類似的MVP所對應的意義:M-Model-模型、V-View-視圖、P-Presenter-表示器。 從MVC和MVP兩者結合來看,Controlller/Presenter在MVC/MVP中都起著邏輯控制處理的角色,起著控制各業務流程的作用。而 MVP與MVC最不同的一點是M與V是不直接關聯的也是就Model與View不存在直接關係,這兩者之間間隔著的是Presenter層,其負責調控 View與Model之間的間接交互,MVP的結構圖如下所示,對於這個圖理解即可而不必限於其中的條條框框,畢竟在不同的場景下多少會有些出入的。在 Android中很重要的一點就是對UI的操作基本上需要非同步進行也就是在MainThread中才能操作UI,所以對View與Model的切斷分離是 合理的。此外Presenter與View、Model的交互使用介面定義交互操作可以進一步達到松耦合也可以通過介面更加方便地進行單元測試。

  MVP結構圖

關於android的MVP模式其實一直沒有一個統一的實現方式,不同的人由於個人理解的不同,進而產生了很多不同的實現方式,其實很難去說哪一種更好,哪一種不好,針對不同的場合,不同的實現方式都有各自的優缺點。

而我採用的MVP是Google提出的一種MVP實現方式,個人認為這種方式實現簡單,更加適合android項目。傳統的MVP中Model起著處理具體邏輯的功能,Presenter起著隔離和解耦的作用,而在Google的MVP中弱化了Model,Model的邏輯由Presenter來實現,spManager、dbManager可以看做是Model,由Fragment實現View實現視圖的變化,Activity作為一個全局的控制者,負責創建view以及presenter實例,並將二者聯繫起來。

實現步驟

1.BasePresenter

public interface BasePresenter {
    void start();
}

2.BaseView

public interface BaseView<P extends BasePresenter> {
    void setPresenter(P presenter);
}

兩個介面分別作為Presenter和View的基類,僅定義了最基本的方法,具體頁面的view和presenter則分別定義繼承的介面,添加屬於自己頁面的方法。

3.Contract 契約類

這是Google MVP與其他實現方式的不同之一,契約類用於定義同一個界面的view和presenter的介面,通過規範的方法命名或註釋,可以清晰的看到整個頁面的邏輯。

public interface SampleContract {

    interface Presenter extends BasePresenter {
        //獲取數據
        void getData(App app, int userId);
        //檢查數據是否有效
        void checkData();
        //刪除消息
        void deleteMsg(App app, int msgId);
        ...
    }

    interface View extends BaseView<Presenter> {
        //顯示載入中
        void showLoading();
        //刷新界面
        void refreshUI(MessageListEntity.CategoryData data);
        //顯示錯誤界面
        void showError();
        ...
    }
}

4.具體的Impl類

Fragment實現View介面,這裡使用Google推薦的創建Fragment實例的方法newInstance(),將fragment必備的參數傳入。

public class SampleFragment extends BaseFragment implements SampleContract.View{

    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";
    
    private String mParam1;
    private String mParam2;

    private SampleContract.Presenter mPresenter;

    
    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @param param1 Parameter 1.
     * @param param2 Parameter 2.
     * @return A new instance of fragment SampleFragment.
     */
    // TODO: Rename and change types and number of parameters
    public static SampleFragment newInstance(String param1, String param2) {
        SampleFragment fragment = new SampleFragment();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, param1);
        args.putString(ARG_PARAM2, param2);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mParam1 = getArguments().getString(ARG_PARAM1);
            mParam2 = getArguments().getString(ARG_PARAM2);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        TextView textView = new TextView(getActivity());
        textView.setText(R.string.hello_blank_fragment);
        return textView;
    }

    @Override
    public void setPresenter(SampleContract.Presenter presenter) {
        mPresenter = presenter;
    }

    @Override
    public void refreshUI(MessageListEntity.CategoryData data) {
        //change UI
    }

    @Override
    public void showError() {
        //change UI
    }
}

Presenter實現類,提供一個參數為對應View的構造器,持有View的引用,並調用View的setPresenter()方法,讓View也持有Presenter的引用,方便View調用Presenter的方法。

public class SamplePresenterImpl implements SampleContract.Presenter {

    private SampleContract.View mView;

    public SamplePresenterImpl(SampleContract.View mView) {
        this.mView = mView;
        mView.setPresenter(this);
    }

    ...
}

5.最後就是Activity

創建view以及presenter實例,並將二者聯繫起來。

public class SampleActivity extends BaseActivity {

    public static final String FRAGMENT_TAG = "fragment_tag";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_layout_detail);
        init();
    }

    private void init() {
        //初始化view
        FragmentManager fragmentManager = getSupportFragmentManager();
        SampleFragment fragment = (SampleFragment) fragmentManager.findFragmentByTag(FRAGMENT_TAG);
        if (fragment == null) {
            fragment = SampleFragment.newInstance(param1, param2);
            fragmentManager.beginTransaction().add(R.id.fl_container, fragment, FRAGMENT_TAG).commit();
        }

        //初始化presenter
        new SamplePresenterImpl(fragment);
    }
}

下圖是Google官方Demo:todo-mvp模式的架構圖

從結構方面觀察一下MVP模式


  20160510110516376.png

為什麼使用MVP?

文章一開始並沒有說MVP的好處,而是先介紹瞭如何實現MVP,在這裡我會通過分析來體現MVP的好處。

1.解耦

毋庸置疑這是最大的好處,通過上述例子可以看到一個頁面被分成了兩個部分,一個是視圖的邏輯,另一個是業務邏輯。兩者持有的引用都是對方的介面,因此可以隨意地替換實現(頁面大修改),並且單獨修改view或者presenter的邏輯並不會影響另一方(小修改)。

2.代碼清晰

以前的MVC模式,當一個頁面的邏輯足夠複雜,就會出現一個activity上千行的情況,在這樣的一個類中想定位一個bug是十分困難的,有時候自己的敲的代碼看著都像別人的代碼,得重頭捋一捋才能定位,相當耗時。而用MVP模式,一個頁面的邏輯都可以從Contract類中直觀的看到有哪些,找到對應邏輯的方法再進入實現類中找到問題所在,效率上不可同日而語。

3.靈活

好多MVP的實現都是用Activity來實現View,這裡使用Fragment便是出於靈活的考慮,Fragment可以復用,可以替換。面對多變的需求可以更從容的應對,例如:一個頁面原本是一個列表,後來要求這個頁面顯示2個Tab,原來的列表變為其中一個Tab。類似的情況可能很常見,如果是用Activity實現的,可能要修改activity類中的很多代碼,而原本就使用Fragment和MVP的話,僅需添加一個Fragment,修改Activity假如切換Tab的邏輯,而不用修改第一個Fragment的任何邏輯,這更符合OOP的編程思想。

4.簡化

開頭說過,傳統MVP中Model起著處理具體邏輯的功能,Presenter起著隔離和解耦的作用。這種方式實現的MVP中Presenter看上去更像一個代理類,僅僅是不讓View直接訪問Model。雖然這樣做解耦的程度更高,但實際項目中,一個頁面邏輯的修改是非常少的,僅僅在產品修改需求是才會發生,大部分情況下僅僅是修複bug之類的小修改,並需要這樣徹底的解耦。從另一個方面來說,一個頁面的視圖邏輯和業務邏輯本就是一體的。因此,Google的MVP弱化的Model的存在,讓Presenter代替傳統意義上的Model,減少了因使用MVP而劇增的類。這種方式相比不適用MVP僅從1個activity,增加到了1個activity,1個fragment(view),1個presenter,1個contract(如果有傳統意義上的Model,還會有1個Model的介面和1個實現類)。

註意點!

1.一段邏輯到底是放在View還是放在Presenter?其實從定義我們也可以知道View只處理界面的邏輯,任何邏輯判斷都應該在presenter中實現。但在實際過程中很容易出現View處理邏輯的情況,例如:網路請求返回一些數據,需要對數據進行刪選或處理,處理的過程很容易發生在View層,這是需要極力避免的情況,所有不涉及界面的判斷都要放到presenter層。

2.類名、方法名的規範。頁面的契約類都以contract結尾,presenter的實現類都以PresenterImpl結尾。View和Presenter介面中的方法需要見名知意,意義模糊的可以適當添加註釋。規範的命名有助於後續的修改和他人的維護。

3.子線程回調。Presenter中處理子線程任務完成後,一般會回到主線程調用View的方法刷新UI,但如果此時activity已經銷毀,並且沒有取消子線程的任務(例如網路請求),此時去調用View的方法很容易發生空指針,應該在調用之前判斷一下,因此建議view增加一個isActive()方法,用於判斷當前view是否可見:

public interface BaseView<P extends BasePresenter> {
    void setPresenter(P presenter);
    boolean isActive();
}

在fragment中只需這樣實現即可:

    @Override
    public boolean isActive() {
        return isAdded();
    }

除此之外,如果子線程的任務會持續很久,或者由於網路等額外因素導致子線程耗時過久,但此時activity其實已經destroy了,presenter的子線程還在運行則不會被GC,並且presenter持有了fragment(view),導致了記憶體泄露。
解決這個問題可以通過弱應用的方式解決,下是實現方式:

public abstract class BasePresenterImpl<V extends BaseView> implements BasePresenter {
 
    protected WeakReference<V> mView;
 
    public BasePresenterImpl(V view) {
        mView = new WeakReference<V>(view);
        view.setPresenter(this);
    }
 
    protected boolean isViewActive() {
        return mView != null && mView.get().isActive();
    }
 
    public void detachView() {
        if (mView != null) {
            mView.clear();
            mView = null;
        }
    }
 
}

view也可以抽象abstract類。



作者:胡奚冰
鏈接:https://www.jianshu.com/p/bbb3b77d47eb
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 轉載於https://www.cnblogs.com/aiyr/p/6830593.html mysql CONCAT()函數用於將多個字元串連接成一個字元串,是最重要的mysql函數之一,下麵就將為您詳細介紹mysql CONCAT()函數,供您參考 mysql CONCAT(str1,str2, ...
  • 前言 筆者在分類中的Hbase欄目之前已經分享了hbase的安裝以及一些常用的shell命令的使用,這裡不僅僅重新複習一下shell命令,還會介紹hbase的DDL以及DML的相關操作。 一、hbase的shell操作 1.1啟動hbase shell 在hbase的安裝目錄的bin目錄下麵啟動我們 ...
  • 我並不期望成為一個專家級的 DBA,但是,在我優化 MySQL 時,我推崇 80/20 原則,明確說就是通過簡單的調整一些配置,你可以壓榨出高達 80% 的性能提升。尤其是在伺服器資源越來越便宜的當下。 警告 沒有兩個資料庫或者應用程式是完全相同的。這裡假設我們要調整的資料庫是為一個“典型”的 We ...
  • mysql用戶授權、資料庫許可權管理、sql語法詳解 —— NiceCui ...
  • 第1章 Mysql體繫結構和存儲引擎 1.1 定義資料庫和實例 資料庫:database,物理的操作系統文件或其他形式文件類型的集合。當使用NDB存儲引擎時,資料庫文件可能是存放在記憶體中而不是磁碟之上。 實例:instance,Mysql資料庫實例由後臺線程和一個共用記憶體區組成。實例才是真正用於操作 ...
  • 簡要:本系列文章講會對expo進行全面的介紹,本人從2017年6月份接觸expo以來,對expo的研究斷斷續續,一路走來將近10個月,廢話不多說,接下來你看到內容,講全部來與官網 我猜去全部機翻+個人修改補充+demo測試的形式,對expo進行一次大補血!歡迎加入expo興趣學習交流群:597732 ...
  • 一、使用 1、build.gradle引入 如果少了第二行引入,會報錯: 2、新建一個實體類 3、建立Dao類介面 4、建立繼承RoomDatabase的資料庫管理虛類 5、初始化資料庫 6、操作舉例(與RxJava配合使用) 子線程中查詢出所有的用戶(主線程操作資料庫會報錯),用Gson轉換成js ...
  • 最近看到一篇文章推了一個開源項目, "GlobalTimer" 。主要是可以用一個定時器來統一管理多個定時任務,可以針對特定任務進行管理。 一、原理 1.一個公共的timer 2.封裝任務到自定義個Event中,保留任務的執行代碼與數據,時間信息等 3.計算所有任務間隔的最大公約數x,用這個x作為t ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...