- MVC:Model-View-Controller,經典模式,很容易理解,主要缺點有兩個:
View對Model的依賴,會導致View也包含了業務邏輯;
- Controller會變得很厚很複雜。
3.MVVM:Model-View-ViewModel,是對MVP的一個優化模式,採用了雙向繫結:View的變動,自動反映在ViewModel,反之亦然。
MVC
簡單的說:我們平時寫的Demo都是MVC,controller就是我們的activity,model(資料提供者)就是讀取資料庫,網路請求這些我們一般有專門的類處理,View一般用自定義控制元件。
但這一切,只是看起來很美。
想象實際開發中,我們的activity程式碼其實是越來越多,model和controller根本沒有分離,控制元件也需要關係資料和業務。
所以說,MVC的真實存在是MC(V),Model和Controller根本沒辦法分開,並且資料和View嚴重耦合。這就是它的問題。舉個簡單例子 :獲取天氣資料展示在介面上
- 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
看上圖可以看出,從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還是複雜了對於中小型專案有點過度設計,這裡就不展開講。
模組化
上圖是一個專案常見的架構方式
最底層是基礎庫,放置與業務無關的模組:比如基礎網路請求,圖片壓縮等等,可以按需分為邏輯模組,通用UI模組和第三方庫。(建議採用獨立的svn分支)
中間層是通用業務層,放置公司多個android專案的通用業務模組(和業務相關的),比如登入流程,檔案上傳/下載等。
最上層就是應用層了,比如公司有三個android專案:LbBoss,BV和BVHD。我們還可以針對相似的專案再抽取通用層(比如這裡的BV和BV PAD版,通用層為BVCommon)。
新建一個app,我們往往有兩種模組劃分方法:
- 按照型別劃分
- 按照業務劃分
每一個包都是一個業務模組,每個模組下再按照型別來分。
- 怎麼選
我建議中小型的新專案按照型別比較好,因為開始程式碼量不多按照業務來分不切實際,一個包只放幾個檔案?? 況且前期業務不穩定,等到開發中期業務定型了,再進行重構難度也不大。
上面講的模組劃分既不屬於模組化也不屬於外掛化,僅僅是一個簡單package結構不同而已,app還是一個app並沒有產生什麼變化。通常講的模組化,是指把業務劃分為不同的moduler(型別是library),每個moduler之間都不依賴,app(型別是application)只是一個空殼依賴所有的moduler。
這樣架構後,帶來最大的不同就是:不同業務模組完全分離,好處就是不同模組的開發絕對不會互相耦合了,因為你在模組A 根本訪問不到模組B的API。此時模組間通訊急需解決,Intent隱式跳轉可以處理部分Activity的跳轉,但真正的業務場景遠不止兩個介面跳一跳。你之前封裝的業務通用方法,工具類,資料快取現在其他模組都拿不到了,本本來可以複用的控制元件,fragment都不能共享,而這些都是和業務耦合沒辦法拿到底層基礎庫。
模組間通訊
針對上面問題有兩個解決辦法,根據自己專案實際情況,如果專案的前期搭建已經很優秀,有完善的基礎庫,不同模組間的通訊不是很多,可以自己實現。如果專案比較龐大,不同業務間頻繁呼叫建議使用阿里巴巴的開源庫。
自己實現
首先每個moduler有個目錄叫include,裡面有三個類,此處以一個bbs論壇模組為例說明,
IBBSNotify:裡面是一堆interface,作用是該模組對外的回撥,只能被動被觸發。
IBBService:裡面是一堆interface,作用是對外暴露的方法,讓別的模組來主動調,比如enterBbsActivity
IBBSServiceImpl:很明顯是IBBService的實現,比如enterBbsActivity就是具體怎麼跳轉到論壇介面,傳遞什麼資料。
每個模組方法和回撥都有了,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開發除非特殊需要,否則用不到。
外掛化也有成熟的框架,在此不詳細說了。另外,每個人的習慣不一樣,元件化,模組化在我看來差不多,沒必要糾結兩個名詞。