在软件开发设计中,有多种软件设计模式,如web开发中经典的MVC, 将后台分为三层:Model层,View层和Controller层,其中,Model主要是数据处理,如数据库,文件,或网络数据等;View层是视图层,主要是指前端或后端用于直接展现给用户的页面,Controller层则是负责业务逻辑处理,即将Model中读取的数据显示到View层,同时又将View层触发的操作事件经过业务处理后将数据保存到Model层中。

在android中,可能很多开发者使用的还是mvc模式,比如,在代码中可以发现大量的IxxEntity,IxxDao,IxxDaoImpl,IxxController,IxxControllerImpldengdeng。但是,Android开发中,还有一种不错的开发设计,那就是MVP模式。在Android4.4源码中的InCallUI中,我们会发现就应用了这种模式。

首先,先解释下,什么是MVP,MVP模式其实跟MVC模式的意图是一样的,都是为了将视图,数据和业务层分离开了,从而更好的应用于后续的开发,升级以及维护。其中MVP中的P,代表Presenter,即主持的意思,主要负责业务逻辑处理,跟MVC模式不同的是,在MVP中View层是不允许跟Model层直接交互的。MVP模式的理想场景就是可以只修改View层的界面代码,但是Presenter层和model层不需要修改任何代码,这是因为在android中,开发者面对最多的需求就是界面,变化最多的也是界面,所以mvp模式对android开发来说是一个非常不错的选择,当然,弊端就是,额外增加的代码量也有点多。。。

具体调用逻辑,借用下面网上一张图:

接着,我们来看看android4.4中InCallUI源码中MVP代码的应用。

InCallUI源码路径是在/packages/apps/InCallUI,我们直接看/src/com/android/incallui目录,会发现该目录下有一大堆xxxPresenter的类。其中,AnswerPresenter是处理接听电话的Presenter,CallButtonPresenter是处理拨号盘的Presenter,CallCardPresenter是通话界面的Presenter。在InCallUI中,从所有的界面中抽象出一个接口:Ui,它是一个空接口,它的子类分别对应不同的Ui,因为对应MVP每一个V,它的界面都是不同的,所以需要抽象出一个接口,并且让这个接口继承Ui,具体代码如下:

/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/ package com.android.incallui; /**
* Base class for all presenter ui.
*/
public interface Ui { }

如前面所说,CallButtonXX是负责拨号盘,对于拨号盘的界面显示,InCallUI抽象了一个继承Ui的接口CallButtonUi(定义在CallButtonPresenter类中),相关代码如下:

    public interface CallButtonUi extends Ui {
void setEnabled(boolean on);
void setMute(boolean on);
void enableMute(boolean enabled);
void setHold(boolean on);
void showHold(boolean show);
void enableHold(boolean enabled);
void showMerge(boolean show);
void showSwap(boolean show);
void showAddCall(boolean show);
void enableAddCall(boolean enabled);
void displayDialpad(boolean on);
boolean isDialpadVisible();
void setAudio(int mode);
void setSupportedAudio(int mask);
void showManageConferenceCallButton();
void showGenericMergeButton();
void hideExtraRow();
void displayManageConferencePanel(boolean on);
}

在InCallUI中,抽象类Presenter是所有XXPresenter的父类,他主要实现了跟View交互时所有Presenter都所需的onUiReady和onUiUnready两个方法。具体代码如下:

/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/ package com.android.incallui; /**
* Base class for Presenters.
*/
public abstract class Presenter<U extends Ui> { private U mUi; /**
* Called after the UI view has been created. That is when fragment.onViewCreated() is called.
*
* @param ui The Ui implementation that is now ready to be used.
*/
public void onUiReady(U ui) {
mUi = ui;
} /**
* Called when the UI view is destroyed in Fragment.onDestroyView().
*/
public final void onUiDestroy(U ui) {
onUiUnready(ui);
mUi = null;
} /**
* To be overriden by Presenter implementations. Called when the fragment is being
* destroyed but before ui is set to null.
*/
public void onUiUnready(U ui) {
} public U getUi() {
return mUi;
}
}

其中Presenter中的实现CallButtonPresenter的代码如下:

/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/ package com.android.incallui; import com.android.incallui.AudioModeProvider.AudioModeListener;
import com.android.incallui.InCallPresenter.InCallState;
import com.android.incallui.InCallPresenter.InCallStateListener;
import com.android.incallui.InCallPresenter.IncomingCallListener;
import com.android.services.telephony.common.AudioMode;
import com.android.services.telephony.common.Call;
import com.android.services.telephony.common.Call.Capabilities; import android.telephony.PhoneNumberUtils; /**
* Logic for call buttons.
*/
public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButtonUi>
implements InCallStateListener, AudioModeListener, IncomingCallListener { //省略若干行代码 public CallButtonPresenter() {
} @Override
public void onUiReady(CallButtonUi ui) {
super.onUiReady(ui); AudioModeProvider.getInstance().addListener(this); // register for call state changes last
InCallPresenter.getInstance().addListener(this);
InCallPresenter.getInstance().addIncomingCallListener(this);
} @Override
public void onUiUnready(CallButtonUi ui) {
super.onUiUnready(ui); InCallPresenter.getInstance().removeListener(this);
AudioModeProvider.getInstance().removeListener(this);
InCallPresenter.getInstance().removeIncomingCallListener(this);
} @Override
public void onStateChange(InCallState state, CallList callList) { //省略若干行代码
} @Override
public void onIncomingCall(InCallState state, Call call) {
onStateChange(state, CallList.getInstance());
} @Override
public void onAudioMode(int mode) {
if (getUi() != null) {
getUi().setAudio(mode);
}
} @Override
public void onSupportedAudioMode(int mask) {
if (getUi() != null) {
getUi().setSupportedAudio(mask);
}
} @Override
public void onMute(boolean muted) {
if (getUi() != null) {
getUi().setMute(muted);
}
} public int getAudioMode() {
return AudioModeProvider.getInstance().getAudioMode();
} public int getSupportedAudio() {
return AudioModeProvider.getInstance().getSupportedModes();
} public void setAudioMode(int mode) { //省略若干行代码
} /**
* Function assumes that bluetooth is not supported.
*/
public void toggleSpeakerphone() {
//省略若干行代码
} public void endCallClicked() {
if (mCall == null) {
return;
} CallCommandClient.getInstance().disconnectCall(mCall.getCallId());
} public void manageConferenceButtonClicked() {
getUi().displayManageConferencePanel(true);
} public void muteClicked(boolean checked) {
Log.d(this, "turning on mute: " + checked); CallCommandClient.getInstance().mute(checked);
} public void holdClicked(boolean checked) {
if (mCall == null) {
return;
} Log.d(this, "holding: " + mCall.getCallId()); CallCommandClient.getInstance().hold(mCall.getCallId(), checked);
} public void mergeClicked() {
CallCommandClient.getInstance().merge();
} public void addCallClicked() {
//省略若干行代码
} public void swapClicked() {
//省略若干行代码
} public void showDialpadClicked(boolean checked) {
//省略若干行代码
} private void updateUi(InCallState state, Call call) {
//省略若干行代码
} private void updateExtraButtonRow() {
//省略若干行代码
} public void refreshMuteState() {
//省略若干行代码
} public interface CallButtonUi extends Ui {
void setEnabled(boolean on);
void setMute(boolean on);
void enableMute(boolean enabled);
void setHold(boolean on);
void showHold(boolean show);
void enableHold(boolean enabled);
void showMerge(boolean show);
void showSwap(boolean show);
void showAddCall(boolean show);
void enableAddCall(boolean enabled);
void displayDialpad(boolean on);
boolean isDialpadVisible();
void setAudio(int mode);
void setSupportedAudio(int mask);
void showManageConferenceCallButton();
void showGenericMergeButton();
void hideExtraRow();
void displayManageConferencePanel(boolean on);
}
}

在InCallUI中,除了InCallActivity,其他界面都是由Fragment负责界面,即,每个Fragment都属于MVP中的View,InCallUI中抽象出了一个BaseFragment来作为所有Fragment的父类,BaseFragment代码如下:

/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/ package com.android.incallui; import android.app.Fragment;
import android.os.Bundle; /**
* Parent for all fragments that use Presenters and Ui design.
*/
public abstract class BaseFragment<T extends Presenter<U>, U extends Ui> extends Fragment { private T mPresenter; abstract T createPresenter(); abstract U getUi(); protected BaseFragment() {
mPresenter = createPresenter();
} /**
* Presenter will be available after onActivityCreated().
*
* @return The presenter associated with this fragment.
*/
public T getPresenter() {
return mPresenter;
} @Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mPresenter.onUiReady(getUi());
} @Override
public void onDestroyView() {
super.onDestroyView();
mPresenter.onUiDestroy(getUi());
}
}

即在BaseFragment或继承自BaseFragment的子Fragment被创建的时候,将V(Fragment)跟P(Presenter)关联,在Fragment被销毁的时候,将V和P解绑。这里我们继续看看CallButtonFragment的代码:

/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/ package com.android.incallui; import android.graphics.drawable.LayerDrawable;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.ImageButton;
import android.widget.PopupMenu;
import android.widget.PopupMenu.OnDismissListener;
import android.widget.PopupMenu.OnMenuItemClickListener;
import android.widget.ToggleButton; import com.android.services.telephony.common.AudioMode; /**
* Fragment for call control buttons
*/
public class CallButtonFragment
extends BaseFragment<CallButtonPresenter, CallButtonPresenter.CallButtonUi>
implements CallButtonPresenter.CallButtonUi, OnMenuItemClickListener, OnDismissListener,
View.OnClickListener, CompoundButton.OnCheckedChangeListener { private ImageButton mMuteButton;
private ImageButton mAudioButton;
private ImageButton mHoldButton;
private ToggleButton mShowDialpadButton;
private ImageButton mMergeButton;
private ImageButton mAddCallButton;
private ImageButton mSwapButton; private PopupMenu mAudioModePopup;
private boolean mAudioModePopupVisible;
private View mEndCallButton;
private View mExtraRowButton;
private View mManageConferenceButton;
private View mGenericMergeButton; @Override
CallButtonPresenter createPresenter() {
// TODO: find a cleaner way to include audio mode provider than
// having a singleton instance.
return new CallButtonPresenter();
} @Override
CallButtonPresenter.CallButtonUi getUi() {
return this;
} @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
} @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//省略若干行代码 mManageConferenceButton = parent.findViewById(R.id.manageConferenceButton);
mManageConferenceButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getPresenter().manageConferenceButtonClicked();
}
});
mGenericMergeButton = parent.findViewById(R.id.cdmaMergeButton);
mGenericMergeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getPresenter().mergeClicked();
}
});
//省略若干行代码
} @Override
public void onActivityCreated(Bundle savedInstanceState) {
//省略若干行代码
} @Override
public void onResume() {
if (getPresenter() != null) {
getPresenter().refreshMuteState();
}
//省略若干行代码
} @Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
} @Override
public void onClick(View view) {
//省略若干行代码
} @Override
public void setEnabled(boolean isEnabled) {
//省略若干行代码
} @Override
public void setMute(boolean value) {
mMuteButton.setSelected(value);
} @Override
public void enableMute(boolean enabled) {
mMuteButton.setEnabled(enabled);
} @Override
public void setHold(boolean value) {
mHoldButton.setSelected(value);
} @Override
public void showHold(boolean show) {
mHoldButton.setVisibility(show ? View.VISIBLE : View.GONE);
} @Override
public void enableHold(boolean enabled) {
mHoldButton.setEnabled(enabled);
} @Override
public void showMerge(boolean show) {
mMergeButton.setVisibility(show ? View.VISIBLE : View.GONE);
} @Override
public void showSwap(boolean show) {
mSwapButton.setVisibility(show ? View.VISIBLE : View.GONE);
} @Override
public void showAddCall(boolean show) {
mAddCallButton.setVisibility(show ? View.VISIBLE : View.GONE);
} @Override
public void enableAddCall(boolean enabled) {
mAddCallButton.setEnabled(enabled);
} @Override
public void setAudio(int mode) {
updateAudioButtons(getPresenter().getSupportedAudio());
refreshAudioModePopup();
} @Override
public void setSupportedAudio(int modeMask) {
updateAudioButtons(modeMask);
refreshAudioModePopup();
} @Override
public boolean onMenuItemClick(MenuItem item) {
//省略若干行代码
} // PopupMenu.OnDismissListener implementation; see showAudioModePopup().
// This gets called when the PopupMenu gets dismissed for *any* reason, like
// the user tapping outside its bounds, or pressing Back, or selecting one
// of the menu items.
@Override
public void onDismiss(PopupMenu menu) {
Log.d(this, "- onDismiss: " + menu);
mAudioModePopupVisible = false;
} /**
* Checks for supporting modes. If bluetooth is supported, it uses the audio
* pop up menu. Otherwise, it toggles the speakerphone.
*/
private void onAudioButtonClicked() {
Log.d(this, "onAudioButtonClicked: " +
AudioMode.toString(getPresenter().getSupportedAudio())); if (isSupported(AudioMode.BLUETOOTH)) {
showAudioModePopup();
} else {
getPresenter().toggleSpeakerphone();
}
} /**
* Refreshes the "Audio mode" popup if it's visible. This is useful
* (for example) when a wired headset is plugged or unplugged,
* since we need to switch back and forth between the "earpiece"
* and "wired headset" items.
*
* This is safe to call even if the popup is already dismissed, or even if
* you never called showAudioModePopup() in the first place.
*/
public void refreshAudioModePopup() {
if (mAudioModePopup != null && mAudioModePopupVisible) {
// Dismiss the previous one
mAudioModePopup.dismiss(); // safe even if already dismissed
// And bring up a fresh PopupMenu
showAudioModePopup();
}
} /**
* Updates the audio button so that the appriopriate visual layers
* are visible based on the supported audio formats.
*/
private void updateAudioButtons(int supportedModes) {
//省略若干行代码
} private boolean isSupported(int mode) {
return (mode == (getPresenter().getSupportedAudio() & mode));
} private boolean isAudio(int mode) {
return (mode == getPresenter().getAudioMode());
} @Override
public void displayDialpad(boolean value) {
mShowDialpadButton.setChecked(value);
if (getActivity() != null && getActivity() instanceof InCallActivity) {
((InCallActivity) getActivity()).displayDialpad(value);
}
} @Override
public boolean isDialpadVisible() {
if (getActivity() != null && getActivity() instanceof InCallActivity) {
return ((InCallActivity) getActivity()).isDialpadVisible();
}
return false;
} @Override
public void displayManageConferencePanel(boolean value) {
if (getActivity() != null && getActivity() instanceof InCallActivity) {
((InCallActivity) getActivity()).displayManageConferencePanel(value);
}
} @Override
public void showManageConferenceCallButton() {
mExtraRowButton.setVisibility(View.VISIBLE);
mManageConferenceButton.setVisibility(View.VISIBLE);
mGenericMergeButton.setVisibility(View.GONE);
} @Override
public void showGenericMergeButton() {
mExtraRowButton.setVisibility(View.VISIBLE);
mManageConferenceButton.setVisibility(View.GONE);
mGenericMergeButton.setVisibility(View.VISIBLE);
} @Override
public void hideExtraRow() {
mExtraRowButton.setVisibility(View.GONE);
}
}

从而,通过Ui,CallButtonUi,BaseFragment,CallButtonFragment,Presenter,CallButtonPresenter将M,V,P分开,让数据,业务,展示分开开发维护,代码变得清晰,每层只需要关注自己的东西就行,这就比我们以前都只在一个Activity或Fragment中糅杂在一起好很多。

如果有需要查看Android源码的童鞋,可以自行到Android官网下载或去下面两个网站进行在线查看。

1,http://androidxref.com

2,http://www.grepcode.com

android MVP模式思考的更多相关文章

  1. android MVP模式介绍与实战

    android MVP模式介绍与实战 描述 MVP模式是什么?MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方:Controller/Presenter负责逻辑的处理,Model提供数 ...

  2. Android MVP模式

    转自http://segmentfault.com/blogs,转载请注明出处Android MVP Pattern Android MVP模式\[1\]也不是什么新鲜的东西了,我在自己的项目里也普遍 ...

  3. Android MVP模式 简单易懂的介绍方式

    主要学习这位大神的博客:简而易懂 Android MVP模式 简单易懂的介绍方式 https://segmentfault.com/a/1190000003927200

  4. Android MVP模式简单易懂的介绍方式 (三)

    Android MVP模式简单易懂的介绍方式 (一) Android MVP模式简单易懂的介绍方式 (二) Android MVP模式简单易懂的介绍方式 (三) 讲完M和P,接下来就要讲V了.View ...

  5. Android MVP模式简单易懂的介绍方式 (二)

    Android MVP模式简单易懂的介绍方式 (一) Android MVP模式简单易懂的介绍方式 (二) Android MVP模式简单易懂的介绍方式 (三) 上一篇文章我们介绍完了Model的创建 ...

  6. Android MVP模式简单易懂的介绍方式 (一)

    Android MVP模式简单易懂的介绍方式 (一) Android MVP模式简单易懂的介绍方式 (二) Android MVP模式简单易懂的介绍方式 (三) 最近正在研究Android的MVP模式 ...

  7. Android MVP模式 谷歌官方代码解读

    Google官方MVP Sample代码解读 关于Android程序的构架, 当前(2016.10)最流行的模式即为MVP模式, Google官方提供了Sample代码来展示这种模式的用法. Repo ...

  8. Android mvp模式、mvvm模式

    MVC和MVP的区别2007年08月08日 星期三 上午 09:23 MVC和MVP到底有什么区别呢? 从这幅图可以看到,我们可以看到在MVC里,View是可以直接访问Model的!从而,View里会 ...

  9. Xamarin.Android MVP模式

    一.简介 随着UI创建技术的功能日益增强,UI层也履行着越来越多的职责.为了更好地细分视图(View)与模型(Model)的功能,让View专注于处理数 据的可视化以及与用户的交互,同时让Model只 ...

随机推荐

  1. 每天学一点Python

    9月11日 1.用List实现Python里的?:条件表达式 ["false","true"][判断条件] 其实就是一个List[0]还是List[1]的问题. ...

  2. css 让两个div重叠

    做网页的时候在div里放了一个别的网页的天气插件,但是点击了会跳到广告页面的,想去网上找个禁止div点击的方法,可是发现没有,用了js的方法好像也没有成功,后来觉得还是用两个层重叠的方法来阻止点击,虽 ...

  3. 规划设计系列3 | SketchUp+实景三维,方案现状一起看

    将SketchUp中建立的模型与实景三维模型进行集成,既可以充分发挥实景三维在地理空间记录方面的优势,又可以去除SketchUp在周边环境设计上的不足. 同时借助Wish3D Earth丰富的场景浏览 ...

  4. MapWindowPoints

    中文名 MapWindowPoints Windows CE 1.0及以上版本 头文件 winuser.h 库文件 user32.lib MapWindowPoints函数把相对于一个窗口的坐标空间的 ...

  5. Hadoop部署启动异常问题排查

    hadoop的日志目录(/home/hadoop/app/hadoop-2.6.4/logs) 1.hadoop启动不正常用浏览器访问namenode的50070端口,不正常,需要诊断问题出在哪里: ...

  6. IntelliJ IDEA创建文件时自动填入作者时间 定制格式

    IntelliJ IDEA创建文件时自动填入作者时间 定制格式 学习了:https://blog.csdn.net/Hi_Boy_/article/details/78205483 学习了:http: ...

  7. browsersync按照官网,然后本地配置后,动态监听时不起作用

    官方API也未曾标注,要添加文件指向 --files 所以解决方案就是: browser-sync start --proxy "tp5.cn" --files "css ...

  8. [JS][jQuery]清空元素html(&quot;&quot;)、innerHTML=&quot;&quot; 与 empty()的差别:关于内容泄露问题

    清空元素html("").innerHTML="" 与 empty()的差别 一.清空元素的差别      1.错误做法一:            $(&quo ...

  9. 学习使用用Eclipse编写java程序

    本文讲解了在Eclipse中完成一个HelloWorld程序的编写过程. 刚刚学习java的同学们可能用 记事本编写java源代码,在命令提示符中完成java程序的编译和运行过程.这样的方法对于学习j ...

  10. ORA-01591错误的原因和处理方法

    http://blog.csdn.net/tclcaojun/article/details/6777022错误代码:ORA-01591 错误原因:使用了分布式事务,造成这个问题的原因很多时候都是由于 ...