各位好,今天的内容应该算是一个小框架了,使用Rx系列配合MVP实现异步请求,使用ButterKnife注入框架更新ui。
今天我主要讲的是 RxAndroid+RxLifecycle+MVP+ButterKnife整体结合的小框架。
关于什么是mvp不明白的可参考:
[ 浅谈 MVP in Android]
关于mvp + rxandroid实现异步请求不明白的可参考:
[ 使用RxAndroid配合MVP模式实现异步网络请求,更新ui ]
Rx已经问世很久了,JakeWharton 大神对 RxJava 贡献了一系列的快速开发框架,其中就包括了 RxLifecycle。RxLifecycle 是什么呢?对我们项目有什么作用呢? (我告诉你,作用大着呢!)
作用:
在之前博文-> [ 使用RxAndroid配合MVP模式实现异步网络请求,更新ui ] 中有一个小问题,那就是当在真实网络请求中,假如网络很慢,慢到我已经把页面关了,它才返回结果过来,这样问题就来了。
首先我们的请求是异步的,当异步请求结果返回后,我们会根据返回结果到ui线程中更新ui操作,而当请求结果还没有返回,我们就将页面关闭了,于是就会出现访问ui操作空指针。
最简单的解决办法就是:在所有ui回调中判断activity.isFinishing(),这种办法有用,但是很麻烦,需要在所有ui回调中写。作为聪明的我们当然忍受不了这种重复工作,那该怎么办呢?当我遇见 RxLifecycle 之后,一切变得更美好了。
只需要在请求中加一行代码就能搞定
那 ButterKnife 是什么呢?它是 JakeWharton 写的 views 注入框架,配合 mvp 后让代码更简洁,ButterKnife 的使用可参考 [ ButterKnife 使用官方指南]
,下面进行demo环节:
首先,目录结构:
很典型的mvp目录结构。
- biz:数据处理层
- ApiData:模拟后台数据
- LoginBiz:mvp中的m层,用于处理登录逻辑
- User:用户实体对象
- view:界面层
- ILoginView:登录界面,是一个接口,如:展示进度条、隐藏进度条、展示数据等
- MainActivity:ILoginView的实现类
- persenter:连接view与biz的纽带
- LoginPersenter:请求LoginBiz获取数据,使用ILoginView层更新界面。
好像似曾相识。只是多了几个 base 基类,方便拓展。
代码环节:
首先看BaseActivity,基类的简单封装。
public abstract class BaseActivity extends RxActivity {
protected Context context;
private Unbinder bind;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
context = this;
/** 注意:setContentView 必需在 bind 之前 */
setContentView(setContentView());
bind = ButterKnife.bind(this);
initView();
}
/** 子类设置界面 */
protected abstract int setContentView();
protected abstract void initView();
@Override
protected void onDestroy() {
super.onDestroy();
bind.unbind();
}
}
这里统一注入是 ButterKnife。这里需要注意的一点是 BaseActivity 继承的是 RxActivity。 RxActivity 是 RxLifecycle 对 Activity 生命周期的管理
MainActivity:
public class MainActivity extends BaseActivity implements ILoginView {
@BindView(R.id.tv_name) TextView tv_name;
@BindView(R.id.et_name) EditText et_name;
@BindView(R.id.pb_loading) ProgressBar pb_loading;
@BindView(R.id.bt_login) Button bt_login;
@BindView(R.id.bt_next) Button bt_next;
private LoginPresenter loginPresenter;
@Override
protected int setContentView() {
return R.layout.activity_main;
}
@Override
protected void initView() {
loginPresenter = new LoginPresenter(context, this);
}
@OnClick({R.id.bt_login, R.id.bt_next})
public void click(View view){
int id = view.getId();
switch (id){
case R.id.bt_login:
String userName = et_name.getText().toString();
loginPresenter.doLogin(userName);
break;
case R.id.bt_next:
startActivity(new Intent(context, NextActivity.class));
finish();
break;
}
}
@Override
public void loginResult(String msg) {
tv_name.setText(msg);
}
@Override
public void showLoadding() {
pb_loading.setVisibility(View.VISIBLE);
}
@Override
public void hideLoadding() {
pb_loading.setVisibility(View.GONE);
}
@Override
protected void onDestroy() {
super.onDestroy();
/** 这里释放 presenter 占用的资源,防止内容泄露 */
loginPresenter.doDestroy();
Timber.e("onDestroy");
}
}
使用了mvp后,activity就是这么简单,此时的activity只有了ui操作。
NextActivity:
public class NextActivity extends BaseActivity {
@BindView(R.id.bt_back)
Button bt_back;
@Override
protected int setContentView() {
return R.layout.activity_next;
}
@Override
protected void initView() {
}
@OnClick(R.id.bt_back)
public void click(View view){
startActivity(new Intent(context, MainActivity.class));
this.finish();
}
}
User: 用户对象
public class User {
public String name;
public User() {
}
public User(String name) {
this.name = name;
}
}
LoginPresenter: 实现登录逻辑
public class LoginPresenter extends BasePresenter{
private LoginBiz loginBiz;
private ILoginView loginView;
public LoginPresenter(Context context, ILoginView loginView) {
super(context);
this.loginView = loginView;
this.loginBiz = new LoginBiz();
}
public void doLogin(String userName){
loginView.showLoadding();
loginBiz.doLogin(new User(userName))
.subscribeOn(Schedulers.newThread()) // 子线程执行网络操作
.observeOn(AndroidSchedulers.mainThread()) // 转回主线程
// 当 ACTIVITY 销毁时,Subscriber 里方法将不会执行
.compose(getActivityLifecycleProvider().<User>bindUntilEvent(ActivityEvent.DESTROY))
.subscribe(new Subscriber<User>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable throwable) {
loginView.hideLoadding();
doError(throwable);
}
@Override
public void onNext(User user) {
Timber.e("onNext");
loginView.hideLoadding();
if (null != user){
loginView.loginResult(user.name + ",你好帅!");
}else {
loginView.loginResult("用户名不正确!");
}
}
});
}
}
.compose(getActivityLifecycleProvider().bindUntilEvent(ActivityEvent.DESTROY))
这一句是重点,当activity执行到了destroy方法后,onNext将不会执行了。
在本文最后会通过 Timber(开源log框架) 打印 log 来验证。
其中:getActivityLifecycleProvider()这个方法来源于 BasePresenter。
下面我们看看 BasePresenter
BasePresenter: 这里做了很多基础性的工作,其中包括了 activity 生命周期的管理。
public class BasePresenter {
protected Context mContext;
public BasePresenter(Context mContext) {
this.mContext = mContext;
}
/**
* 对 ACTIVITY 生命周期进行管理
* @return
*/
protected ActivityLifecycleProvider
getActivityLifecycleProvider() {
ActivityLifecycleProvider provider = null;
if (null != mContext &&
mContext instanceof ActivityLifecycleProvider) {
provider = (ActivityLifecycleProvider)mContext;
}
return provider;
}
protected void doError(Throwable error){
// TODO 此处可处理错误
}
public void doDestroy(){
this.mContext = null;
}
}
LoginBiz:
public class LoginBiz{
public Observable<User> doLogin( final User user){
return rx.Observable.create(new Observable.OnSubscribe<User>() {
@Override
public void call(Subscriber<? super User> subscriber) {
// 模拟执行网络操作
try {
Thread.sleep(2000);
// 这里可以处理更多操作,捕获到异常对 onError 进行处理
User user2 = new ApiData().doLogin(user);
subscriber.onNext(user2);
} catch (InterruptedException e) {
e.printStackTrace();
subscriber.onError(e);
}
}
});
}
}
模拟网络请求
ApiData:模拟后台处理
public class ApiData {
public User doLogin(User user){
User user2 = null;
if (user.name.equals("zane")){
user2 = new User();
user2.name = "zane";
}
return user2;
}
}
ILoginView
public interface ILoginView {
void loginResult(String msg);
void showLoadding();
void hideLoadding();
}
下面测试环节:
不使用 RxLifecycle
在 biz 模拟网络操作未完成将 activity finish 调后打印的 log:
可以看到先是 activity 执行 destroy , destroy 完毕之后才执行的 next 方法。
这是很危险的,如果 activity 里的 view 都释放了,很容易出现空指针。特别像使用了 view 注入框架的项目中。因为 view 注入框架中会对 view 在 activity 生命周期中进行注册和注销操作。一般会在 onCreate 中注册,在 onDestory 中注销,这样在 onDestory 之后访问 view 就肯定会出现空指针。
好了,下面我们来看看使用 RxLifecycle 会怎样:
可见,只执行了 activity destroy 方法。
看官可以自己写个demo试试,下面是本文的源码,可下载下来运行试试
点击下载源码