當類與類直接依賴細節,那他們之間就有直接的耦合,當具體實現需要變化時,意味著需要修改依賴者的代碼,這限制了系統的可擴展性。
MVP的全稱是Model、View、Presenter,將整個應用分為三層。
View層:視圖層,包含界面相關的功能,例如Activity,Fragment,View,Adapter等,該層專注于用戶交互,實現設計師給出的界面,動畫等交互效果。View層一般會持有Presenter層的引用,或者依賴注入(如dagger)的方式獲得presenter實例,并將非UI的邏輯操作委托給Presenter。
Presenter層:邏輯控制層,充當中間人的角色,用來隔離View層和model層,該層是通過從View層剝離控制邏輯部分而形成,主要負責View層和model層的控制和交互。例如接受View層的網絡數據的加載請求,并分發給model處理,同時監聽model層的處理結果,最終將其反饋給View層從而實現界面的刷新
model層封裝各種數據來源,例如遠程網絡數據,本地數據庫數據等,對Presenter層提供簡單易用的接口
Presenter和View以及Model的交互都是通過接口進行的;通常View與Presenter是一對一的,當然,復雜的View可能需要多個Presenter來共同處理。
關于android的MVP模式其實一直沒有一個統一的實現方式,不同的人由于個人理解的不同,進而產生了很多不同的實現方式
MVP的好處
使用MVP組織代碼架構,并對代碼實施分層管理,有以下好處:
1.如果界面發生變化,甚至是全新改版,只需修改對應的View即可,Presenter和Model層無需改動。
2.如果業務邏輯或者數據獲取方式放生變化,只需修改model層
3.如果控制邏輯發生變化,只需修改Presenter層。
4.Presenter層和View層以及model層的交互都是基于接口實現的,這有助于對Presenter進行單元測試,同時由于是面向接口編程,只需要事先定義好接口,每一層的實現都可以交由不同的開發人員并行實現,最終再一起連調,都能明顯的加快某一功能的開發進度。
5.團隊的新成員拿到項目的代碼,能夠很容易的讀懂現有的邏輯,快速上手。
6.如果你正在開發一個對外的sdk,根據市場需求,需要提供帶UI版本和不帶UI的純接口版本,那么使用MVP模式,將UI部分代碼放在View層,將接口部分代碼放在model層,打包的時候可以輕松實現是否將View層打包進去,從而避免純借口版本混入UI相關的代碼。
7.UI畫好可以先寫UI,接口寫好可以先寫業務邏輯
MVP存在的問題
1.增加代碼類的數量
2.由于進行了三層劃分,函數的調用棧變深,如果開發人員沒能非常清楚的了解哪一層具體該負責哪些功能,那么可能存在因為層次職責辨認不清等原因導致不同層之間的代碼亂入,從而沒能達到MVP充分解耦各層的目的
使用
presenter:SplashPresenter(接口)---具體p層的實現SplashPresenterImpl(通過構造,持有View層的引用)
View: SplashView(接口)--具體View的實現SplashActivity--extends BaseActivity implements SplashView
View層需要持有p層的引用--因此SplashActivity中要創建篇p層
SplashPresenter presenter = new SplashPresenterImpl(this);
//檢查是否登錄過
presenter.checkLoginState();
調用p層--p層會調用View層
Presenter層
public interface SplashPresenter {
/**
* 檢查之前是否登陸過
*/
void checkLoginState();
}
public class SplashPresenterImpl implements SplashPresenter {
private SplashView splashView;
public SplashPresenterImpl(SplashView splashView) {
this.splashView = splashView;
}
@Override
public void checkLoginState() {
if(EMClient.getInstance().isLoggedInBefore()&&EMClient.getInstance().isConnected()){
splashView.onGetLoginState(true);
}else{
splashView.onGetLoginState(false);
}
}
}
View層
public interface SplashView {
/**
* 獲取到登錄狀態之后 后續操作
* @param isLoginBefore 是否登錄過 如果是true說明登錄過
*/
void onGetLoginState(boolean isLoginBefore);
}
public class SplashActivity extends BaseActivity implements SplashView {
@InjectView(R.id.iv_splash)
ImageView ivSplash;
private SplashPresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
ButterKnife.inject(this);
SplashPresenter presenter = new SplashPresenterImpl(this);
//檢查是否登錄過
presenter.checkLoginState();
}
@Override
public void onGetLoginState(boolean isLoginBefore) {
if(isLoginBefore){
//登錄過 直接打開MainActivity
startActivity(MainActivity.class,true);
}else{
//沒登錄 打開登錄界面
ObjectAnimator animator = ObjectAnimator.ofFloat(ivSplash,"alpha",0,1);
animator.setDuration(2000);
animator.start();
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
//當動畫結束的時候 開啟login頁面
startActivity(LoginActivity.class,true);
}
});
}
}
}
案例2
public interface ContactPresenter {
/**
* 初始化聯系人數據
*/
void initContact();
/**
* 聯網更新聯系人
*/
void updateContactFromServer();
/**
* 刪除聯系人
*/
void deleteContact(String username);
}
public class ContactPresenterImpl implements ContactPresenter {
private ContactView contactView;
public ContactPresenterImpl(ContactView contactView) {
this.contactView = contactView;
}
@Override
public void initContact() {
//先從數據庫獲取聯系人數據
List<String> contacts = DbUtils.initContacts(EMClient.getInstance().getCurrentUser());
//通知view更新數據
contactView.onInitContact(contacts);
//聯網獲取最新數據
updateContactFromServer();
}
@Override
public void updateContactFromServer() {
ThreadUtils.runOnSubThread(new Runnable() {
@Override
public void run() {
try {
final List<String> contactsFromServer = EMClient.getInstance().contactManager().getAllContactsFromServer();
Collections.sort(contactsFromServer, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});
//排序之后的集合放到數據庫中
DbUtils.updateContactsDB(EMClient.getInstance().getCurrentUser(),contactsFromServer);
//沒走異常 說明聯系人數據更新成功
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
contactView.onUpdateContact(true,contactsFromServer,null);
}
});
} catch (final HyphenateException e) {
e.printStackTrace();
//如果走異常 說明聯系人數據更新失敗
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
contactView.onUpdateContact(false,null,e.getMessage());
}
});
}
}
});
}
@Override
public void deleteContact(final String username) {
ThreadUtils.runOnSubThread(new Runnable() {
@Override
public void run() {
try {
EMClient.getInstance().contactManager().deleteContact(username);
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
contactView.ondeleteContact(true,null);
}
});
} catch (final HyphenateException e) {
e.printStackTrace();
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
contactView.ondeleteContact(false,e.getMessage());
}
});
}
}
});
}
}
public interface ContactView {
void onInitContact(List<String> contacts);
void onUpdateContact(boolean isUpdateSuccess,List<String> contacts,String errorMsg);
void ondeleteContact(boolean isDeleteSuccess,String errorMsg);
}
public class ContactFragment extends BaseFragment implements ContactView {
private ContactLayout contactLayout;
private ContactPresenter presenter;
private ContactAdapter adapter;
public ContactFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_contact, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
contactLayout = (ContactLayout) view.findViewById(R.id.contactlayout);
presenter = new ContactPresenterImpl(this);
presenter.initContact();
}
@Override
public void onInitContact(List<String> contacts) {
if(adapter == null){
adapter = new ContactAdapter(contacts);
}
adapter.setOnItemClickListener(new ContactAdapter.OnItemClickListener() {
@Override
public void onclick(View v, String username) {
//跳轉到聊天界面
Intent intent = new Intent(getContext(),ChatActivity.class);
intent.putExtra("username",username);
startActivity(intent);
}
@Override
public boolean onLongClick(View v, final String username) {
//刪除聯系人
Snackbar.make(contactLayout,"真的要刪除"+username+"嗎?",Snackbar.LENGTH_SHORT).
setAction("確定", new View.OnClickListener() {
@Override
public void onClick(View v) {
presenter.deleteContact(username);
}
}).show();
return false;
}
});
contactLayout.setAdapter(adapter);
contactLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
//刷新聯系人
presenter.updateContactFromServer();
}
});
}
@Override
public void onUpdateContact(boolean isUpdateSuccess, List<String> contacts, String errorMsg) {
contactLayout.setRefreshing(false);
if(isUpdateSuccess){
adapter.setContacts(contacts);
adapter.notifyDataSetChanged();
}else{
ToastUtils.showToast(getContext(),"刷新失敗");
}
}
@Override
public void ondeleteContact(boolean isDeleteSuccess, String errorMsg) {
if(isDeleteSuccess){
ToastUtils.showToast(getContext(),"刪除成功");
}else{
ToastUtils.showToast(getContext(),"刪除失敗");
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onContactChanged(ContactChangeEvent event){
//收到刪除聯系人的事件就更新列表
presenter.updateContactFromServer();
}
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
}