RxAndroid+RxLifecycle+MVP实现异步请求,ButterKnife更新ui

本文介绍了一个基于MVP模式、RxAndroid、RxLifecycle及ButterKnife的小型框架,用以实现异步网络请求和UI更新。文章详细阐述了如何避免在页面关闭后继续执行异步请求导致的空指针异常,并提供了完整的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

各位好,今天的内容应该算是一个小框架了,使用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试试,下面是本文的源码,可下载下来运行试试
点击下载源码

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值