1. 程式人生 > >MVC、MVP、MVVM,談談我對Android應用架構的理解

MVC、MVP、MVVM,談談我對Android應用架構的理解

640?wx_fmt=png&wxfrom=5&wx_lazy=1

今日科技快訊

近日,美國國務院公佈了一條新規,要求外國人在申請美國簽證時提供過去5年在指定社交媒體平臺上使用的使用者名稱。據美國《紐約時報》報道,新規針對的指定社交媒體平臺有20家,其中大部分設在美國,包括臉書、推特、領英、優兔等。此外,新規還涉及設在中國的豆瓣、QQ、新浪微博、騰訊微博和優酷視訊以及俄羅斯社交媒體平臺VK、比利時交友平臺Twoo和拉脫維亞社交問答平臺Ask.fm。

作者簡介

週一早上好,清明節小長假即將來臨咯,但是大家仍然要保持好好學習工作哦。

本篇來自 08_carmelo 的投稿,分享了他對 Android應用架構的理解 ,一起來看看!希望大家喜歡。

08_carmelo 的部落格地址:

https://www.jianshu.com/u/b8dad3885e05

前言

android架構可能是論壇討論最多的話題了,mvc mvp和mvvm不絕於耳,後面又有模組化和外掛化。對此,關於哪種架構更好的爭論從未停止。

我的觀點:脫離實際專案比較這些模式優劣毫無意義,各種模式都有優點和缺點,沒有好壞之分。越高階的架構實現起來越複雜,需要更多的學習成本更多的人力,所以說技術選型關鍵是在你自己專案的特點,團隊的水平,資源的配備,開發時間的限制,這些才是重點!但是不少團隊本末倒置,把mvvm往自己的專案硬套。

下面我從兩大塊講下我理解的Android架構:程式碼層面,主要是MVC和MVP的理解。專案層面,主要是怎麼搭建整個專案,怎麼劃分模組。

正文

先上結論:

  • MVC:Model-View-Controller,經典模式,很容易理解,主要缺點有兩個:

    •  View對Model的依賴,會導致View也包含了業務邏輯;

    • Controller會變得很厚很複雜。

  • MVP:Model-View-Presenter,MVC的一個演變模式,將Controller換成了Presenter,主要為了解決上述第一個缺點,將View和Model解耦,不過第二個缺點依然沒有解決。

  • MVVM:Model-View-ViewModel,是對MVP的一個優化模式,採用了雙向繫結:View的變動,自動反映在ViewModel,反之亦然。

MVC

簡單的說:我們平時寫的Demo都是MVC,controller就是我們的activity,model(資料提供者)就是讀取資料庫,網路請求這些我們一般有專門的類處理,View一般用自定義控制元件。

但這一切,只是看起來很美。

想象實際開發中,我們的activity程式碼其實是越來越多,model和controller根本沒有分離,控制元件也需要關係資料和業務。

640?wx_fmt=png

image.png

所以說,MVC的真實存在是MC(V),Model和Controller根本沒辦法分開,並且資料和View嚴重耦合。這就是它的問題。舉個簡單例子 :獲取天氣資料展示在介面上

640?wx_fmt=png

image.png

  • Model層

public interface WeatherModel { 
    void getWeather(String cityNumber, OnWeatherListener listener); 
} 
................ 
public class WeatherModelImpl implements WeatherModel { 
        @Override 
    public void getWeather(String cityNumber, final OnWeatherListener listener) { 
        /*資料層操作*/ 
        VolleyRequest.newInstance().newGsonRequest(http://www.weather.com.cn/data/sk/ + cityNumber + .html, 
                Weather.class, new Response.Listener<weather>() { 
                    @Override 
                    public void onResponse(Weather weather) { 
                        if (weather != null) { 
                            listener.onSuccess(weather); 
                        } else { 
                            listener.onError(); 
                        } 
                    } 
                }, new Response.ErrorListener() { 
                    @Override 
                    public void onErrorResponse(VolleyError error) { 
                        listener.onError(); 
                    } 
                }); 
    } 
}
  • Controllor(View)層

public class MainActivity extends ActionBarActivity implements OnWeatherListener, View.OnClickListener { 
    private WeatherModel weatherModel; 
    private EditText cityNOInput; 
    private TextView city; 
    ... 
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_main); 
        weatherModel = new WeatherModelImpl(); 
        initView(); 
    } 
    //初始化View 
    private void initView() { 
        cityNOInput = findView(R.id.et_city_no); 
        city = findView(R.id.tv_city); 
        ... 
        findView(R.id.btn_go).setOnClickListener(this); 
    } 
    //顯示結果 
    public void displayResult(Weather weather) { 
        WeatherInfo weatherInfo = weather.getWeatherinfo(); 
        city.setText(weatherInfo.getCity()); 
        ... 
    } 
    @Override 
    public void onClick(View v) { 
        switch (v.getId()) { 
            case R.id.btn_go: 
                weatherModel.getWeather(cityNOInput.getText().toString().trim(), this); 
                break; 
        } 
    } 
    @Override 
    public void onSuccess(Weather weather) { 
        displayResult(weather); 
    } 
    @Override 
    public void onError() { 
        Toast.makeText(this, 獲取天氣資訊失敗, Toast.LENGTH_SHORT).show(); 
    } 
    private T findView(int id) { 
        return (T) findViewById(id); 
    } 
}

簡單分析下這個例子:

  • activity裡面的控制元件必須關心業務和資料,才能知道自己怎麼展示。換句話說,我們很難讓兩個人在不互相溝通的情況下,一人負責獲取資料,一人負責展示UI,然後完成這個功能。

  • 所以的邏輯都在activity裡面。

完美的體現了MVC的兩大缺點,下面看看MVP怎麼解決第一個缺點的

MVP

640?wx_fmt=png

image.png

看上圖可以看出,從MVC中View被拆成了Presenter和View,真正實現了邏輯處理和View的分離。下面寫一個例項:模擬一個登入介面,輸入使用者名稱和密碼,可以登入以及清除密碼

  • Model層

/** 
定義業務介面 
*/ 
public interface IUserBiz {    public void login(String username, String password, OnLoginListener loginListener); } /** 結果回撥介面 */
public interface OnLoginListener {    void loginSuccess(User user);    void loginFailed(); } /** 具體Model的實現 */
public class UserBiz implements IUserBiz {    @Override    public void login(final String username, final String password, final OnLoginListener loginListener)    {        //模擬子執行緒耗時操作        new Thread()        {            @Override            public void run()            {                try                {                    Thread.sleep(2000);                } catch (InterruptedException e)                {                    e.printStackTrace();                }                //模擬登入成功                if ("zhy".equals(username) && "123".equals(password))                {                    User user = new User();                    user.setUsername(username);                    user.setPassword(password);                    loginListener.loginSuccess(user);                } else                {                    loginListener.loginFailed();                }            }        }.start();    } }
  • View

上面說到View層是以介面的形式定義,我們不關心資料,不關心邏輯處理!只關心和使用者的互動,那麼這個登入介面應該有的操作就是(把這個介面想成一個容器,有輸入和輸出)。

獲取使用者名稱,獲取密碼,現實進度條,隱藏進度條,跳轉到其他介面,展示失敗dialog,清除使用者名稱,清除密碼。接下來定義介面:

public interface IUserLoginView  {  
    String getUserName();  
    String getPassword();  
    void clearUserName();  
    void clearPassword();  
    void showLoading();  
    void hideLoading();  
    void toMainActivity(User user);  
    void showFailedError();  
}

然後Activity實現這個這個介面:

public class UserLoginActivity extends ActionBarActivity implements IUserLoginView { 
    private EditText mEtUsername, mEtPassword; 
    private Button mBtnLogin, mBtnClear; 
    private ProgressBar mPbLoading; 
    private UserLoginPresenter mUserLoginPresenter = new UserLoginPresenter(this); 
    @Override 
    protected void onCreate(Bundle savedInstanceState) 
    { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_user_login); 
        initViews(); 
    } 
    private void initViews() 
    { 
        mEtUsername = (EditText) findViewById(R.id.id_et_username); 
        mEtPassword = (EditText) findViewById(R.id.id_et_password); 
        mBtnClear = (Button) findViewById(R.id.id_btn_clear); 
        mBtnLogin = (Button) findViewById(R.id.id_btn_login); 
        mPbLoading = (ProgressBar) findViewById(R.id.id_pb_loading); 
        mBtnLogin.setOnClickListener(new View.OnClickListener() 
        { 
            @Override 
            public void onClick(View v) 
            { 
                mUserLoginPresenter.login(); 
            } 
        }); 
        mBtnClear.setOnClickListener(new View.OnClickListener() 
        { 
            @Override 
            public void onClick(View v) 
            { 
                mUserLoginPresenter.clear(); 
            } 
        }); 
    } 
    @Override 
    public String getUserName() 
    { 
        return mEtUsername.getText().toString(); 
    } 
    @Override 
    public String getPassword() 
    { 
        return mEtPassword.getText().toString(); 
    } 
    @Override 
    public void clearUserName() 
    { 
        mEtUsername.setText(""); 
    } 
    @Override 
    public void clearPassword() 
    { 
        mEtPassword.setText(""); 
    } 
    @Override 
    public void showLoading() 
    { 
        mPbLoading.setVisibility(View.VISIBLE); 
    } 
    @Override 
    public void hideLoading() 
    { 
        mPbLoading.setVisibility(View.GONE); 
    } 
    @Override 
    public void toMainActivity(User user) 
    { 
        Toast.makeText(this, user.getUsername() + 
                " login success , to MainActivity", Toast.LENGTH_SHORT).show(); 
    } 
    @Override 
    public void showFailedError() 
    { 
        Toast.makeText(this, 
                "login failed", Toast.LENGTH_SHORT).show(); 
    } 
}
  • Presenter

Presenter的作用就是從View層獲取使用者的輸入,傳遞到Model層進行處理,然後回撥給View層,輸出給使用者!

public class UserLoginPresenter { 
    private IUserBiz userBiz; 
    private IUserLoginView userLoginView; 
    private Handler mHandler = new Handler(); 
//Presenter必須要能拿到View和Model的實現類 
    public UserLoginPresenter(IUserLoginView userLoginView) 
    { 
        this.userLoginView = userLoginView; 
        this.userBiz = new UserBiz(); 
    } 
    public void login() 
    { 
        userLoginView.showLoading(); 
        userBiz.login(userLoginView.getUserName(), userLoginView.getPassword(), new OnLoginListener() 
        { 
            @Override 
            public void loginSuccess(final User user) 
            { 
                //需要在UI執行緒執行 
                mHandler.post(new Runnable() 
                { 
                    @Override 
                    public void run() 
                    { 
                        userLoginView.toMainActivity(user); 
                        userLoginView.hideLoading(); 
                    } 
                }); 
            } 
            @Override 
            public void loginFailed() 
            { 
                //需要在UI執行緒執行 
                mHandler.post(new Runnable() 
                { 
                    @Override 
                    public void run() 
                    { 
                        userLoginView.showFailedError(); 
                        userLoginView.hideLoading(); 
                    } 
                }); 
            } 
        }); 
    } 
    public void clear() 
    { 
        userLoginView.clearUserName(); 
        userLoginView.clearPassword(); 
    } 
}

分析下這個例子:

  • 我們有了IUserLoginView 這個介面(協議),activity裡面的控制元件根本不需要關心資料,只要實現這個介面在每個方法中“按部就班”的展示UI就行了。換句話說,我們讓兩個人一起開發這個功能,一人要處理資料並且制定介面(協議),另一人直接用activity實現這個介面,閉著眼睛就可以在每個回撥裡展示UI,合作很愉快。

  • MVP成功解決了MVC的第一個缺點,但是邏輯處理還是雜糅在Activity。

MVC到MVP簡單說,就是增加了一個介面降低一層耦合。那麼,用樣的MVP到MVVM就是再加一個介面唄。實際專案我建議用MVP模式,MVVM還是複雜了對於中小型專案有點過度設計,這裡就不展開講。

模組化

640?wx_fmt=png

image.png

上圖是一個專案常見的架構方式

  • 最底層是基礎庫,放置與業務無關的模組:比如基礎網路請求,圖片壓縮等等,可以按需分為邏輯模組,通用UI模組和第三方庫。(建議採用獨立的svn分支)

  • 中間層是通用業務層,放置公司多個android專案的通用業務模組(和業務相關的),比如登入流程,檔案上傳/下載等。

  • 最上層就是應用層了,比如公司有三個android專案:LbBoss,BV和BVHD。我們還可以針對相似的專案再抽取通用層(比如這裡的BV和BV PAD版,通用層為BVCommon)。

新建一個app,我們往往有兩種模組劃分方法:

  • 按照型別劃分

640?wx_fmt=png

image.png

  • 按照業務劃分

每一個包都是一個業務模組,每個模組下再按照型別來分。

  • 怎麼選

我建議中小型的新專案按照型別比較好,因為開始程式碼量不多按照業務來分不切實際,一個包只放幾個檔案?? 況且前期業務不穩定,等到開發中期業務定型了,再進行重構難度也不大。

上面講的模組劃分既不屬於模組化也不屬於外掛化,僅僅是一個簡單package結構不同而已,app還是一個app並沒有產生什麼變化。通常講的模組化,是指把業務劃分為不同的moduler(型別是library),每個moduler之間都不依賴,app(型別是application)只是一個空殼依賴所有的moduler。

640?wx_fmt=png

image.png

每個紅色箭頭都是一個業務模組,紅色框是我們的app裡面只包含簡單的業務:自定義Application,入口Activity,build.gradle編譯打包配置。看下專案的依賴關係:

640?wx_fmt=png

image.png

這樣架構後,帶來最大的不同就是:不同業務模組完全分離,好處就是不同模組的開發絕對不會互相耦合了,因為你在模組A 根本訪問不到模組B的API。此時模組間通訊急需解決,Intent隱式跳轉可以處理部分Activity的跳轉,但真正的業務場景遠不止兩個介面跳一跳。你之前封裝的業務通用方法,工具類,資料快取現在其他模組都拿不到了,本本來可以複用的控制元件,fragment都不能共享,而這些都是和業務耦合沒辦法拿到底層基礎庫。

模組間通訊

針對上面問題有兩個解決辦法,根據自己專案實際情況,如果專案的前期搭建已經很優秀,有完善的基礎庫,不同模組間的通訊不是很多,可以自己實現。如果專案比較龐大,不同業務間頻繁呼叫建議使用阿里巴巴的開源庫。

自己實現

首先每個moduler有個目錄叫include,裡面有三個類,此處以一個bbs論壇模組為例說明,

  • IBBSNotify:裡面是一堆interface,作用是該模組對外的回撥,只能被動被觸發。

  • IBBService:裡面是一堆interface,作用是對外暴露的方法,讓別的模組來主動調,比如enterBbsActivity 

  • IBBSServiceImpl:很明顯是IBBService的實現,比如enterBbsActivity就是具體怎麼跳轉到論壇介面,傳遞什麼資料。

640?wx_fmt=png

image.png

每個模組方法和回撥都有了,in和out都具備了,別的模組怎麼使用呢?就該app該上場了,app不能只是一個殼裡面要定義一個ModulerManager implements 所有模組的對外interface,作為每個模組的中轉站,A模組告訴ModulerManager我想跳轉到論壇模組,接著ModulerManager呼叫IBBService.enterBbsActivity,IBBSServiceImpl是IBBService的具體實現(多型)然後呼叫IBBSServiceImpl.enterBbsActivity跳轉到BBS介面。

通訊是解決了,其實踩坑才剛剛開始:

  • 這裡的app是我們新建的,那麼之前專案的app模組要降為library:

apply plugin: 'com.android.library'

app的build.gradle配置:

apply plugin: 'com.android.application'

性質發生巨大變化。裡面的自定義application,build.gradle,程式碼混淆配置等全部移到app

  • R.java在Lib型別的moduler中不是final的,所有switch case語句全部替換成if else

  • 一定要再建一個common模組,放置通用資料,快取等

  • 還有很多通用功能,例如分享,推送,儘量剝離業務放到common

  • 其他與專案相關的細節

結語

外掛化其實最後釋出的產品也是一個apk,只不過大小可以控制(可以隨意去掉某些模組),支援使用者動態載入子apk。因此,外掛化就是動態載入apk。有人說我用intent隱式可以直接跳轉到另一個apk啊,幹嘛還要外掛化。

其實是兩碼事,intent只是指定一個Activity跳過去,後面的互動完成不受你控制,2個apk也是執行在獨立的程序資料無法共享。而外掛化可以讓兩個apk執行在一個程序,可以完全像同一個apk一樣開發。不過,我覺得外掛化只適合需要多部門並行開發的那種,比如支付寶這種超級app,一般的app開發除非特殊需要,否則用不到。

外掛化也有成熟的框架,在此不詳細說了。另外,每個人的習慣不一樣,元件化,模組化在我看來差不多,沒必要糾結兩個名詞。

歡迎長按下圖 -> 識別圖中二維碼

或者 掃一掃 關注我的公眾號

640.png?

640?wx_fmt=jpeg

相關推薦

過去一年談談code-review的理解| 掘金年度徵文

1. what—什麼是CR codereview(CR)一直以來在軟體行業被視為提升程式碼質量的一種有效的方式,也被視為一種工程師文化的代表。關於什麼是CR,在goole出具體的定義如下: 程式碼評審是指在軟體開發過程中,對原始碼的系統性。通常的目的是查詢系統缺陷,保證軟體總體質量和提高開發者自身水

入門量化兩年談談量化投資的理解

比例 高頻 原因 期貨 依據 tps 實施 行為 2015年 雖然我現在已經入門量化投資兩年,但是我還沒有接觸量化投資之前,和大家一樣,只有一種感覺那就是神秘,難度大;但通過學習了量化金融分析師AQF實訓項目之後,慢慢的對量化投資有一定理解之後,量化投資並沒有大家想象的那麽

ag視訊都統一怎麽作假談談ag真人視訊的看法

並不是 數據 自己的 都是 我們 操作 速度 理解 統一 信譽首選【永久網址864968.C○㎡】AG真人視訊網站,首先你要明白ag是什麽,ag其實只做源頭,它沒有自己的網苔,這點一定要清楚,不要被一些詞語給蒙蔽了。如果理解了ag只是源頭的話,那就應該明白是怎麽回事了,作為

撇開程式碼不說談談架構的6個冷思考

計算機是個複雜的機器,相比普通的機器(比如小家電、汽車),它可以在使用過程中對其「工作行為」進行「再定義和場景適配」,以解決不同場景下的人的需求和問題,這種「定義的結果」,對於機器的終端使用者來說,是「應用 / Application」。 對於非計算機相關的普通人而言,即便是有諸多對於職位頭銜的描述

談談Android View事件分發的理解

event 調用 ack 處理 group ans import ras 運行 寫這篇博客的緣由。近期因為項目中用到相似一個LinearLayout中水平布局中,有一個TextView和Button,然後對該LinearLayout布局設置點擊事件。點擊

談談Docker的簡單理解

linux 安全性 看到了 用戶 總結 們的 部分 占用 ont Docker能解決什麽問題呢?一個工具的出現必然需要解決一些問題,Docker也不例外,簡單說說我們常見的2種情況Docker是如何解決的吧。1、程序在我這跑得好好的,在你那怎麽就不行呢?!這是一個典型的應用

談談Spring IOC的理解

反轉 頻率 註解 改變 enc encoding 圖1 1.3 ram 轉自京東開濤大神的微博,這是我看過最好的對IOC DI的解釋. 學習過Spring框架的人一定都會聽過Spring的IoC(控制反轉) 、DI(依賴註入)這兩個概念,對於初學Spring的人來說,總

談談多態的理解

哪些 extends 對象 構造方法 bsp 自己的 ext 調用 根據 舉例: 父類:Person{}   子類:Child extends Person{} 父類的引用指向子類的對象:Person p = new Child(); 理解:在編譯期認為p是父類的對象,在運

談談區塊鏈的理解(基礎篇)

希望 總決賽 意思 效果 理由 合約 又是 安全 價值       最近,區塊鏈技術成為每個人耳渲目染的話題,甚至被一些人認為可以顛覆整個社會,可能是由於比特幣的大漲,也有可能是因為國家政策的出行,各個國家都在為區塊鏈招兵買馬

談談多型的理解

什麼是多型 多型是面向物件的特性之一,從字面上理解,多型就是指一個物件在執行是會有多種形態,就比如可以說student是一個person,也可以說student是一個object。 我們來看一個經典的例子: Service service=new ServiceImpl(); 我們經

敏捷開發實踐(一)--談談敏捷開發的理解

隨著敏捷開發越來越流行,人人都在談敏捷,人人也都在學習scrum等敏捷開發方法。。。當然,自己也是敏捷開發的實施者和受益者。 背景 我們公司引入敏捷開發的時間並不長,在實施敏捷的過程還存在一些問題,自己在實施敏捷的過程也存在很多的疑惑(畢竟原來沒有學過,

10007---敏捷開發實踐(一)--談談敏捷開發的理解

原文 隨著敏捷開發越來越流行,人人都在談敏捷,人人也都在學習scrum等敏捷開發方法。。。當然,自己也是敏捷開發的實施者和受益者。 背景 我們公司引入敏捷開發的時間並不長,在實施敏捷的過程還存在一些問題,自己在實施敏捷的過程也存在很多的疑惑(畢竟原來沒有學過,和真實

談談微服務的理解

微服務是一個近些年說的非常熱的概念,尤其在網際網路的大背景下,微服務的理論有機會被廣泛實踐。但是在實踐過程中,大家對微服務的理解確大相徑庭,到底要怎樣做才能真正掌握微服務的架構理論呢?通過此文筆者想和大家分享一下對微服務架構的認識和理解。 什麼是微服務 微服務的

談談Java反射的理解

 在張老師的課程當中,談到了反射的概念,用馮偉立學長的最簡單的一句話來說就是:反射就是把Java類中的各種成分對映成相應的Java類。 當然在很多教科書上有其他的一些定義,大多數比較長的解釋。比如說: JAVA反射機制是在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬

談談HTTP協議的理解

一.HTTP協議版本         HTTP1.0與HTTP1.1的區別主要體現在以下幾個方面:              1. HTTP1.0是短連線、HTTP1.1是長連線。              2. 增加請求頭和響應頭。(什麼是請求頭和響應頭?等下我會上圖說明

不要再問MVCMVPMVVM

網路上有很多類似的討論。比如 阮一峰:MVC,MVP 和 MVVM 的圖示 廖雪峰:MVVM 司徒正美: 各自用一句話來概括MVC、MVP、MVVM的差異特點 。。。 但是說的往往比較概念化、空泛,初學者很難理解。本篇用最簡單的例子講解這三者,看完本篇,你就不會再糾結這個問題了   框架的起源與目的

【框架篇】mvcmvpmvvm使用關系總結

mvc模型 details eset 網站架構 特性 自動 分享 規模 arch MVC MVC全名是Model View Controller,是模型(model)-視圖(view)-控制器(controller)的縮寫,一種軟件設計典範,用一種業務邏輯、數據、界面顯

MVCMVPMVVM模式對比總結(2)橫向構架模型

span del nec 處理請求 eth .cn pos 實現 通過 前言說明 在實戰項目及學習中來總結一下Android端項目構架 包括MVC、MVP、MVVM,主要針對移動Android端 該篇只描述橫向構架模型 目錄 1.構架基礎 2.橫向構架模型 3.縱向

淺談MVCMVPMVVM架構模式的區別和聯系

.html csdn 獲取 視圖 viewmodel url title tle htm 淺談MVC、MVP、MVVM架構模式的區別和聯系 學習了:http://www.cnblogs.com/guwei4037/p/5591183.html http://blog.csd