Android MVP-编程思想3(MVP-内存泄露问题处理,基类封装,有没有必要再使用软引用?)

前言

通过一个小的登录功能模块案例,帮助大家了解MVP。最终实现一个结合Rxjava2,Retrofit 的MVP通用框架。代码放到github上。(如有错误之处,请在评论区指出,谢谢。如果感觉写的不错,请点赞,关注,谢谢。)

目录:

Android MVP-编程思想1(什么是MVC-MVP-MVVM模式?)
Android MVP-编程思想2(代码实现初级版)
Android MVP-编程思想3(内存泄露问题处理,基类封装,有没有必要再使用软引用?)
Android MVP-编程思想4(AOP思想-动态代理运用,反射创建M层实例对象)
Android MVP-编程思想5(如何处理多个P层的问题?)
Android MVP-编程思想6(依赖注入多个P层优化—注解,反射)
Android MVP-编程思想7(为什么使用代理类抽取通用方法而不是工具类?,基类BaseMvpFragment)

未完待续--------
Android MVP-编程思想8(集成Rxjava2,Retrofit)

本篇是接着上一篇写的。但是并不需要按照顺序阅读,对MVP已经有一定理解和实践的可以直接阅读。如果没有接触过MVP请先阅读第一小节

这一节主要实现两个小目标,1.内存泄露问题处理,2.基类抽取封装

第一个问题,内存泄露,为什么会内存泄露?

Activity又持有P层的引用,P持有V层(Activity)的强引用,循环引用了(所以即使在onDestory中设置mPresenter=null 也不会被回收,因为P还持有了V的引用)。当用户点击返回按钮,关闭Activity时,由于Activity 被强引用了,所以就不会去回收它,导致内存泄露。当你再次打开 Activity 又关闭时,Activity 又申请了一段新的内存空间,GC 又没去回收它,久而久之,势必会内存溢出。

如何解决内存泄露?

这里我们就在Activity需要回收的时候,断开循环强引用不就行了。看代码如下,注入view的方法attachView,释放view的方法detachView。然后再Activity的onDestory() 方法中调用P层的detachView,释放mView,来断开循环引用链,在释放mPresenter=null。继续看就知道了

P层基类抽取BasePresenter (多个P的情况后面讲)

每个P都有V,M的引用,可以抽取到父类,由于没有实现类的V,M不同,所以使用泛型。P持有的V的引用使用attachView 注入,P持有M引用,通过抽象方法createModel注入。detachView解决循环引用,内存无法释放问题。

public abstract class BasePresenter<V, M> {
    protected V mView;
    protected M mModel;

    /**
     * 注入view
     *
     * @param view
     */
    protected void attachView(final V view) {
        mView = view;
        mModel = createModel();
    }

    /**
     * 释放View
     */
    protected void detachView() {
        mView = null;
        mModel = null;
    }
    protected abstract M createModel();
}

有没有必要使用软引用

有一些人说没必要了,觉得我们已经使用detachView 的方式处理了内存泄露问题了。其实你有没有考虑过某些情况下Activity的onDestroy方法不会执行?所以这才是使用软引用的原因。

public abstract class BasePresenter<V, M> {
    private SoftReference<V> mViewReference;
    protected V mView;
    protected M mModel;

    /**
     * 注入view
     *
     * @param view
     */
    protected void attachView(final V view) {
        mViewReference = new SoftReference<>(view);
        mView = mViewReference.get();
        mModel = createModel();
    }

    /**
     * 释放View
     */
    protected void detachView() {
        mViewReference.clear();
        mViewReference = null;
        mView = null;
        mModel = null;
    }

    protected abstract M createModel();

}

抽取基类BaseMvpActivity

为什么叫BaseMvpActivity? 为了和BaseMvcActivity区别,BaseMvcActivity的抽取后面几节会贴上。大家也看到了Mvp有个缺点就是需要写很多接口。所以一些简单的功能模块我们还是使用Mvc模式开发更快。在一个应用中根据模块的复杂度同时使用Mvc和Mvp模式才是正解。

每个V层都持有P的引用,(多个P层的情况到后面讲,我们一步一步来),抽取到基类,P的实现类不同,使用泛型。提供createPresenter()抽象方法注入P。解决内存泄露:onDestroy方法先调用 mPresenter.detachView();释放V,然后mPresenter=null释放P。

public abstract class BaseMvpActivity<P extends BasePresenter> extends AppCompatActivity {
    protected P mPresenter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        View rootView;
        final Object layout = getLayout();
        if (layout instanceof Integer) {
            rootView = View.inflate(this, (int) getLayout(), null);
        } else if (layout instanceof View) {
            rootView = (View) getLayout();
        } else {
            throw new ClassCastException("type of setLayout() must be int or View!");
        }
        setContentView(rootView);
        mPresenter = createPresenter();
        mPresenter.attachView(this);
        afterCreate();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mPresenter.detachView();
        mPresenter = null;
    }


    @SuppressWarnings("ConstantConditions")
    protected <T extends View> T $(@IdRes int viewId) {
        return findViewById(viewId);
    }

    public void showProgress(boolean isShow) {
        if (isShow) {
            Log.i("mvp_", "显示等待框");
        } else {
            Log.i("mvp_", "隐藏等待框");
        }
    }

    public void showToast(String msg) {
        Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
    }

    protected abstract Object getLayout();

    protected abstract P createPresenter();

    protected abstract void afterCreate();
}

抽取基类IBaseView

每个V层都有下面两个行为,所以这里做一个基类

public interface IBaseView {
    //请求网络显示等待框
    void showProgress(boolean isShow);

    //显示提示框-登录失败,或其他提示信息
    void showToast(String string);
}

其它相关类的变化

由于抽取类IBaseView,这里变化了LoginContract类,不多解释,懂得都懂

interface IView extends IBaseView {
        //登录成功跳转到主界面
        void goToMainActivity();
    }

由于抽取了BasePresenter,所以LoginPresenter变化如下

public class LoginPresenter extends BasePresenter<LoginContract.IView, LoginContract.IModel> implements LoginContract.IPresenter {

    @Override
    protected LoginContract.IModel createModel() {
        return new LoginModel();
    }

    @Override
    public void onClickLogin(String username, String pwd) {
        mView.showProgress(true);
        mModel.getUserInfo(username, pwd, new ICallBack<String>() {
            @Override
            public void success(String data) {
                if (mView != null) {
                    mView.showToast("登录成功");
                    mView.showProgress(false);
                    mView.goToMainActivity();
                }
            }

            @Override
            public void error(String error) {
                if (mView != null) {
                    mView.showProgress(false);
                    mView.showToast("登录失败");
                }
            }
        });
    }
}

由于抽取了BaseMvpActivity,所以LoginActivity变化如下

public class LoginActivity extends BaseMvpActivity<LoginPresenter> implements LoginContract.IView {


    @Override
    protected Object getLayout() {
        return R.layout.activity_main;
    }

    @Override
    protected LoginPresenter createPresenter() {
        return new LoginPresenter();
    }

    @Override
    protected void afterCreate() {
        $(R.id.btn_login).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mPresenter.onClickLogin("xxx", "xxx");
            }
        });
    }

    @Override
    public void goToMainActivity() {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                //todo 登录成功跳转到主界面
                Log.i("mvp_", "登录成功跳转到主界面");
            }
        });

    }

    @Override
    public void showProgress(boolean isShow) {
        if (isShow) {
            Log.i("mvp_", "显示等待框");
        } else {
            Log.i("mvp_", "隐藏等待框");
        }
    }

    @Override
    public void showToast(String str) {
        Toast.makeText(this, str, Toast.LENGTH_LONG).show();
    }
}

这一节讲解了内存泄露的原因以及解决方案,还有基类的封装抽取,复用。但是还是不够完美,比如,每次调用mView之前需要进行非空判断。这个重复的逻辑能不能做统一封装?这个问题留到下一节处理。

你可能感兴趣的:(architectural,thinking)