程序员Android知识Android开发

App架构之旅

2017-04-24  本文已影响947人  zuoweitan

一个不拘泥于形式的MVP架构


一个满足下图逻辑的架构,我们就可以称之为是MVP架构:

MVP

这里表示的逻辑是:

  1. 各部分之间的通信,都是双向的。
  2. View 与 Model 不发生联系,都通过 Presenter 传递。

将这种逻辑应用于Android App架构设计上,它的表示形式如下:

MAL

说明:

  1. Activity只负责业务逻辑处理,并且充当了Presenter的角色。
  2. Layout负责解析布局文件和一些UI层面的逻辑判断,提供View Interface。
  3. Model用来操作数据,包括存储,搜索等。

这里将这种形式的MVP简称为MLA。

MLA的实际应用


【以下所有实例均来自于我开源项目】

1.Activity

为了很好的充当一个Presenter的角色,它需要能够很好,很方便的跟Layout通信,它们之间的障碍是通过以下两步打破的:

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通信的:

  1. 由于Activity持有了View Interface,如onSearchItemClickListener,则当Layout有相应的用户操作时,则会通知到Activity去处理相关的业务逻辑,这里的UI逻辑与业务逻辑彻底分开了
  2. 而当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并且努力的去扬其长避其短,甚至可以改造它,完善它。

上一篇 下一篇

猜你喜欢

热点阅读