App架构之旅
一个不拘泥于形式的MVP架构
一个满足下图逻辑的架构,我们就可以称之为是MVP架构:
MVP这里表示的逻辑是:
- 各部分之间的通信,都是双向的。
- View 与 Model 不发生联系,都通过 Presenter 传递。
将这种逻辑应用于Android App架构设计上,它的表示形式如下:
MAL说明:
- Activity只负责业务逻辑处理,并且充当了Presenter的角色。
- Layout负责解析布局文件和一些UI层面的逻辑判断,提供View Interface。
- Model用来操作数据,包括存储,搜索等。
这里将这种形式的MVP简称为MLA。
MLA的实际应用
【以下所有实例均来自于我开源项目】
1.Activity
为了很好的充当一个Presenter的角色,它需要能够很好,很方便的跟Layout通信,它们之间的障碍是通过以下两步打破的:
- Activity天生持有一个Layout的实例。
- Layout主动加载Activity关心的所有View Interface。
public class BaseActivity<T extends BaseLayout> extends FragmentActivity {
/**
* mLayout与Layout通信
*/
protected T mLayout;
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
...
checkAndInstallEatMark();
...
}
private List<IEater> eater = new ArrayList<>();
private void checkAndInstallEatMark() {
Class<?>[] declaredClasses = getClass().getDeclaredClasses();
if (declaredClasses != null) {
for (Class<?> declaredClass : declaredClasses) {
EatMark eatMark = declaredClass.getAnnotation(EatMark.class);
if (eatMark != null) {
IEater iEater = null;
try {
Constructor<?> constructor = declaredClass.getDeclaredConstructor(this.getClass());
constructor.setAccessible(true);
iEater = (IEater) constructor.newInstance(this);
} catch (InstantiationException e) {
NLog.e(TAG, "checkAndInstallEatMark failed :",e);
} catch (IllegalAccessException e) {
NLog.e(TAG, "checkAndInstallEatMark failed :",e);
} catch (NoSuchMethodException e) {
NLog.e(TAG, "checkAndInstallEatMark failed :",e);
} catch (InvocationTargetException e) {
NLog.e(TAG, "checkAndInstallEatMark failed :",e);
}
if (iEater != null) {
eater.add(iEater);
EaterManager.getInstance().registerEater(eatMark.action(),iEater);
}
}
}
} else {
NLog.i(TAG, "No EatMarks");
}
}
@Override
protected void onResume() {
super.onResume();
if (mLayout != null) {
mLayout.onResume();
}
}
@Override
protected void onStop() {
super.onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mLayout != null) {
mLayout.onDestroy();
}
...
unInstallEatMark();
}
private void unInstallEatMark() {
for (IEater iEater : eater) {
EaterManager.getInstance().unRegisterEater(iEater);
}
eater.clear();
eater = null;
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
if (mLayout != null) {
mLayout.onWindowAttached();
}
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mLayout != null) {
mLayout.onWindowDetached();
}
}
protected View getBaseView(){
return getWindow().getDecorView();
}
@Override
public void setContentView(int layoutResID) {
super.setContentView(layoutResID);
mLayout = generateLayout();
if (mLayout != null) {
bindAllInterfaces();
LayoutIdBinder.LAYOUT.bindViewToLayout(mLayout);
mLayout.onContentViewCreate(getBaseView());
}
}
/**
* 绑定所有关心的View Interface
*/
private void bindAllInterfaces() {
Field[] declaredFields = getClass().getDeclaredFields();
if (declaredFields != null) {
for (Field injectField : declaredFields) {
InterfaceInject annotation = injectField.getAnnotation(InterfaceInject.class);
if (annotation == null) {
continue;
}
String name = annotation.bindName();
try {
Field field = mLayout.getClass().getDeclaredField(name);
field.setAccessible(true);
injectField.setAccessible(true);
field.set(mLayout,injectField.get(this));
} catch (NoSuchFieldException e) {
NLog.e(TAG,"bindAllInterfaces failed : ",e);
} catch (IllegalAccessException e) {
NLog.e(TAG,"bindAllInterfaces failed : ",e);
}
}
}
}
private T generateLayout() {
LayoutInject layoutInject = getClass().getAnnotation(LayoutInject.class);
if (layoutInject != null){
String layoutName = layoutInject.name();
try {
Class layoutC = Class.forName(PREFIX + layoutName);
Constructor constructor = layoutC.getConstructor(View.class);
constructor.setAccessible(true);
return (T) constructor.newInstance(getBaseView());
}catch (InvocationTargetException ex){
NLog.e(TAG,"generateLayout failed : ",ex.getTargetException());
} catch (Exception e) {
NLog.e(TAG,"generateLayout failed : ",e);
}
}
return null;
}
}
从代码中可以看到,Activity还关注了所有想要消费的事件,这里是通过向EaterManager注册IEater实现的。
EaterManager是一个仿Android LocalBroadcastReceiver的模块间通信的工具。
这样我们一个具体的Activity就可以实现的非常简单了:
@LayoutInject(name = "AddFriendLayout")
public class AddFriendActivity extends BaseActivity<AddFriendLayout>{
public static void start(Context c){
start(c,AddFriendActivity.class);
}
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
setContentView(R.layout.activity_add_friend_layout);
}
@InterfaceInject(bindName = "onItemClickListener")
private AddFriendLayout.OnItemClickListener onItemClickListener
= new AddFriendLayout.OnItemClickListener() {
@Override
public void onScanQr() {
scanQr();
}
@Override
public void onAddAddress() {
addAddress();
}
};
@InterfaceInject(bindName = "onSearchItemClickListener")
AddFriendLayout.OnSearchItemClickListener onSearchItemClickListener = ()->{
SearchActivity.startWithAnimation(this,mLayout.getShareElement()
,Constants.SearchType.SEARCH_FRIEND_TYPE);
};
}
我们看下它如何与Layout通信的:
- 由于Activity持有了View Interface,如onSearchItemClickListener,则当Layout有相应的用户操作时,则会通知到Activity去处理相关的业务逻辑,这里的UI逻辑与业务逻辑彻底分开了
- 而当Activity需要去更新Layout时,则可以直接通过mLayout直接操作Layout
2.Layout
public class BaseLayout implements View.OnClickListener{
protected View mRootView;
protected Context mCtx;
protected Context mAppCtx;
protected Resources mRes;
protected LayoutInflater mLayoutInflater;
protected OnLayoutEventListener mOnLayoutEventListener;
@Override
public void onClick(View v) {
}
public interface OnLayoutEventListener <T extends BaseLayout>{
void onContentConfirmed(T t);
}
public BaseLayout (View rootView){
mRootView = rootView;
mCtx = mRootView.getContext();
mRes = mRootView.getResources();
mLayoutInflater = LayoutInflater.from(mCtx);
}
public View findViewById(int id){
return mRootView.findViewById(id);
}
public void onContentViewCreate(View view){
}
public void onWindowAttached(){}
public void onWindowDetached(){}
public void onResume(){}
public void onStop(){}
public void onDestroy(){
}
}
BaseLayout实现很简单,主要提供了几个常用的对象。我们来看一个具体的Layout类:
public class AddFriendLayout extends BaseLayout{
@BindView(id = R.id.scanQrRl, boundClick = true)
private View scanQrV;
@BindView(id = R.id.addressRl, boundClick = true)
private View addressV;
@BindView(id = R.id.searchLt)
private View searchLt;
@BindView(id = R.id.titleBar)
private BGATitlebar titlebar;
public interface OnTitleActionListener{
void onBack();
void showQR();
}
public interface OnItemClickListener{
void onScanQr();
void onAddAddress();
}
public interface OnSearchItemClickListener{
void onSearch();
}
public AddFriendLayout(View rootView) {
super(rootView);
}
private OnTitleActionListener onTitleActionListener;
private OnItemClickListener onItemClickListener;
private OnSearchItemClickListener onSearchItemClickListener;
@Override
public void onContentViewCreate(View view) {
super.onContentViewCreate(view);
titlebar.setDelegate(new BGATitlebar.BGATitlebarDelegate(){
@Override
public void onClickLeftCtv() {
super.onClickLeftCtv();
if (onTitleActionListener != null) {
onTitleActionListener.onBack();
}
}
@Override
public void onClickRightCtv() {
super.onClickRightCtv();
if (onTitleActionListener != null) {
onTitleActionListener.showQR();
}
}
});
searchLt.setOnClickListener(new ViewClickHelper(false) {
@Override
public void onRealClick(View v) {
if (onSearchItemClickListener != null) {
onSearchItemClickListener.onSearch();
}
}
});
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.scanQrRl:
if (onItemClickListener != null) {
onItemClickListener.onScanQr();
}
break;
case R.id.addressRl:
if (onItemClickListener != null) {
onItemClickListener.onAddAddress();
}
break;
}
}
public View getShareElement(){
return searchLt;
}
}
它提供了所涉及到的所有的View Interface,如OnTitleActionListener。
这样一来Layout就只用关注自己的UI层面的操作与变化,并且通过这样的分层,我们可以很好的将UI层的坏数据做一次简单的过滤
这里有一个问题,如果Layout与Activity要传递的层次太多了怎么办?是不是需要写很多接口来传递?
其实上面提到的EaterManager就可以很好的解决这个问题,并且Activity天生就能够通过checkAndInstallEatMark集成IEater,通过EaterManager就可以很好的将任何信息传递给指定的Activity,看如下代码【这里取一个Fragment的例子,道理跟Activity一样】:
@LayoutInject(name = "AddressFragmentLayout")
public class AddressFragment extends LazyFragment<AddressFragmentLayout> {
private Fragment[] mFragments;
private AddressFragmentSub1 addressFragmentSub1;
private AddressFragmentSub2 addressFragmentSub2;
private AddressFragmentSub3 addressFragmentSub3;
private int mIndex;
@Override
public void onAttach(Context context) {
super.onAttach(context);
addressFragmentSub1 = new AddressFragmentSub1();
addressFragmentSub2 = new AddressFragmentSub2();
addressFragmentSub3 = new AddressFragmentSub3();
mFragments = new Fragment[]{addressFragmentSub1,addressFragmentSub2,addressFragmentSub3};
}
@Override
protected void onViewCreated() {
super.onViewCreated();
showContent();
init();
}
private void showContent() {
getActivity().getSupportFragmentManager().beginTransaction()
.add(mLayout.getContentId(), addressFragmentSub1)
.add(mLayout.getContentId(), addressFragmentSub2)
.add(mLayout.getContentId(), addressFragmentSub3)
.hide(addressFragmentSub2)
.hide(addressFragmentSub3)
.show(addressFragmentSub1).commit();
mIndex = 0;
}
private void init() {
mLayout.setOnTabClickListener(new AddressFragmentLayout.OnTabClickListener() {
@Override
public void onTabClick(int index) {
switchTab(index);
}
});
}
private void switchTab(int index) {
if (index != mIndex) {
FragmentTransaction trx = getActivity().getSupportFragmentManager()
.beginTransaction();
trx.hide(mFragments[mIndex]);
if (!mFragments[index].isAdded()) {
trx.add(mLayout.getContentId(), mFragments[index]);
}
trx.show(mFragments[index]).commit();
}
mIndex = index;
}
@Override
protected void lazyLoad() {
}
@Override
protected int getContentLayout() {
return R.layout.fragment_address_layout;
}
@EatMark(action = EaterAction.ACTION_DO_INVITATION)
public class InvitateListener extends AbstractHandler<InvitationParam> {
@Override
public void doJobWithParam(ParamWrap<InvitationParam> paramWrap) {
InvitationParam param = paramWrap.getParam();
if (param.justRefresh){
FriendsManager.getInstance().countUnreadRequests(null);
} else {
FriendsManager.getInstance().unreadRequestsIncrement();
updateNewRequestBadge();
}
}
}
@EatMark(action = EaterAction.ACTION_ON_ADDRESS)
public class RequestCountUpdate extends AbstractHandler<UnReadRequestCountParam>{
@Override
public void doJobWithParam(ParamWrap<UnReadRequestCountParam> paramWrap) {
updateNewRequestBadge();
}
}
private void updateNewRequestBadge() {
AddressFragmentLayout.Tab tab = mLayout.get(2);
tab.showBadge(FriendsManager.getInstance().hasUnreadRequests());
}
@Override
public void onResume() {
super.onResume();
updateNewRequestBadge();
}
}
以上Fragment通过@EatMark注册了它关心的两种事件,这样当事件发生时,这里就得到通知了。
关于Model层的交互,我这里就不展开了,有需要的就读源码【源码等整理完成就会发布到github】
总结
一种架构,了解其思想后,就不必拘泥于形式。你可以实现自己的MVP并且努力的去扬其长避其短,甚至可以改造它,完善它。