Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 淺談安卓中的MVP模式

淺談安卓中的MVP模式

編輯:關於Android編程

1.MVP簡介:

隨著UI創建技術的功能日益增強,UI層也履行著越來越多的職責。為了更好地細分視圖(View)與模型(Model)的功能,讓View專注於處理數據的可視化以及與用戶的交互,同時讓Model只關系數據的處理,基於MVC概念的MVP(Model-View-Presenter)模式應運而生。

在MVP模式裡通常包含4個要素:

(1)View:負責繪制UI元素、與用戶進行交互(在Android中體現為Activity);

(2)Viewinterface:需要View實現的接口,View通過View interface與Presenter進行交互,降低耦合,方便進行單元測試;

(3)Model:負責存儲、檢索、操縱數據(有時也實現一個Modelinterface用來降低耦合);

(4)Presenter:作為View與Model交互的中間紐帶,處理與用戶交互的負責邏輯。


2.為什麼使用MVP模式

\

 

在Android開發中,Activity並不是一個標准的MVC模式中的Controller,它的首要職責是加載應用的布局和初始化用戶界面,並接受並處理來自用戶的操作請求,進而作出響應。隨著界面及其邏輯的復雜度不斷提升,Activity類的職責不斷增加,以致變得龐大臃腫。當我們將其中復雜的邏輯處理移至另外的一個類(Presneter)中時,Activity其實就是MVP模式中 View,它負責UI元素的初始化,建立UI元素與Presenter的關聯(Listener之類),同時自己也會處理一些簡單的邏輯(復雜的邏輯交由 Presenter處理).

另外,回想一下你在開發Android應用時是如何對代碼邏輯進行單元測試的?是否每次都要將應用部署到Android模擬器或真機上,然後通過模擬用戶操作進行測試?然而由於Android平台的特性,每次部署都耗費了大量的時間,這直接導致開發效率的降低。而在MVP模式中,處理復雜邏輯的 Presenter是通過interface與View(Activity)進行交互的,這說明了什麼?說明我們可以通過自定義類實現這個 interface來模擬Activity的行為對Presenter進行單元測試,省去了大量的部署及測試的時間。

 

3.MVP模式實例

好了,大致了解了MVP模式的基本概念之後,我們就使用MVP模式來寫一個小例子。

包的結構如下圖所示: 效果展示:

\ \

下面開始講解mvp模式的步驟:

1)創建view的接口類,根據業務定義抽象方法

 

public interface IUserView {
	//顯示進度條
	void showLoading();
	//展示用戶數據
	void showUser(List users);
	
}
2)創建model的接口類,根據業務定義抽象方法

其中定一個加載數據的方法,同時設置一個加載完成的監聽,監聽內設置抽象方法complete,用於加載完成後進行回調

 

public interface IUserModel {
	//加載用戶信息的方法
	void loadUser(UserLoadListenner listener);
	//加載完成的回調
	interface UserLoadListenner{
		void complete(List users);
	}
}

 

3)創建model的實現類,實現其中抽象方法,其中的user類是在bean包根據需求自行創建的

public class UserModelImpl implements IUserModel{

	@Override
	public void loadUser(UserLoadListenner listener) {
		//模擬加載本地數據
		List users = new ArrayList();
		users.add(new User("姚明", "我很高", R.drawable.ic_launcher));
		users.add(new User("科比", "怒砍81分", R.drawable.ic_launcher));
		users.add(new User("詹姆斯", "我是宇宙第一", R.drawable.ic_launcher));
		users.add(new User("庫裡", "三分我最強", R.drawable.ic_launcher));
		users.add(new User("杜蘭特", "千年老二", R.drawable.ic_launcher));
		if(listener != null){
			listener.complete(users);
		}
	}
	
}

加載完數據,回調listener中的complete方法。

4) 創建present,在構造函數傳入view的實現類,同時在其中new出model的實現類,創建一個方法load,實現view與model間通信的橋梁。

public class Presenter1 {
	//view
	IUserView mUserView;
	//model
	IUserModel mUserModel = new UserModelImpl();
	//?通過構造函數傳入view
	public Presenter1(IUserView mUserView) {
		super();
		this.mUserView = mUserView;
	}
//加載數據
	public void load() {
		//加載進度條
		mUserView.showLoading();
		//model進行數據獲取
		if(mUserModel != null){
			mUserModel.loadUser(new UserLoadListenner() {
				
				@Override
				public void complete(List users) {
					// 數據加載完後進行回調,交給view進行展示
					mUserView.showUser(users);
					
				}
			});
		}
		
	}
Load中,先調用mUserView.showLoading() 顯示加載進度,然後是調用mUserModel.loadUser加載數據,其中要實現Listenner的complete方法,其中的邏輯就是用view將數據顯示到界面,model的最後會回調listener中的complete方法,數據就顯示在界面上了。

5)MainActivity顯然是用來顯示數據的,其中有一個listview,創建與其相關的兩個布局文件activity_main.xml與item_user.xml,令MainActivity實現IUserView接口,並實現兩個抽象方法,創建listview的適配器,重寫構造函數,並利用viewHolder,復用convertView對其進行優化,最後創建Presenter,並調用其load方法,完成加載所有邏輯。


public class MainActivity extends ActionBarActivity implements IUserView { private ListView mListView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mListView = (ListView) findViewById(R.id.lv); new Presenter1(this).load(); } public void showUser(List users) { //顯示所有用戶列表 mListView.setAdapter(new UserAdapter(this,users)); } @Override public void showLoading() { Toast.makeText(this, "正在拼命加載中", Toast.LENGTH_SHORT).show(); } } 


適配器:

 

public class UserAdapter extends BaseAdapter {

	private Context context;
	private List users;

	public UserAdapter(Context context, List users) {
		this.context = context;
		this.users = users;
	}

	@Override
	public int getCount() {
		// TODO Auto-generated method stub
		return users.size();
	}

	@Override
	public Object getItem(int position) {
		// TODO Auto-generated method stub
		return users.get(position);
	}

	@Override
	public long getItemId(int position) {
		// TODO Auto-generated method stub
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		LayoutInflater inflater = LayoutInflater.from(context);
		ViewHolder viewHolder = null;
		//convertView
		if (convertView == null) {
			convertView = inflater.inflate(R.layout.item_user, null);
			viewHolder = new ViewHolder();
			viewHolder.image = (ImageView) convertView
					.findViewById(R.id.iv_user);
			viewHolder.name = (TextView) convertView.findViewById(R.id.tv_name);
			viewHolder.content = (TextView) convertView
					.findViewById(R.id.tv_content);
			convertView.setTag(viewHolder);
		}else{
			viewHolder = (ViewHolder) convertView.getTag();
		}
		
		viewHolder.image.setImageResource(users.get(position).getPicid());
		viewHolder.name.setText(users.get(position).getName());
		viewHolder.content.setText(users.get(position).getContent());
		return convertView;
	}

	private static class ViewHolder {
		ImageView image;
		TextView name;
		TextView content;
	}

}

這樣,我們的小例子就寫完了,效果如下:

 

\

體會MVP模式的優越性:

 

 

a) 假設我們不從本地獲取用戶數據了,改成從網絡獲取,只需要從新寫一個model的實現類,並new 一個present,並在MainActivity中進行替換,就可以解決,我們模擬一下這種情況,發現修改十分方便,主界面建議使用MVP模式,它很好遵守了開閉原則。

b) 假設我不想用listview顯示數據,想換成gridview,無需修改原來代碼,只需要新建一個新的Activity來實現view,實現接口方法,同時使用gridview與新建一個與其對應的adapter即可,符合了開閉原則,不修改源碼,而是進行擴展性修改。View與model解耦,可以發現我們寫的Activity裡面都是沒有model的影子的,只有presenter.

public class GridActivity extends MvpBaseActivity implements IUserView{
	 private GridView mGridView;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_grid);
		mGridView = (GridView) findViewById(R.id.gv);
		mPresenter.load();
	}

	@Override
	public void showLoading() {
		// TODO Auto-generated method stub
		Toast.makeText(this, "正在拼命加載中", Toast.LENGTH_SHORT).show();
	}

	@Override
	public void showUser(List users) {
		// TODO Auto-generated method stub
		mGridView.setAdapter(new UserAdapter(this,users));
	}

	@Override
	protected GridPresenter createPresenter() {
		// TODO Auto-generated method stub
		
		return new GridPresenter();
	}

}

public class Presenter2 {
	//view
	IUserView mUserView;
	//model
	IUserModel mUserModel = new UserModelImpl2();
	//?通過構造函數傳入view
	public Presenter2(IUserView mUserView) {
		super();
		this.mUserView = mUserView;
	}
	//加載數據
	public void load() {
		//加載進度條
		mUserView.showLoading();
		//model進行數據獲取
		if(mUserModel != null){
			mUserModel.loadUser(new UserLoadListenner() {
				
				@Override
				public void complete(List users) {
					// 數據加載完後進行回調,交給view進行展示
					mUserView.showUser(users);
					
				}
			});
		}
		
	}
	
}


 

4)MVP中的內存洩露問題

 

發現我們之前寫的兩個Acitivty有共性的地方,就是都new 了present,我們對代碼進行抽取,提高代碼的復用性。

在各個Activitty中Presenter有很多類型,所以在BaseActivitty中,也需要對Presenter進行抽取成BasePresenter,MVP中Presenter是持有view的引用的,所以BasePresenter中使用泛型

public abstract class BasePresenter {
	
}

在BaseActivitty中,Presenter的具體類型交給子類去確定,我們只提供一個生成Presenter的方法,這裡多次用到了泛型,需要注意

 

public abstract class MvpBaseActivity> extends ActionBarActivity {
	protected T mPresenter;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		//創建presenter
		mPresenter = createPresenter();
		//內存洩露
		//關聯View
		mPresenter.attachView((V) this);
	}

	protected abstract  T createPresenter();
	
}

 

 

內存洩露分析:加入Model在請求網絡加載數據,此時假設Activity由於內存不足,被GC回收,但是網絡加載還未完成,則Presenter還存在,並持有Activity的引用,當網絡加載數據完成,Presenter會使用Activity進行數據展現,而此時Activity已被回收,就發生了內存洩露,會報錯,所以解決方法是:當view被回收,Presenter要解除與其的關聯。

既然是Presenter解除與view的關聯,那關聯與解除的邏輯肯定是在Presenter中,使用弱引用包裹view,理由是,使用弱引用,當GC掃描到的時候,就會立即回收。所以對BasePresenter進行如下的修改:

 

public abstract class BasePresenter {
	//當內存不足,釋放內存
	protected WeakReference mViewReference;

 

創建關聯和解除關聯的方法:

進行關聯的邏輯:創建弱引用,並包裹view

解除關聯的邏輯:判斷,如果弱引用不為空,清空弱引用,並設置為空,徹底釋放

//進行關聯
	public void attachView(T view) {
		mViewReference = new WeakReference(view);
	}
	//解除關聯
	public void detachView() {
		if(mViewReference != null){
			mViewReference.clear();
			mViewReference = null;
		}
	}

暴露一個方法,用於其他類從弱引用中取出view

protected T getView() {
		
		return mViewReference.get();
		
	}
GridPresenter繼承BasePresenter,進行對象抽象方法的實現

 

public class GridPresenter extends BasePresenter{
	//view
	//IUserView mUserView;
	//model
	IUserModel mUserModel = new UserModelImpl();

	/*public GridPresenter(IUserView mUserView) {
		super();
		this.mUserView = mUserView;
	}*/
	//加載數據
	public void load() {
		//加載進度條
		//mUserView.showLoading();
		getView().showLoading();
		//model進行數據獲取
		if(mUserModel != null){
			mUserModel.loadUser(new UserLoadListenner() {
				
				@Override
				public void complete(List users) {
					// 數據加載完後進行回調,交給view進行展示
					//mUserView.showUser(users);
					getView().showUser(users);
					
				}
			});
		}
		
	}
	
}

 

然後對BaseActivity進行修改:

public abstract class MvpBaseActivity> extends ActionBarActivity {
	protected T mPresenter;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		//創建presenter
		mPresenter = createPresenter();
		//內存洩露
		//關聯View
		mPresenter.attachView((V) this);
	}

	protected abstract  T createPresenter();
	@Override
	protected void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
		mPresenter.detachView();
	}
}

oncreate方法中關聯view,onDestroy方法中對關聯進行清除,所有關於內存洩露的邏輯就完成了,好了,對MVP模式的分析到此就結束了,更多的應用得大家自己在項目中對該模式進行運用,並不斷進行總結。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved