Android MVP(三)内存泄漏分析与动态代理

博主声明:

转载请在开头附加本文链接及作者信息,并标记为转载。本文由博主 威威喵 原创,请多支持与指教。

本文首发于此   博主威威喵  |  博客主页https://blog.csdn.net/smile_running

MVP 架构系列文章:

Android MVP 架构(一)MVP 架构介绍与实战运用

Android MVP 架构(二)MVP 之 BaseMVP 基础框架设计

Android MVP 架构(三)MVP 内存泄漏分析与动态代理

Android MVP 架构(四)MVP 泛型 Model 的配置

Android MVP 架构(五)MVP 多个 Presenter 依赖注入

Android MVP 架构(六)MVP 之 BaseFragment 的封装

Android MVP 架构(七)MVP 之代理模式消除重复代码(结束)

源码地址:

github下载:MVPDemo

csdn 下载:MVPDemo

    上篇文章(Android MVP 架构(二)MVP 之 BaseMVP 基础框架设计),我们讲到了如何封装一个 BaseMVP 基类,使用这个模板,可以避免我们写很多重复性的代码以及一些必要的处理,避免发生漏写的错误,还有同样的代码写多了也肯定很烦。不过,从上篇的文章的 BaseMVP 封装来看,还是有一些不足之处,比如会不会造成内存泄漏的问题?比如每次调用 View 层的方法时,都要去判断 View 层是不是被摧毁了,是不是为 NULL 的情况。鉴于这两个问题,我们这次就针对这两个问题进行分析和解决。

  • 内存泄漏

    第一个问题:会不会造成内存泄漏的问题?

    这个问题涉及到内存泄漏的问题,那就先看看内存泄漏是什么吧!在 Java 中的 GC(垃圾回收器)就是来回收那些已经没用了的对象,已经被释放掉对象的内存,因为正是 Java 的这种垃圾回收机制,所以我们才不需要手动去释放对象引用的内存。相信大家在大学中都有学过 C、C++ 语言,肯定会在指针那里有所顾忌。我们每一次都需要通过在内存地址中开辟一段内存来存放指针的引用对象,而当指针对象使用完了后,必须手动给它置空的操作。而在 Java 中却的 GC 已经帮我们做了这样的工作,但是呢, GC 其实也不是那么万能的,它是通过一些算法来控制对象引用的回收,那么我们就应该去了解 GC 的回收算法及原理。

    说了这么多,你应该大致有个了解,内存泄漏就是当 GC 来回收时,然而这些对象没有得到释放,或者被其它的给引用了,这种情况下 GC 是不会自动回收的,而这块内存就会被一直占用着,得不到释放,这就是内存泄漏的基本概念。然而,程序这样的情况越来越多时,程序就会出现卡顿、奔溃、报异常的现象,当这些对象占用的内存达到一定的临界值时,机器中没有多余可用的内存,这时你再去申请内存空间,就会发生 OOM (内存溢出)。所以说,内存泄漏是一种安全隐患,它直接影响的是程序的性能。要想在这方面做的好,这需要一个深入的研究。

    多的也不扯淡了,我们继续来看问题。BaseMVP 其实也是会造成内存泄漏的一大安全隐患,它的内存泄漏是来自于 View 层与 Presenter 层之间的强引用关系。我们在 Presenter 层直接绑定了 View 才可以拿到 View 层的引用,它们之间是强引用的关系,如果不进行解绑的话,那就会造成内存泄漏的情况发生。为什么不解绑就会内存泄漏呢?我们来看看代码:

public abstract class BasePresenter<V extends IBaseView> implements IBasePresenter {
    protected V mView;

    @SuppressWarnings("unchecked")
    @Override
    public void attech(IBaseView view) {
        mView = (V) view;
    }

    @Override
    public void detech() {
//        mView = null;
    }
}

    这里,我们不解绑 View,也就是 mView = null 注释掉,意味着 Presenter 层还持有 View 的引用,当 Activity 被关闭时,Activity 相当于 View 层,由于 Activity 还是被 Presenter 层引用了,当 GC 来了,它一看 Activity 被引用了,所以就不会去回收它。当你再次打开 Activity 又关闭时,Activity 又申请了一段新的内存空间,GC 又没去回收它,久而久之,势必会内存溢出。

    而这里置空了就不会造成内存泄漏,因为此时的 View,也就是 Activity 的引用被释放了,如果再也没有其他类引用到 Activity 对象的时候,当 GC 来时,发现 Activity 是可以回收的,就把它回收掉了,这段内存空间就释放了。

    说了这么多,其实就是为了介绍我们自己写的 BaseMVP 存在的内存泄漏问题,这里的代码还是基于上一篇 MVP v2 版本进行修改,因为 V 与 P 之间是强引用,所以我们就改为软引用的方式,避免内存泄漏导致的 OOM 情况发生。代码我做了一点细微的修改,代码如下:

BasePresenter 基类修改:

package com.test.mvp.mvpdemo.mvp.v3.basemvp;

import java.lang.ref.SoftReference;

public abstract class BasePresenter<V extends IBaseView> implements IBasePresenter {
    private SoftReference<IBaseView> mReferenceView;

    @SuppressWarnings("unchecked")
    @Override
    public void attech(IBaseView view) {
        mReferenceView = new SoftReference<>(view);
    }

    @SuppressWarnings("unchecked")
    public V getView() {
        return (V) mReferenceView.get();
    }

    @Override
    public void detech() {
        mReferenceView.clear();
        mReferenceView = null;
    }
}

    使用软引用的方式让 P 层持有 V 层的引用,并且提供了 getView() 方法给 P 层调用,父类 View 变量进行私有化,防止子类对其进行更改造成的其他错误。我们的 MainPresenter 获取 Activity 的引用就可以使用 getView() 方法获得。软引用在内存降到不足的情况下,GC 就会进行优先回收释放那些以软引用方式引用的对象,一定程度上去避免内存溢出(OOM)。

  • 动态代理

第二个问题:每次都要让 View 做空判断,很烦?

/**
 * presenter 层,承担业务逻辑处理,数据源处理等
 */
public class MainPresenter extends BasePresenter<MainContract.IMainView> implements MainContract.IMainPresenter {

    private MainContract.IMainModel mModel;

    @Override
    public void attech(IBaseView view) {
        super.attech(view);
        mModel = new DataModel();
    }

    @Override
    public void handlerData() {
        if (getView() != null) {
            getView().showDialog();
        }

        mModel.requestBaidu(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String content = response.body().string();
                if (getView() != null) {
                    getView().succes(content);
                }
            }
        });
    }

    @Override
    public void detech() {
        super.detech();
        /**
         * 释放内存、关闭网络请求、关闭线程等操作
         */
        Log.d("==========", "detech: 解除绑定,释放内存");
    }
}

    为什么要用动态代理呢?我们看上面的 getView()代码,没次都需要判断 null 类型,是不是非常麻烦,又因为这里的 View 类型是一个接口(V extends IBaseView)泛型接口,所以这就好办了,动态代理完全就可以做到统一的空类型判断,那么这里的代码修改为如下:

package com.test.mvp.mvpdemo.mvp.v3.basemvp;

import java.lang.ref.SoftReference;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public abstract class BasePresenter<V extends IBaseView> implements IBasePresenter {
    private SoftReference<IBaseView> mReferenceView;
    private V mProxyView;

    @SuppressWarnings("unchecked")
    @Override
    public void attech(IBaseView view) {
        mReferenceView = new SoftReference<>(view);
        mProxyView = (V) Proxy.newProxyInstance(view.getClass().getClassLoader(), view.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                if (mReferenceView == null || mReferenceView.get() == null) {
                    return null;
                }
                return method.invoke(mReferenceView.get(), objects);
            }
        });
    }

    @SuppressWarnings("unchecked")
    public V getView() {
        return mProxyView;
    }

    @Override
    public void detech() {
        mReferenceView.clear();
        mReferenceView = null;
    }
}

    使用动态代理之后,我们在 Presenter 的实现类中就不需要做 View 层的空类型判断了,这样既节省了代码,虽然没有多少代码,但是写起来还是很烦的,又让我们的代码变得更加优雅。

    那么,这篇文章就结束了,通过这篇文章为 BaseMVP 继续加料,我们的 BaseMVP 基础框架也有模有样了,要想做的更好,还需要继续去优化代码。不着急,我们一次一次慢慢的搞定它。

发布了101 篇原创文章 · 获赞 766 · 访问量 87万+

猜你喜欢

转载自blog.csdn.net/smile_Running/article/details/94984874
今日推荐