SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
boolean isLogin = preferences.getBoolean(“isLogin”, false);
if (isLogin) {
mLoginText.setVisibility(View.GONE);
} else {
mLoginText.setVisibility(View.VISIBLE);
}
mVipImageView = (ImageView) findViewById(R.id.iv_vip);
if (getIntent().getBooleanExtra(“isShowVip”, false)) {
mVipImageView.setVisibility(View.VISIBLE);
mVipImageView.setImageResource(R.drawable.ic_vip);
} else {
mVipImageView.setVisibility(View.GONE);
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.tv_login:
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
String userName = preferences.getString(“userName”, “”);
String userPassword = preferences.getString(“userPassword”, “”);
if (!TextUtils.isEmpty(userName) && !TextUtils.isEmpty(userPassword)) {
sendLoginRequest(userName, userPassword);
}
break;
default:
break;
}
}
private void sendLoginRequest(String userName, String password) {
String url = “http://xx.xxx.xxx”;
StringHashMap requestParams = new StringHashMap();
requestParams.put(“userName”, userName);
requestParams.put(“password”, password);
HttpDataManager.post(url, requestParams, new HttpCallback() {
@Override
public void onSuccess(String result, Object tag) {
mMyAttentionLayout.setVisibility(View.GONE);
mMyAttentionLayout.setVisibility(View.GONE);
LoginResultBean loginResult = JsonUtils.jsonToObject(result, LoginResultBean.class);
if (loginResult != null) {
if (loginResult.getErrCode() == 200) {
mMyAttentionLayout.setVisibility(View.VISIBLE);
mMyAttentionLayout.setVisibility(View.VISIBLE);
} else {
mLoginText.setText(“登录失败”);
}
} else {
mLoginText.setText(“登录失败”);
}
}
@Override
public void onError(String result, Object tag) {
mLoginText.setText(“登录失败”);
}
}, 100);
}
}
这段代码是我随意编写的,本身不具有任何的具体含义,只是为了表达一些情况,布局文件就不贴了。这段代码中有大量由数据驱动的UI状态的改变,包括显隐、内容设置等,这些数据来自不同的源:本地数据库、SharedPreferences、网络、页面传值等,这些都是跟UI无关的操作,也就是我们需要从View层提取的那部分代码,上面的代码比较简单,你应该很容易就能分清哪些是纯UI的操作:
我们现在要做的很明确了,就是把框起来的代码留在当前的MainActivity中,而把剩余的代码全部挪到Presenter当中:
第一步提取变化的View接口:
public interface IMainView {
void setUserNameText(String str);
void setSexText(String str);
void setLoginText(String str);
void setLoginTextVisibility(boolean visibility);
void setIsShowVipImage(boolean isShow);
void setMyAttentionLayoutVisibility(boolean visibility);
void setMyCommentLayoutVisibility(boolean visibility);
Context getContext();
}
第二步业务相关的逻辑移到Presenter中:
public class MainPresenter {
private IMainView mMainView;
private Bundle arguments;
public MainPresenter(IMainView mainView) {
this.mMainView = mainView;
}
public void setArguments(Bundle arguments) {
this.arguments = arguments;
}
public Bundle getArguments() {
return arguments;
}
public void init() {
User savedUser = LocalDataManager.getSavedUser();
if (savedUser != null) {
if (!TextUtils.isEmpty(savedUser.getName())) {
mMainView.setUserNameText(savedUser.getName());
} else {
mMainView.setUserNameText(“匿名用户”);
}
if (savedUser.getSex() == 1) {
mMainView.setSexText(“男”);
} else if (savedUser.getSex() == 2) {
mMainView.setSexText(“女”);
}
}
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mMainView.getContext());
boolean isLogin = preferences.getBoolean(“isLogin”, false);
if (isLogin) {
mMainView.setLoginTextVisibility(false);
} else {
mMainView.setLoginTextVisibility(true);
}
if (getArguments().getBoolean(“isShowVip”, false)) {
mMainView.setIsShowVipImage(true);
} else {
mMainView.setIsShowVipImage(false);
}
}
public void sendLoginRequest() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mMainView.getContext());
String userName = preferences.getString(“userName”, “”);
String userPassword = preferences.getString(“userPassword”, “”);
if (!TextUtils.isEmpty(userName) && !TextUtils.isEmpty(userPassword)) {
sendLoginRequest(userName, userPassword);
}
}
private void sendLoginRequest(String userName, String password) {
String url = “http://xx.xxx.xxx”;
StringHashMap requestParams = new StringHashMap();
requestParams.put(“userName”, userName);
requestParams.put(“password”, password);
HttpDataManager.post(url, requestParams, new HttpCallback() {
@Override
public void onSuccess(String result, Object tag) {
mMainView.setMyAttentionLayoutVisibility(false);
mMainView.setMyCommentLayoutVisibility(false);
LoginResultBean loginResult = JsonUtils.jsonToObject(result, LoginResultBean.class);
if (loginResult != null) {
if (loginResult.getErrCode() == 200) {
mMainView.setMyAttentionLayoutVisibility(true);
mMainView.setMyCommentLayoutVisibility(true);
} else {
mMainView.setLoginText(“登录失败”);
}
} else {
mMainView.setLoginText(“登录失败”);
}
}
@Override
public void onError(String result, Object tag) {
mMainView.setLoginText(“登录失败”);
}
}, 100);
}
}
第三步MainActivity实现定义的View接口并调用Presenter:
public class MainActivity extends Activity implements View.OnClickListener, IMainView {
private TextView mUserNameText;
private TextView mSexText;
private LinearLayout mAdsLayout;
private LinearLayout mNewsLayout;
private TextView mLoginText;
private LinearLayout mMyAttentionLayout;
private LinearLayout mMyCommentLayout;
private ImageView mVipImageView;
private MainPresenter mMainPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mMainPresenter = new MainPresenter(this);
mMainPresenter.setArguments(getIntent().getExtras());
initView();
}
private void initView() {
mUserNameText = (TextView) findViewById(R.id.tv_user_name);
mSexText = (TextView) findViewById(R.id.tv_sex);
mLoginText = (TextView) findViewById(R.id.tv_login);
mLoginText.setOnClickListener(this);
mMyAttentionLayout = (LinearLayout) findViewById(R.id.ll_my_attention);
mMyCommentLayout = (LinearLayout) findViewById(R.id.ll_my_comment);
mVipImageView = (ImageView) findViewById(R.id.iv_vip);
mMainPresenter.init();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.tv_login:
mMainPresenter.sendLoginRequest();
break;
default:
break;
}
}
@Override
public void setUserNameText(String str) {
mUserNameText.setText(str);
}
@Override
public void setSexText(String str) {
mSexText.setText(str);
}
@Override
public void setLoginText(String str) {
mLoginText.setText(str);
}
@Override
public void setLoginTextVisibility(boolean visibility) {
mLoginText.setVisibility(visibility ? View.VISIBLE: View.GONE);
}
@Override
public void setIsShowVipImage(boolean isShowVipImage) {
if (isShowVipImage) {
mVipImageView.setVisibility(View.VISIBLE);
mVipImageView.setImageResource(R.drawable.ic_vip);
} else {
mVipImageView.setVisibility(View.GONE);
}
}
@Override
public void setMyAttentionLayoutVisibility(boolean visibility) {
mMyAttentionLayout.setVisibility(visibility ? View.VISIBLE: View.GONE);
}
@Override
public void setMyCommentLayoutVisibility(boolean visibility) {
mMyCommentLayout.setVisibility(visibility ? View.VISIBLE: View.GONE);
}
@Override
public Context getContext() {
return this;
}
}
这里我们需要定义的跟View交互的接口方法数量可能有点多,几乎是你每改一处就要增加一个,然后在Presenter当中去调用,显得很麻烦,但是我们也有方法能够减少这类麻烦(后续文章中会介绍Presenter与View交互的可以避免这些)。
另外,上述代码你可能关心的一个问题是,原来if-else也算是一种逻辑,这是当然的,上面的代码只是为了示例判断都比较简单,实际中可能会比较复杂,比如你的判断条件可能是一个综合判断条件,由多个判断因素共同决定,而这些因素又可能来自不同的数据源,比如你可能需要从本地取一个值然后去&&前一个页面传递过来的值,最后你还得调用一个查询接口从服务器获取一个状态,这都是有可能的。所有涉及这些判断因素的变量可能又会牵扯出一些逻辑处理。那么UI界面的所有if-else判断都是需要提取到Presenter当中吗?当然不是,再来看一个代码:
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
setIsShowCommentView(isChecked);
}
private void setIsShowCommentView(boolean isChecked) {
if (isChecked) {
mMyCommentText.setText(“AAA”);
mMyCommentLayout.setVisibility(View.VISIBLE);
} else if (mLoginText.getVisibility() == View.VISIBLE) {
mMyCommentText.setText(“BBB”);
mMyCommentLayout.setVisibility(View.GONE);
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.tv_my_attention:
if (mMyAttentionText.getText().toString().equals(getString(R.string.attention))) {
mMyAttentionText.setText(R.string.unattention);
} else if (mMyAttentionText.getText().toString().equals(getString(R.string.unattention))) {
mMyAttentionText.setText(R.string.attention);
}
break;
default:
break;
}
}
这个代码里面已经都是纯UI的操作,所以不需要再提取了,如果再增加一些逻辑变量去控制上面的代码,反而又增加了逻辑的复杂性。你会发现这个代码有个特点就是它是由界面中其他控件的状态改变直接导致某个控件的变化,比如点击状态、复选框状态发生变化,需要另一些UI状态对此作出变化。也是UI状态的变化直接导致的UI状态变化,而非业务数据导致的。
所以这里可以总结出只有那些是由业务数据造成的影响而导致的UI变化,导致这种变化的因素我们需要将它从View层剥离出来,挪到Presenter中去。当然,如果你乐意并且时间充足,也可以把所有导致的UI变化的因素
《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
【/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享
全部抽象成逻辑,但是这样会增加时间成本。
前面例子是改变UI的状态,下面看一个提交UI的数据的代码,就用第一篇中登录的例子来修改一下:
上面代码也是我随意写的,这里除了用户名密码增加了一些参数,而增加的这些参数的特点是跟UI没有任何关系,所以这部分的代码做MVP的改造的话,主要是把绿框中的UI相关的代码留在当前页面,其他全部挪到Presenter当中即可,改造代码这里就不贴了,跟前面的过程类似。
到这里差不多能把View层中跟UI相关或者无关的代码分离开来了, 至少从主观上我们能够做一些区分了,然而前面我们只是简单的将逻辑部分全部移到了Presenter中, 那么Model层呢? 其实如果你的项目比较简单,到这里我觉得就足够了,为什么,因为再分下去,除了增加调用链的长度以外,没有任何好处。但是如果你的项目稍微大一点复杂一点,那么最好将Presenter里的调用再次抽取,也就是我们所谓的Model层,那哪些代码需要放到这一层呢? 主要是数据的存取、转换、过滤操作等相关的代码,比如请求网络回来判断结果值是否成功、json数据解析成Java实体类、本地数据库的查询等。当然在Model层也可以有业务逻辑,不过这部分的逻辑主要跟数据有关系的,比如数据的转换。如果这一层的逻辑较少只是纯粹的读写的话,那么它看起来只不过是将取得的数据直接抛给了Presenter层而已(因为IO读写实际上只需一个工具类即可完成,这个工具类你可以把它看做Model层)。说白了,只是封装的层次深浅和调用链长短的区别了。但是如果涉及数据的业务比较重(如有大量的数据类转换处理),那就必须进行抽取Model层来做。所以这个是要根据实际情况进行取舍的,不一定层次越多越好,维护成本也要考虑进去。
下面将前面例子中提取的MainPresenter中的代码进一步提取,将其中的数据存取的部分提取到Model层,先看一下哪些是可以从Presenter中剥离的:
其中绿色框起来的部分是可以被放到Model层进行处理,应该是比较显而易见的,其实判断方法很简单,理论上在Presenter中除了直接调用view层接口的,剩下的部分都可以算作是Model层。但是如果你真的按照这个去区分的话,又会有点过分,比如Presenter中的一些if-else判断也是可以挪到Model层处理的,又比如下面这行代码,这个参数是由Activity中传递到Presenter然后在Presenter中获取出来的,挪到Model层有点浪费时间。
先不管这些细节的,我们先把上面绿色框起来的部分提取到Model层:
先抽取Model接口:
public interface IMainModel {
void getSavedUser();
boolean getIsLogin();
void sendLoginRequest();
}
Model实现类:
public class MainModel implements IMainModel {
private IMainPresenter mMainPresenter;
public MainModel(IMainPresenter presenter) {
mMainPresenter = presenter;
}
@Override
public void getSavedUser() {
User user = LocalDataManager.getSavedObj(mMainPresenter.getContext(), “User”, User.class);
mMainPresenter.onGetUser(user);
}
@Override
public boolean getIsLogin() {
return LocalDataManager.getShareBool(mMainPresenter.getContext(), “isLogin”);
}
@Override
public void sendLoginRequest() {
String[] values = LocalDataManager.getShareStrings(
mMainPresenter.getContext(), “userName”, “userPassword”);
String userName = values[0];
String userPassword = values[1];
if (!TextUtils.isEmpty(userName) && !TextUtils.isEmpty(userPassword)) {
String url = “http://xx.xxx.xxx”;
StringHashMap requestParams = new StringHashMap();
requestParams.put(“userName”, userName);
requestParams.put(“password”, userPassword);
HttpDataManager.post(url, requestParams, new HttpCallback() {
@Override
public void onSuccess(String result, Object tag) {
LoginResultBean loginResult = JsonUtils.jsonToObject(result, LoginResultBean.class);
if (loginResult != null) {
if (loginResult.getErrCode() == 200) {
mMainPresenter.onLoginSuccess();
} else {
mMainPresenter.onLoginFail();
}
} else {
mMainPresenter.onLoginFail();
}
}
@Override
public void onError(String result, Object tag) {
mMainPresenter.onLoginFail();
}
}, 100);
}
}
}
其中有一部分纯IO的做成了工具类方法放到了LocalDataManager类中:
public class LocalDataManager {
public static T getSavedObj(Context context, String key, Class cls) {
String json = getShareString(context, key);
T t = JsonUtils.jsonToObject(json, cls);
return t;
}
public static String getShareString(Context context, String key) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
return preferences.getString(key, “”);
}