轉自http://segmentfault.com/blogs,轉載請註明出處
Android MVP Patternandroid
Android MVP模式\[1\]也不是什麼新鮮的東西了,我在本身的項目裏也廣泛地使用了這個設計模式。當項目愈來愈龐大、複雜,參與的研發人員愈來愈多的時候,MVP模式的優點就充分顯示出來了。git
導讀:MVP模式是MVC模式在Android上的一種變體,要介紹MVP就得先介紹MVC。在MVC模式中,Activity應該是屬於View這一層。而實質上,它既承擔了View,同時也包含一些Controller的東西在裏面。這對於開發與維護來講不太友好,耦合度大高了。把Activity的View和Controller抽離出來就變成了View和Presenter,這就是MVP模式。github
基本信息segmentfault
MVP模式(Model-View-Presenter)能夠說是MVC模式(Model-View-Controller)在Android開發上的一種變種、進化模式。後者你們可能比較熟悉,就算不熟悉也可能或多或少地在本身的項目中用到過。要介紹MVP模式,就不得不先說說MVC模式。設計模式
MVC模式網絡
MVC模式的結構分爲三部分,實體層的Model,視圖層的View,以及控制層的Controller。異步
例如,View層接受用戶的輸入,而後經過Controller修改對應的Model實例;同時,當Model實例的數據發生變化的時候,須要修改UI界面,能夠經過Controller更新界面。(View層也能夠直接更新Model實例的數據,而不用每次都經過Controller,這樣對於一些簡單的數據更新工做會變得方便許多。)ide
舉個簡單的例子,如今要實現一個飄雪的動態壁紙,能夠給雪花定義一個實體類Snow,裏面存放XY軸座標數據,View層固然就是SurfaceView(或者其餘視圖),爲了實現雪花飄的效果,能夠啓動一個後臺線程,在線程裏不斷更新Snow實例裏的座標值,這部分就是Controller的工做了,Controller裏還要定時更新SurfaceView上面的雪花。進一步的話,能夠在SurfaceView上監聽用戶的點擊,若是用戶點擊,只經過Controller對觸摸點周圍的Snow的座標值進行調整,從而實現雪花在用戶點擊後出現彈開等效果。具體的MVC模式請自行Google。oop
MVP模式post
在Android項目中,Activity和Fragment佔據了大部分的開發工做。若是有一種設計模式(或者說代碼結構)專門是爲優化Activity和Fragment的代碼而產生的,你說這種模式重要不?這就是MVP設計模式。
按照MVC的分層,Activity和Fragment(後面只說Activity)應該屬於View層,用於展現UI界面,以及接收用戶的輸入,此外還要承擔一些生命週期的工做。Activity是在Android開發中充當很是重要的角色,特別是TA的生命週期的功能,因此開發的時候咱們常常把一些業務邏輯直接寫在Activity裏面,這很是直觀方便,代價就是Activity會愈來愈臃腫,超過1000行代碼是常有的事,並且若是是一些能夠通用的業務邏輯(好比用戶登陸),寫在具體的Activity裏就意味着這個邏輯不能複用了。若是有進行代碼重構經驗的人,看到1000+行的類確定會有所顧慮。所以,Activity不只承擔了View的角色,還承擔了一部分的Controller角色,這樣一來V和C就耦合在一塊兒了,雖然這樣寫方便,可是若是業務調整的話,要維護起來就難了,並且在一個臃腫的Activity類查找業務邏輯的代碼也會很是蛋疼,因此看起來有必要在Activity中,把View和Controller抽離開來,而這就是MVP模式的工做了。
MVP模式的核心思想
MVP把Activity中的UI邏輯抽象成View接口,把業務邏輯抽象成Presenter接口,Model類仍是原來的Model。
這就是MVP模式,如今這樣的話,Activity的工做的簡單了,只用來響應生命週期,其餘工做都丟到Presenter中去完成。從上圖能夠看出,Presenter是Model和View之間的橋樑,爲了讓結構變得更加簡單,View並不能直接對Model進行操做,這也是MVP與MVC最大的不一樣之處。
MVP模式的做用
MVP的好處都有啥,誰說對了就給他 KIRA!!(<ゝω·)☆
其中最重要的有三點
Activity 代碼變得更加簡潔
相信不少人閱讀代碼的時候,都是從Activity開始的,對着一個1000+行代碼的Activity,看了都以爲難受。
使用MVP以後,Activity就能瘦身許多了,基本上只有FindView、SetListener以及Init的代碼。其餘的就是對Presenter的調用,還有對View接口的實現。這種情形下閱讀代碼就容易多了,並且你只要看Presenter的接口,就能明白這個模塊都有哪些業務,很快就能定位到具體代碼。Activity變得容易看懂,容易維護,之後要調整業務、刪減功能也就變得簡單許多。
方便進行單元測試
通常單元測試都是用來測試某些新加的業務邏輯有沒有問題,若是採用傳統的代碼風格(習慣性上叫作MV模式,少了P),咱們可能要先在Activity裏寫一段測試代碼,測試完了再把測試代碼刪掉換成正式代碼,這時若是發現業務有問題又得換回測試代碼,咦,測試代碼已經刪掉了!好吧從新寫吧……
MVP中,因爲業務邏輯都在Presenter裏,咱們徹底能夠寫一個PresenterTest的實現類繼承Presenter的接口,如今只要在Activity裏把Presenter的建立換成PresenterTest,就能進行單元測試了,測試完再換回來便可。萬一發現還得進行測試,那就再換成PresenterTest吧。
避免 Activity 的內存泄露
Android APP 發生OOM的最大緣由就是出現內存泄露形成APP的內存不夠用,而形成內存泄露的兩大緣由之一就是Activity泄露(Activity Leak)(另外一個緣由是Bitmap泄露(Bitmap Leak))。
Java一個強大的功能就是其虛擬機的內存回收機制,這個功能使得Java用戶在設計代碼的時候,不用像C++用戶那樣考慮對象的回收問題。然而,Java用戶老是喜歡隨便寫一大堆對象,而後幻想着虛擬機能幫他們處理好內存的回收工做。但是虛擬機在回收內存的時候,只會回收那些沒有被引用的對象,被引用着的對象由於還可能會被調用,因此不能回收。
Activity是有生命週期的,用戶隨時可能切換Activity,當APP的內存不夠用的時候,系統會回收處於後臺的Activity的資源以免OOM。
採用傳統的MV模式,一大堆異步任務和對UI的操做都放在Activity裏面,好比你可能從網絡下載一張圖片,在下載成功的回調裏把圖片加載到 Activity 的 ImageView 裏面,因此異步任務保留着對Activity的引用。這樣一來,即便Activity已經被切換到後臺(onDestroy已經執行),這些異步任務仍然保留着對Activity實例的引用,因此係統就沒法回收這個Activity實例了,結果就是Activity Leak。Android的組件中,Activity對象每每是在堆(Java Heap)裏佔最多內存的,因此係統會優先回收Activity對象,若是有Activity Leak,APP很容易由於內存不夠而OOM。
採用MVP模式,只要在當前的Activity的onDestroy裏,分離異步任務對Activity的引用,就能避免 Activity Leak。
說了這麼多,沒看懂?好吧,我本身都沒看懂本身寫的,咱們仍是直接看代碼吧。
MVP模式的使用
上面一張簡單的MVP模式的UML圖,從圖中能夠看出,使用MVP,至少須要經歷如下步驟:
經過上面的介紹,MVP的主要特色就是把Activity裏的許多邏輯都抽離到View和Presenter接口中去,並由具體的實現類來完成。這種寫法多了許多IView和IPresenter的接口,在某種程度上加大了開發的工做量,剛開始使用MVP的小夥伴可能會以爲這種寫法比較彆扭,並且難以記住。其實一開始想太多也沒有什麼卵用,只要在具體項目中多寫幾回,就能熟悉MVP模式的寫法,理解TA的意圖,以及享♂受其帶來的好處。
扯了這麼多,可是好像並無什麼卵用,畢竟
Talk is cheap, let me show you the code!
因此仍是來寫一下實際的項目吧。
MVP模式簡單實例
一個簡單的登陸界面(實在想不到別的了╮( ̄▽ ̄")╭),點擊LOGIN則進行帳號密碼驗證,點擊CLEAR則重置輸入。
項目結構看起來像是這個樣子的,MVP的分層仍是很清晰的。個人習慣是先按模塊分Package,在模塊下面再去建立model、view、presenter的子Package,固然也能夠用model、view、presenter做爲頂級的Package,而後把全部的模塊的model、view、presenter類都到這三個頂級Package中,就好像有人喜歡把項目裏全部的Activity、Fragment、Adapter都放在一塊兒同樣。
首先來看看LoginActivity
public class LoginActivity extends ActionBarActivity implements ILoginView, View.OnClickListener {
private EditText editUser;
private EditText editPass;
private Button btnLogin;
private Button btnClear;
ILoginPresenter loginPresenter;
private ProgressBar progressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//find view
editUser = (EditText) this.findViewById(R.id.et_login_username);
editPass = (EditText) this.findViewById(R.id.et_login_password);
btnLogin = (Button) this.findViewById(R.id.btn_login_login);
btnClear = (Button) this.findViewById(R.id.btn_login_clear);
progressBar = (ProgressBar) this.findViewById(R.id.progress_login);
//set listener
btnLogin.setOnClickListener(this);
btnClear.setOnClickListener(this);
//init
loginPresenter = new LoginPresenterCompl(this);
loginPresenter.setProgressBarVisiblity(View.INVISIBLE);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_login_clear:
loginPresenter.clear();
break;
case R.id.btn_login_login:
loginPresenter.setProgressBarVisiblity(View.VISIBLE);
btnLogin.setEnabled(false);
btnClear.setEnabled(false);
loginPresenter.doLogin(editUser.getText().toString(), editPass.getText().toString());
break;
}
}
@Override
public void onClearText() {
editUser.setText("");
editPass.setText("");
}
@Override
public void onLoginResult(Boolean result, int code) {
loginPresenter.setProgressBarVisiblity(View.INVISIBLE);
btnLogin.setEnabled(true);
btnClear.setEnabled(true);
if (result){
Toast.makeText(this,"Login Success",Toast.LENGTH_SHORT).show();
startActivity(new Intent(this, HomeActivity.class));
}
else
Toast.makeText(this,"Login Fail, code = " + code,Toast.LENGTH_SHORT).show();
}
@Override
public void onSetProgressBarVisibility(int visibility) {
progressBar.setVisibility(visibility);
}
}
全選複製放進筆記
從代碼能夠看出LoginActivity只作了findView以及setListener的工做,並且包含了一個ILoginPresenter,全部業務邏輯都是經過調用ILoginPresenter的具體接口來完成。因此LoginActivity的代碼看起來很舒爽,甚至有點愉♂悅呢 (/ω\*)。視力不錯的你可能還看到了ILoginView接口的實現,若是不懂爲何要這樣寫的話,能夠先往下看,這裏只要記住LoginActivity實現了ILoginView接口。
再來看看ILoginPresenter
public interface ILoginPresenter {
void clear();
void doLogin(String name, String passwd);
void setProgressBarVisiblity(int visiblity);
}
public class LoginPresenterCompl implements ILoginPresenter {
ILoginView iLoginView;
IUser user;
Handler handler;
public LoginPresenterCompl(ILoginView iLoginView) {
this.iLoginView = iLoginView;
initUser();
handler = new Handler(Looper.getMainLooper());
}
@Override
public void clear() {
iLoginView.onClearText();
}
@Override
public void doLogin(String name, String passwd) {
Boolean isLoginSuccess = true;
final int code = user.checkUserValidity(name,passwd);
if (code!=0) isLoginSuccess = false;
final Boolean result = isLoginSuccess;
handler.postDelayed(new Runnable() {
@Override
public void run() {
iLoginView.onLoginResult(result, code);
}
}, 3000);
}
@Override
public void setProgressBarVisiblity(int visiblity){
iLoginView.onSetProgressBarVisibility(visiblity);
}
private void initUser(){
user = new UserModel("mvp","mvp");
}
}
全選複製放進筆記
從代碼能夠看出,LoginPresenterCompl保留了ILoginView的引用,所以在LoginPresenterCompl裏就能夠直接進行UI操做了,而不用在Activity裏完成。這裏使用了ILoginView引用,而不是直接使用Activity,這樣一來,若是在別的Activity裏也須要用到相同的業務邏輯,就能夠直接複用LoginPresenterCompl類了(一個Activity能夠包含一個以上的Presenter,總之,須要什麼業務就new什麼樣的Presenter,是否是很靈活(@ ̄︶ ̄@)),這也是MVP的核心思想
經過IVIew和IPresenter,把Activity的UI Logic和Business Logic分離開來,Activity just does its basic job! 至於Model嘛,仍是原來MVC裏的Model。
再來看看ILoginView,至於ILoginView的實現類呢,翻到上面看看LoginActivity吧
全選複製放進筆記
public interface ILoginView {
public void onClearText();
public void onLoginResult(Boolean result, int code);
public void onSetProgressBarVisibility(int visibility);
}
代碼這種東西放在日誌裏講好像除了把整個版面拉長沒什麼卵用,我把幾種本身經常使用的MVP的寫法寫成一個Demo項目,歡迎圍觀和PullRequest:Android-MVP-Pattern。
後記
以上就是個人MVP模式的一點理解,在MVVM模式尚未成熟的如今,我以爲沒有比MVP模式更好的替代品了。固然今天寫的只是MVP的基礎使用,介紹的實例項目也很是簡單,看不出MVP的優點,後面還會針對MVP模式寫一些日誌,就目前能想到的至少包括
附錄