android架構多是論壇討論最多的話題了,mvc mvp和mvvm不絕於耳,後面又有模塊化和插件化。對此,關於哪一種架構更好的爭論從未中止。php
個人觀點:脫離實際項目比較這些模式優劣毫無心義,各類模式都有優勢和缺點,沒有好壞之分。越高級的架構實現起來越複雜,須要更多的學習成本更多的人力,因此說技術選型關鍵是在你本身項目的特色,團隊的水平,資源的配備,開發時間的限制,這些纔是重點!可是很多團隊本末倒置,把mvvm往本身的項目硬套。html
下面我從兩大塊講下我理解的Android架構:代碼層面,主要是MVC和MVP的理解。項目層面,主要是怎麼搭建整個項目,怎麼劃分模塊。java
先上結論:
android
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通常用自定義控件。app
但這一切,只是看起來很美。
想象實際開發中,咱們的activity代碼實際上是愈來愈多,model和controller根本沒有分離,控件也須要關係數據和業務。
image.png
因此說,MVC的真實存在是MC(V),Model和Controller根本沒辦法分開,而且數據和View嚴重耦合。這就是它的問題。舉個簡單例子 :獲取天氣數據展現在界面上
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
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仍是複雜了對於中小型項目有點過分設計,這裏就不展開講。
模塊化
image.png
上圖是一個項目常見的架構方式
最底層是基礎庫,放置與業務無關的模塊:好比基礎網絡請求,圖片壓縮等等,能夠按需分爲邏輯模塊,通用UI模塊和第三方庫。(建議採用獨立的svn分支)
中間層是通用業務層,放置公司多個android項目的通用業務模塊(和業務相關的),好比登陸流程,文件上傳/下載等。
最上層就是應用層了,好比公司有三個android項目:LbBoss,BV和BVHD。咱們還能夠針對類似的項目再抽取通用層(好比這裏的BV和BV PAD版,通用層爲BVCommon)。
新建一個app,咱們每每有兩種模塊劃分方法:
按照類型劃分
image.png
按照業務劃分
每個包都是一個業務模塊,每一個模塊下再按照類型來分。
怎麼選
我建議中小型的新項目按照類型比較好,由於開始代碼量很少按照業務來分不切實際,一個包只放幾個文件?? 何況前期業務不穩定,等到開發中期業務定型了,再進行重構難度也不大。
上面講的模塊劃分既不屬於模塊化也不屬於插件化,僅僅是一個簡單package結構不一樣而已,app仍是一個app並無產生什麼變化。一般講的模塊化,是指把業務劃分爲不一樣的moduler(類型是library),每一個moduler之間都不依賴,app(類型是application)只是一個空殼依賴全部的moduler。
image.png
每一個紅色箭頭都是一個業務模塊,紅色框是咱們的app裏面只包含簡單的業務:自定義Application,入口Activity,build.gradle編譯打包配置。看下項目的依賴關係:
image.png
這樣架構後,帶來最大的不一樣就是:不一樣業務模塊徹底分離,好處就是不一樣模塊的開發絕對不會互相耦合了,由於你在模塊A 根本訪問不到模塊B的API。此時模塊間通訊急需解決,Intent隱式跳轉能夠處理部分Activity的跳轉,但真正的業務場景遠不止兩個界面跳一跳。你以前封裝的業務通用方法,工具類,數據緩存如今其餘模塊都拿不到了,本原本能夠複用的控件,fragment都不能共享,而這些都是和業務耦合沒辦法拿到底層基礎庫。
模塊間通訊
針對上面問題有兩個解決辦法,根據本身項目實際狀況,若是項目的前期搭建已經很優秀,有完善的基礎庫,不一樣模塊間的通訊不是不少,能夠本身實現。若是項目比較龐大,不一樣業務間頻繁調用建議使用阿里巴巴的開源庫。
本身實現
首先每一個moduler有個目錄叫include,裏面有三個類,此處以一個bbs論壇模塊爲例說明,
IBBSNotify:裏面是一堆interface,做用是該模塊對外的回調,只能被動被觸發。
IBBService:裏面是一堆interface,做用是對外暴露的方法,讓別的模塊來主動調,好比enterBbsActivity
IBBSServiceImpl:很明顯是IBBService的實現,好比enterBbsActivity就是具體怎麼跳轉到論壇界面,傳遞什麼數據。
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開發除非特殊須要,不然用不到。
插件化也有成熟的框架,在此不詳細說了。另外,每一個人的習慣不同,組件化,模塊化在我看來差很少,不必糾結兩個名詞。