大多數編程語言相關的學習書籍,都會以hello,world這個典型的程序作為第一個示例。作為Android應用開發者,無論使用eclipse還是用android studio,在新建項目的時候,一直按IDE默認選擇項,下一步進行下去,就會創建出一個可以運行的hello,world應用程序。對於這個程序,可以認為是采用MVC模式,對應關系為:
- View:對應於布局文件
- Model:業務邏輯和實體模型
- Controller:對應於Activity
但是數據綁定、事件處理(hello world程序沒有)的代碼都在Activity中,Activity看起來既擔任了View的角色,又擔任了Controller的角色。這樣隨着程序業務邏輯越來越復雜,Activity中的代碼就會越來越多,最終結果就是程序的耦合度越來越高,程序修改和維護越來越難。於是MVP模式的優點就顯示出來了。下面我就以這個最簡單的程序,來談談我對mvp模式的理解。
先上代碼:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.example.joy.etest.MainActivity">
<TextView android:id="@+id/tv_show" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" />
<Button android:id="@+id/btn_test" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="測試"/>
</LinearLayout>
布局文件很簡單,一個 TextView ,程序運行顯示 “Hello World!”。這里我增加了一個Button,點擊Button,開始倒計時10秒鍾,以此來模擬一些耗時的操作,10秒鍾后,TextView顯示"Hello MVP!"。再看java代碼,在MVC模式下,我們直接在 Activity 中通過 setText 方法,就可以給 TextView 設置顯示的內容。MVP模式相對於MVC模式來說,就是將Controller這部分從Activity中分離出來,讓Activity只擔任View的角色,View和Model之間的橋梁作用由 presenter 來承擔,從而達到解耦的目的。具體的方法就是通過接口回調來實現。下面是時候展現真正的步驟了:
首先定義一個接口,就叫 IShowView吧,里面有一個 show 方法,用於給TextView設置顯示內容
package com.example.joy.mvptest; public interface IShowView { void show(String str); }
MainActivity實現上述接口:
package com.example.joy.mvptest; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; public class MainActivity extends AppCompatActivity implements IShowView, View.OnClickListener { private TextView mTvShow; private Button mBtnTest; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTvShow = (TextView) findViewById(R.id.tv_show); mBtnTest = (Button) findViewById(R.id.btn_test); mBtnTest.setOnClickListener(this); } @Override public void show(String str) { mTvShow.setText(str); } @Override public void onClick(View v) { if(v.getId() == R.id.btn_test) { } } }
代碼很簡單。MainActivity實現接口中的show方法,即為TextView賦值。Button的點擊事件暫時沒有寫。
其次,presenter模塊也要定義一個接口,與 MainActivity 實現的接口提供類似的方法,就叫 IPresenter 吧:
package com.example.joy.mvptest.presenter; public interface IPresenter { void show(); }
注意,這個接口里的 show 方法沒有參數,和前面 IShowView 中的方法簽名不一樣,當然,你也可以不命名為 show ,方法名可以自己隨便起。這里的方法參數完全是根據實際需要來確定。有了接口,當然還需要有接口的實現類,命名為 PresenterComl :
package com.example.joy.mvptest.presenter; import android.os.AsyncTask; import android.os.SystemClock; import android.util.Log; import com.example.joy.mvptest.IShowView; public class PresenterComl implements IPresenter { private IShowView iShowView; public PresenterComl(IShowView iShowView) { this.iShowView = iShowView; } @Override public void show() { new AsyncTask<Void, Void, Void>() { @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); Log.d("joy99", "下載完成。"); iShowView.show("Hello,MVP!"); } @Override protected Void doInBackground(Void... params) { for (int i = 0; i < 10; i++) { Log.d("joy99", "正在下載...預計剩余時間 " + (10 - i) + "秒"); SystemClock.sleep(1000); } return null; } }.execute(); } }
此處我用了一個異步任務。將倒計時打印出來,模擬一些耗時的數據操作,比如網絡請求等等。該類中聲明了一個IShowView接口的實現類的對象,iShowView,倒計時完成,調用iShowView的show方法,將“結果”傳遞過去,典型的接口回調的用法。
剩下的最后一步就很清晰了,在 MainActivity 中定義 PresenterComl 類的對象 iPresenter,然后,在Button的點擊事件中,調用 iPresenter 的show方法。注意構造方法中MainActivity本身,作為實現 IShowView 的類的對象傳遞進去。修改后的 MainActivity 類代碼如下:
package com.example.joy.mvptest; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; import com.example.joy.mvptest.presenter.IPresenter; import com.example.joy.mvptest.presenter.PresenterComl; public class MainActivity extends AppCompatActivity implements IShowView, View.OnClickListener { private TextView mTvShow; private Button mBtnTest; private IPresenter iPresenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTvShow = (TextView) findViewById(R.id.tv_show); mBtnTest = (Button) findViewById(R.id.btn_test); mBtnTest.setOnClickListener(this); iPresenter = new PresenterComl(this); } @Override public void show(String str) { mTvShow.setText(str); } @Override public void onClick(View v) { if(v.getId() == R.id.btn_test) { iPresenter.show(); } } }
上面黃色底紋的代碼就是這最后一步,將Presenter部分與Activity建立關聯。
OK,通過這樣一個小程序將MVP模式分析了一下,它的本質其實就是接口回調。當然,對於這樣一個小的不能再小的程序來說,用MVP模式,確實看起來更復雜了。但是代碼邏輯復雜了,MVP模式的優勢就顯示出來了。最后總結一下:
使用MVP模式會使得代碼多出一些接口,但是使得代碼邏輯更加清晰,尤其是在處理復雜界面和邏輯時,我們可以對同一個activity將每一個業務都抽離成一個Presenter,這樣代碼既清晰、邏輯明確又方便我們擴展。當然如果我們的業務邏輯本身就比較簡單的話使用MVP模式就顯得沒那么必要。所以我們不需要為了用它而用它,具體的還是要要業務需要。