VMS移动互联网

WMS Android项目架构演进

2018-06-28  本文已影响126人  mike_fei

前言

随着WMS业务的发展,代码的结构慢慢臃肿,结构开始混乱,层次不清,冗余的代码充斥项目。为了推进业务的快速实现,就到了必须要解决上述的问题的时期,于是就开始浩浩荡荡调整项目结构。

一.架构要实现的目的

从网上许多优秀的方案来看,无论是架构还是代码上,分层都是三层:视图层(Presentation Layer)、控制层(Domain Layer)、数据流层(Data Layer)。
层级之间通过添加接口层作为分隔实现解耦。

1.层次分明,各层级之间都不管对方如何实现,只关注结果;
2.在视图层(Presentation Layer)使用MVP架构,使原本臃肿的Activity(或Fragment)变得简单,其处理方法都交给了Presenter。
3.易于做测试,只要基于每个模块单独做好单元测试就能确保整体的稳定性。
4.易于快速迭代,基于代码的低耦合,只需在业务逻辑上增加接口,然后在相应的层级分别实现即可,丝毫不影响其他功能。

二.目录结构

整体目录结构
项目目录结构

commonui—————————————————自定义view实现
event—————————————————EventBus事件常量
injecter—————————————————实现Dragger注入Bridge
module—————————————————业务模块
       activity———————UI页面
       adapter———————ListView列表适配器
       domain———————存放java model对象
       interfaces———————存放View和Layer 的之间的纽带接口
       net———————RetorfitService请求接口
       presenter———————存放presenter
       request———————存放请求参数model对象
util—————————————————–业务层公共方法

三.框架整体结构

未命名文件.png

四.MVP 的设计与实现

MVP 架构能解决现在所面临过的很多问题,下图呈现的是WMS项目MVP 方案:


MVP架构

五.架构解析

从界面—>业务—>网络数据—–>交互,这样的层次讲解。

1)UI层

UI层其实比较简单,主要就是用到面向对象的封装,ActivityBase为基类,同时ActivityBase实现MvpView接口,这接口的作用是:页面交互封装。MvpView是底层功能实现Presenter跟UI层Activity的衔接层,ActivityBase类会实现该接口,把复杂的功能实现抽象出去,在通过MvpView接口回调回来,轻量化基类。

public abstract class ActivityBase extends SupportActivity implements MvpView {

    private Toolbar mToolbar;
    private FrameLayout mContentContainer;
    private TextView mTitleBarTitle;
    private View mContentView;
    protected DialogLoadingHelper mLoadingHelper;
    private View mTitleBarBack;
    private RelativeLayout mTitleBarLeft;
    protected PageLoadingHelper mPageLoadingHelper;
    private SwipeRefreshLayout mSwipeRefresh;
    private TextView mTitleBarRightText;
    private View mTitleBar;
    private View.OnClickListener mLeftClickListener = null;

    public void setOnLeftClickListener(View.OnClickListener onClickListner) {
        mLeftClickListener = onClickListner;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        setComponent();
        super.onCreate(savedInstanceState);
        ActivityCollector.addActivity(this);
        setContentView(getBaseContentLayoutResId());
        mLoadingHelper = new DialogLoadingHelper(this);
        initBaseView();
        mPageLoadingHelper = new PageLoadingHelper(mContentContainer, this, mContentView);
        initBaseToolBar();
        initActivity(mContentView);
        initPresenter();
        initInstanceState(savedInstanceState);
        showContent();
    }

    /**
     * @return void
     * @throws
     * @Title: exitSystem
     * @Description: (退出系统/绑定退出按钮)
     */
    protected void exitSystem() {
        ActivityCollector.finishAll();
        finish();
        System.exit(0);
    }

    protected void finishAll() {
        ActivityCollector.finishAll();
        finish();
    }

    protected int getBaseContentLayoutResId() {
        return useSwipeRefreshLayout() ?
                R.layout.activity_base_refresh_layout : R.layout.activity_base_layout;
    }

    protected void setRefreshComplete() {
        if (useSwipeRefreshLayout()) {
            mSwipeRefresh.setRefreshing(false);
        }
    }

    /**
     * 初始化保存状态
     *
     * @param savedInstanceState
     */
    protected void initInstanceState(Bundle savedInstanceState) {
    }

    private void initBaseView() {
        mContentContainer = (FrameLayout) super.findViewById(R.id.content_container);
        mContentView = getLayoutInflater().inflate(getContentLayoutResId(), mContentContainer, false);
        if (useSwipeRefreshLayout()) {
            mSwipeRefresh = (SwipeRefreshLayout) super.findViewById(R.id.refresh);
            mSwipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
                @Override
                public void onRefresh() {
                    reLoadData();
                }
            });
        }
    }

    /**
     * 重写此方法  下拉刷新
     */
    public void reLoadData() {
    }

    /**
     * 如果之类想使用下来刷新功能,需要重写 返回true
     *
     * @return
     */
    public boolean useSwipeRefreshLayout() {
        return false;
    }

    @Override
    public View findViewById(@IdRes int id) {
        return mContentView.findViewById(id);
    }


    private void initBaseToolBar() {
        mToolbar = (Toolbar) super.findViewById(R.id.toolbar);
        setNormalTitlebar();
    }

    protected abstract int getContentLayoutResId();

    public Toolbar getToolbar() {
        return mToolbar;
    }

    /**
     * 初始化注入器
     */
    protected void setComponent() {
    }

    protected void initPresenter() {
    }

    ;

    protected abstract void initActivity(View pView);

    /**
     * 初始化标准的标题栏
     */
    public void setNormalTitlebar() {
        View titleBar = getLayoutInflater().inflate(R.layout.view_simple_title_bar, null);
        mTitleBarBack = titleBar.findViewById(R.id.titlebar_back);
        mTitleBarLeft = titleBar.findViewById(R.id.titlebar_left);
        mTitleBar = titleBar.findViewById(R.id.title_bar);
        mTitleBarTitle = (TextView) titleBar.findViewById(R.id.titlebar_title);
        mTitleBarRightText = (TextView) titleBar.findViewById(R.id.titlebar_right_text);
        mTitleBarBack.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (null != mLeftClickListener) {
                    mLeftClickListener.onClick(v);
                } else {
                    finish();
                }
            }
        });
        setCustomTitleBar(titleBar);
    }

    public View getTitleBarView() {
        return mTitleBar;
    }

    public void setLeftView(View view) {
        if (null != view) {
            setBackIconVisiable(false);
            mTitleBarLeft.removeAllViews();
            RelativeLayout.LayoutParams layoutParams =
                    new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                            ScreenUtil.dip2px(this, 56));

            mTitleBarLeft.addView(view, layoutParams);
            mTitleBarLeft.setVisibility(View.VISIBLE);
        }
    }

    /**
     * 添加自定义标题栏
     */
    public void setCustomTitleBar(View titlebarLayout) {
        mToolbar.removeAllViews();  // 先清除掉之前可能加入的
        Toolbar.LayoutParams lp = new Toolbar.LayoutParams(
                Toolbar.LayoutParams.MATCH_PARENT,
                Toolbar.LayoutParams.MATCH_PARENT);//传入的布局,覆盖整个Toolbar
        mToolbar.addView(titlebarLayout, lp);
    }

    public void hideTitleBar() {
        mToolbar.setVisibility(View.GONE);
    }

    @Override
    protected void onPause() {
        super.onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        // 强制竖屏
        if (getRequestedOrientation() != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        }
    }

    @Override
    /**
     * 设置标题
     */
    public void setTitle(int TitleId) {
        if (mTitleBarTitle != null) {
            mTitleBarTitle.setText(TitleId);
        }
    }

    @Override
    public void reLogin() {

    }

    @Override
    public void multipointLogin() {

    }

    /**
     * 如果使用了下拉刷新,重新加载数据成功后需要调此方法
     */
    public void showContent() {
        mPageLoadingHelper.showContent();
        setRefreshComplete();
    }

    protected SwipeRefreshLayout getRefreshLayout() {
        return mSwipeRefresh;
    }

    public void toActivity(Class<?> cls) {
        startActivity(new Intent(this, cls));
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityCollector.removeActivity(this);
        mLoadingHelper.stopWaiting();
    }

}

2)业务

PresenterMain 为登录的业务实现类,他需要做两件事:1、业务处理。2.通知页面数据刷新。Presenter与页面交互是通过接口实现的,这边通过继承基类BasePresenter,从而实现接口attachView(V view),这边的view是个泛型,在这里,他其实是IUMainMainActivity会实现这个接口,这样 Presenter通过这个接口通知页面刷新就只要就可以了。


public class PresenterMain extends BasePresenter<IUIMain> {

    RetrofitApi mRetrofitApi;

    @Inject
    public PresenterMain(RetrofitApi retrofitApi) {
        mRetrofitApi = retrofitApi;
    }

    public void getWarehouse(GetWareHouseParameter parameter) {
        if (null == parameter) {
            getMvpView().showToast(R.string.resource_error);
            return;
        }
        if (null != mRetrofitApi) {
            mRetrofitApi.getWarehouse(parameter)
                    .compose(XgSchedulerApplier.<GetWareHouseRespData>DefaultSchedulers())
                    .subscribe(new XgSubscriber<GetWareHouseRespData>(this,
                            true) {
                        @Override
                        public boolean onFailed(Throwable responeThrowable) {
                            getMvpView().showToast(responeThrowable.getMessage());
                            return false;
                        }

                        @Override
                        public void onSucess(GetWareHouseRespData getWareHouseRespData) {
                            if (null != getWareHouseRespData && getWareHouseRespData.success) {
                                if (null != getWareHouseRespData.body) {
                                    getMvpView().getWarehouse(getWareHouseRespData.body.warehouseList);
                                } else {
                                    getMvpView().showToast(R.string.resource_error);
                                }
                            } else if (!TextUtils.isEmpty(getWareHouseRespData.resultMsg)) {
                                getMvpView().showToast(getWareHouseRespData.resultMsg);
                            }
                        }
                    });
        }
    }

    public void loginResult(String username, String password) {
        if (TextUtils.isEmpty(username)) {
            getMvpView().showToast(R.string.username_empty);
            return;
        }
        if (TextUtils.isEmpty(password)) {
            getMvpView().showToast(R.string.password_empty);
            return;
        }

        if (null != mRetrofitApi) {
            mRetrofitApi.login(username, password)
                    .compose(XgSchedulerApplier.<LoginRespData>DefaultSchedulers())
                    .subscribe(new XgSubscriber<LoginRespData>(this, true) {
                        @Override
                        public boolean onFailed(Throwable responeThrowable) {
                            getMvpView().showToast(responeThrowable.getMessage());
                            return false;
                        }

                        @Override
                        public void onSucess(LoginRespData loginResponse) {
                            if (null != loginResponse && loginResponse.success) {
                                getMvpView().loginResult(loginResponse.body);
                            } else if (!TextUtils.isEmpty(loginResponse.resultMsg)) {
                                getMvpView().showToast(loginResponse.resultMsg);
                            }
                        }
                    });
        }
    }

}

3)网络数据

public interface RetrofitApi {
  @GET("PDALogin/validate/{username}/{password}")
  @Headers({"Content-Type: application/json", "Accept: application/json"})
  Observable<LoginRespData> login(@Path("username") String username, @Path("password") String password);
|
 mRetrofitApi.login(username, password)
                    .compose(XgSchedulerApplier.<LoginRespData>DefaultSchedulers())
                    .subscribe(new XgSubscriber<LoginRespData>(this, true) {}
 mRetrofitApi.login(username, password)
                    .compose(XgSchedulerApplier.<LoginRespData>DefaultSchedulers())
                    .subscribe(new XgSubscriber<LoginRespData>(this, true) {
                        @Override
                        public boolean onFailed(Throwable responeThrowable) {
                            getMvpView().showToast(responeThrowable.getMessage());
                            return false;
                        }

                        @Override
                        public void onSucess(LoginRespData loginResponse) {
                            if (null != loginResponse && loginResponse.success) {
                                getMvpView().loginResult(loginResponse.body);
                            } else if (!TextUtils.isEmpty(loginResponse.resultMsg)) {
                                getMvpView().showToast(loginResponse.resultMsg);
                            }
                        }
                    });

4)交互

我们这边用MVP代替了MVC,从上面activity可以看出,activity只做两件事:1、view的创建。2、用户交互。那业务逻辑我们放在哪里呢?这里我们引入Presenter层,用来专门处理业务逻辑,并通过MvpView接口实现跟activity的交互。
PresenterView交互是通过接口。所以我们这里需要定义一个IUIMain ,难点就在于应该有哪些方法,我们这个是登录页面,其实有哪些功能,就应该有哪些方法,比如登录接口返回数据,加载仓库列表返回的数据,这些都要通知ui(Activity)去更新。所以定义了如下方法:

public interface IUIMain extends MvpView {
     public void loginResult(LoginData data);
     public void getWarehouse(ArrayList<WarehouseData> warehouseList);
}

通知到Activity做出更新最后实现数据返回后的交互

 @Override
    public void getWarehouse(ArrayList<WarehouseData> warehouseList) {
        if (null == warehouseList) {
            showToast(R.string.data_empty);
            return;
        }
        mListAdapter.setData(warehouseList, mIndex);
        showWarehousePopwindow();
    }

    String mUserid;

    @Override
    public void loginResult(LoginData data) {
        if (null == data) {
            showToast(getString(R.string.data_empty));
        } else {
            mUserid = data.userId;
            SharedPStoreUtil.getInstance().setUserId(data.userId);
            SharedPStoreUtil.getInstance().setUserName(data.userName);
            SharedPStoreUtil.getInstance().setToken(data.token);
            SharedPStoreUtil.getInstance().setWarehouseIds(data.warehouseIds);
            SharedPStoreUtil.getInstance().setOwnerIds(data.ownerIds);
            HashMap<String,String> map = new HashMap<String,String>();
            map.put("ownerIds",data.ownerIds);
            map.put("warehouseIds",data.warehouseIds);
            XgNetWork.get().addCommonParams(map);
            String warehouseCode = SharedPStoreUtil.getInstance().getWarehouseCode();
            String warehouseId = SharedPStoreUtil.getInstance().getWarehouseId();
            MyApplication.getMyAppInstance().setTokenHeader();
            if (TextUtils.isEmpty(warehouseCode) && TextUtils.isEmpty(warehouseId)) {
                //jump to warehouse
                GetWareHouseParameter parameter = new GetWareHouseParameter();
                mPresenterMain.getWarehouse(parameter);
            } else {
                ARouterUtils.navigation(ARouterUtils.PATH_SYSTEM_HOME, mUserid);
                finish();
            }
        }
    }
上一篇下一篇

猜你喜欢

热点阅读