最近通過《Android原始碼設計模式解析與實戰》對MVP應用架構進行了瞭解,摘其重點記錄於此。
MVP簡介
MVP模式全稱Model View Presenter。能: 1.有效地降低View複雜性,避免業務邏輯被塞進View中; 2.解除View與Model的耦合,保證了系統的整潔性、靈活性。
理想化的MVP模式可以實現同一份邏輯程式碼搭配不同的顯示頁面,因為它們之間並不依賴於具體(class),而是依賴於抽象(interface)。 MVP並不是一個標準化的模式,我們可根據自己的需求和自己認為對的方式去修正MVP的實現方式,它可以隨著Presenter的複雜程度變化。 只要我們是通過Presenter將View和Model解耦合、降低型別複雜度、各個模組可以獨立測試、獨立變化,這就是正確的方向。
MVP模式的三個角色
- Presenter層:主要作為View和Model的橋樑,它從Model層檢索資料後,返回給View層,使得View和Model之間沒有耦合,也將業務邏輯從View角色上抽離出來。
- View層:使用者介面,通常是指Activity、Fragment或者某個View控制元件,它含有一個Presenter成員變數。負責頁面渲染與事件分發(即呼叫Presenter方法處理事件)。
- Model層:Model角色主要是提供資料的存取功能。Presenter需要通過Model層儲存、獲取資料,Model就像一個資料倉儲。更直白地說,Model是封裝了資料庫DAO或者網路獲取資料的角色,或者兩種資料獲取方式的集合。
MVP模式的應用
下面通過一個簡單的客戶端示例來直觀地體會MVP在開發中的運用。我們的UI原型大致如下圖所示:主介面就是一個ListView和進度條,在載入資料時顯示進度條,載入完成之後隱藏。
進入應用後首先會從伺服器下載最新的20篇文章,然後將每一篇文章的簡介顯示到列表上。因此,我們的業務邏輯大概有下列2個:
- 向伺服器請求資料,並且在資料儲存到資料庫中;
- 從資料庫中載入文章列表。
關鍵類如下:
- 本地倉庫——LinkedList:負責提供(輸出)本地資料,同時負責將遠端資料快取至本地,被倉庫層持有;
- 遠端倉庫介面——RemoteRepository:如Retrofit對應的interface檔案,被倉庫層持有;
- 遠端倉庫實現——RemoteRepositoryImpl :負責Http請求的發起及響應的接收,被倉庫層例項化;
- 倉庫層介面——ArticleModel:定義了倉庫層需要實現的功能,亦即Presenter可呼叫的方法,被Presenter持有;
- 倉庫層實現——ArticleModelImpl :即Model層,內部持有本地倉庫和遠端倉庫的引用,負責為Presenter提供資料,被Presenter例項化;
- Presenter層——ArticlePresenter :被View層實現持有,同時持有倉庫層介面(ArticleModel)及View層介面(ArticleViewInterface),通過呼叫倉庫層及View層的方法實現業務邏輯;
- View層介面——ArticleViewInterface:定義檢視層需要實現的功能,被Presenter持有;
- View層實現——MvpHomeActivity:實現View層需要具備的功能(方法),按需呼叫Presenter的業務邏輯;
/**
*資料載入監聽
*/
public interface DataListener<T> {
//泛型是資料集合的型別
public void onComplete(T articles);
}
/**
*遠端資料獲取介面(remote倉庫介面)
*/
public interface RemoteRepository{
/**
*遠端抓取資料
*/
public void fetchArticles(DataListener<List<String>> listener);
}
/**
*遠端資料獲取實現(remote倉庫實現)
*/
public class RemoteRepositoryImpl implements RemoteRepository{
@Override
public void fetchArticles(DataListener<List<String>> listener) {
List<String> articles = new ArrayList<>();
for (int i = 0; i < 20; i++) {
articles.add("文章" + i);
}
listener.onComplete(articles);
}
}
/**
*資料層抽象(Model層介面、倉庫層介面)
*/
public interface ArticleModel {
/**
*呼叫遠端介面獲取文章列表
*/
public void loadArticlesFromRemote(DataListener<List<String>> listener);
/**
*快取網路資料至本地
*/
public void saveArticles(List<String> articles);
/**
*從快取中載入資料
*@param listener 該引數由ModelImpl呼叫,由Presenter負責實現,用於解耦。
*/
public void loadArticlesFromCache(DataListener<List<String>> listener);
}
/**
*資料層實現(Model層實現、倉庫層實現)
*/
public class ArticleModelImpl implements ArticleModel {
/**
*持有local倉庫的引用
*/
List<String> mCacheArticles = new LinkedList<String>();
/**
*持有remote倉庫的引用
*/
RemoteRepository remote= new RemoteRepositorympl();
@Override
public void loadArticlesFromRemote(DataListener<List<String>> listener) {
remote.fetchArticles(listener);
}
@Override
public void saveArticles(List<String> articles) {
//為了程式碼簡單,我們這裡模擬資料庫的存取操作,將資料快取到記憶體中
mCacheArticles.addAll(articles);
}
@Override
public void loadArticlesFromCache(DataListener<List<String>> listener) {
listener.onComplete(mCacheArticles);
}
}
/**
*Presenter層
*/
public class ArticlePresenter {
//Presenter層持有View層的介面
ArticleViewInterface mArticleView;
//Presenter層持有Model層的介面
ArticleModel mArticleModel;
//構造器,給持有的View層介面賦以具體的實現,進而可以呼叫View層的方法
//同時例項化成員變數
public ArticlePresenter(ArticleViewInterface viewInterface) {
mArticleView = viewInterface;
mArticleModel = new ArticleModelImpl();
}
/**
*業務邏輯1:獲取遠端資料
*/
public void fetchArticles() {
mArticleView.showLoading();
mArticleModel .loadArticlesFromRemote(new DataListener<List<String>>() {
@Override
public void onComplete(List<String> result) {
//資料載入完,呼叫View的showArticles函式將資料傳遞為View顯示
mArticleView.showAricles(result);
mArticleView.hideLoading();
//儲存到資料庫
mArticleModel.saveArticles(result);
}
});
}
/**
*業務邏輯2:獲取快取資料:
*/
public void loadArticlesFromDB(){
mArticleModel.loadArticlesFromCache(new DataListener<List<String>>() {
@Override
public void onComplete(List<String> result) {
mArticleView.showAricles(result);
}
});
}
}
/**
*View層介面
*/
public interface ArticleViewInterface {
/**
*展示資料
*/
public void showAricles(List<String> articles);
/**
*顯示進度條
*/
public void showLoading();
/**
*隱藏進度條
*/
public void hideLoading();
}
/**
*View層實現
*/
public class MvpHomeActivity extends Activity implements ArticleViewInterface {
private ListView lv;
private ProgressBar pb;
private List<String> mArticles = new LinkedList<String>();
private BaseAdapter mAdapter;
//View層實現持有Presenter的引用
private ArticlePresenter mPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvp_home);
//初始化各種控制元件
initViews();
//構建ArticlePresenter,與Activity關聯
mPresenter = new ArticlePresenter(this);
boolean netAvailable = true;
if (netAvailable) {
mPresenter.fetchArticles();
}else{
mPresenter.loadArticlesFromDB();
}
}
private void initViews() {
pb = (ProgressBar) findViewById(R.id.pb);
lv = (ListView) findViewById(R.id.lv);
mAdapter = new CommonAdapter<String>(this,R.layout.item_layout,mArticles) {
@Override
protected void convert(ViewHolder viewHolder, String item, int position) {
viewHolder.setText(R.id.text1,item);
}
};
lv.setAdapter(mAdapter);
}
@Override
public void showAricles(List<String> articles) {
mArticles.addAll(articles);
mAdapter.notifyDataSetChanged();
}
@Override
public void showLoading() {
pb.setVisibility(View.VISIBLE);
}
@Override
public void hideLoading() {
pb.setVisibility(View.GONE);
}
}
複製程式碼
延伸
需要注意的是,上述程式碼中對於ArticlePresenter
並沒有進行介面抽象,而是使用了具體類,因為業務邏輯相對穩定,在此我們直接使用具體類即可。當然,如果業務邏輯相對來說易於變化,使用Presenter
介面來應對更是最好不過了。
同時,由於Presenter經常性地需要執行一些耗時操作,如網路請求。而Presenter
持有了MvpHomeActivity
的強引用,如果在請求結束之前Activity被銷燬了,那麼由於網路請求還沒有返回,導致Presenter
一直持有MvpHomeActivity
物件,使得MvpHomeActivity
物件無法被回收,此時就發生了記憶體洩漏。
為了解決上述問題,可以通過弱引用和Activity,Fragment的生命週期來解決這個問題。首先建立一個Presenter抽象,它是一個泛型類,泛型型別為View角色要實現的介面型別,程式碼如下:
public abstract class BasePresenter<T> {
protected Reference<T> mViewRef;//View介面型別的弱引用
public void attachView(T view){
mViewRef = new WeakReference<T>(view);//建立關聯
}
//獲取View
protected T getView(){
return mViewRef.get();
}
//判斷是否與View建立了關聯
public boolean isViewAttached(){
return mViewRef!=null&&mViewRef.get()!=null;
}
//解除關聯
public void detachView(){
if(mViewRef!=null){
mViewRef.clear();
mViewRef = null;
}
}
}
複製程式碼
然後,建立一個MVPBaseActivity
基類,通過這個基類的生命週期來控制它和Presenter的關係,程式碼如下:
public abstract class MVPBaseActivity<V, P extends BasePresenter<V>> extends Activity {
/**
*Presenter物件
*/
protected P mPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//建立Presenter
mPresenter = createPresenter();
mPresenter.attachView((V) this);
}
@Override
protected void onDestroy() {
super.onDestroy();
/**
*弱引用防止記憶體溢位
*/
mPresenter.detachView();
}
/**
*子類負責提供具體的Presenter物件
*/
protected abstract P createPresenter();
}
複製程式碼
MVPBaseActivity
含有兩個泛型引數,第一個是View層介面,第二個是Presenter的具體型別。通過泛型引數,使得一些通用的邏輯可以被抽象到MVPBaseActivity
類中。