WMS Android项目架构演进
前言
随着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架构
- View Layer: 只负责 UI 的绘制呈现,包含 Fragment 和一些自定义的 UI 组件,View 层需要实现 ViewInterface 接口。Activity 在项目中不再负责 View 的职责,仅仅是一个全局的控制者,负责创建 View 和 Presenter 的实例;
-
Model Layer: 负责检索、存储、操作数据,包括来自网络、数据库、磁盘文件和
SharedPreferences
的数据; - Presenter Layer: 作为 View Layer 和 Module Layer 的之间的纽带,它从 Model 层中获取数据,然后调用 View 的接口去控制 View;
-
Contract: 统一管理
View
和Presenter
的接口,使得某一功能模块的接口能更加直观的呈现出来,这样做是有利于后期维护的。
五.架构解析
从界面—>业务—>网络数据—–>交互,这样的层次讲解。
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是个泛型,在这里,他其实是IUMain
,MainActivity
会实现这个接口,这样 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
的交互。
Presenter
与View
交互是通过接口。所以我们这里需要定义一个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();
}
}
}