对强引用的思考(什么时候不该使用强引用?)

弱引用的定义:

用于非必需对象。被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象

一开始的认知总会感觉,用一个weakreference去引用一个对象一点都不靠谱,毕竟该引用指向的对象,在GC的时候就会被回收,完全没有任何保证。

针对上面这个想法,因为一个对象可能会存在多个引用参与,所以应该更多是强弱同时去引用某个对象,只有当所有强引用都失效之后,你的这个弱引用才有风险被回收掉。

当一些由系统生成的对象,如context类的:Activity,其生命周期更多应该是由系统的逻辑来进行管理,但是常常APK端为了实现一些功能,会增加对这些对象的引用。如果额外的引用的生命周期超过了正常系统对其管理的时间就会造成其生命周期过长占用内存,如果额外的生命周期是整个APP的存在时间,则就会出现内存泄漏。

为了支持和解决以上这种问题的存在,这种额外的引用就可以采用弱引用(WeakReference)、弱哈希(WeakHashMap)

先拿几个强引用造成内存泄漏的例子来观察:

Android开发常见的Activity中内存泄漏及解决办法 – 碎格子 – 博客频道 – CSDN.NET

这篇blog中有提到的泄漏的例子包括:

1、静态Activities:声明static的变量去引用activity导致其始终无法释放,引申到更加广义的行为就是额外引用的时长超过系统对其的管理时长。那么存在一个问题,就是第一个去引用Activity的对象的地方在哪。也就是要寻找第一个强引用的位置。

在ActivityThread的performLaunchActivity中会涉及到Activity的创建以及多方对这个对象的引用

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
......
2314        Activity activity = null;
2315        try {
2316            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
                /*第一个引用的位置*/
2317            activity = mInstrumentation.newActivity(
2318                    cl, component.getClassName(), r.intent);
2319            StrictMode.incrementExpectedActivityCount(activity.getClass());
2320            r.intent.setExtrasClassLoader(cl);
2321            r.intent.prepareToEnterProcess();
2322            if (r.state != null) {
2323                r.state.setClassLoader(cl);
2324            }
2325        } catch (Exception e) {
2326            if (!mInstrumentation.onException(activity, e)) {
2327                throw new RuntimeException(
2328                    "Unable to instantiate activity " + component
2329                    + ": " + e.toString(), e);
2330            }
2331        }
2332
......
                    /*第二个引用的位置*/
2376                r.activity = activity;
2377                r.stopped = true;
2378                if (!r.activity.mFinished) {
2379                    activity.performStart();
2380                    r.stopped = false;
2381                }
......
2422        return activity;
2423    }

第一个引用是强引用,在函数末尾返回。通过追踪代码,是被LocalActivityRecord中的成员引用,是一个强引用(跟这个类相关的是LocalActivityManager跟在一个activity中显示其他Activity相关,使用localActivityManager进行startActiviy返回的是一个view类,可以通过addView添加到某个container当中)

第二个引用是被ActivityClientRecord进行了强引用,这条跟ActivityThread

暂时就发现一个activity被这两个部分进行了引用,其他部分暂时还无法确认。也就是如果framework放弃了这两部分的引用,Activity就可以被回收了。但是如果存在额外引用就会导致内存泄漏了。

上面这个引用关系比较直观,来个稍微隐晦一点的

2、静态view

还是使用前面blog中的例子:

    private static View view;
    TextView saButton;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        saButton = (TextView) findViewById(R.id.text);
        saButton.setOnClickListener(new View.OnClickListener() {
            @Override public void onClick(View v) {
                setStaticView();
                nextActivity();
            }
        });
    }
    void setStaticView() {
        view = findViewById(R.id.sv_view);
    }

乍看跟之前的Activity没差,这个TextView肯定会泄漏,但是还不止这么简单,除了TextView会泄漏之外,还会导致整个Activtiy泄漏,也就是说view的引用链中包含了Activity。接下来就查一查view是怎么跟Activity扯上关系的。

先从setContentView入口(虽然可以直接从View的构造参数包括Context来解释,但是还是先确定这个context的真身就是Activity来比较保险)

Activity:setContentView

PhoneWindow:setContentView(在phonewindow中的构造中就会传入context,然后layoutinflater的构造顺势也会引入context)

publicPhoneWindow(Contextcontext) {
305super(context);
306mLayoutInflater = LayoutInflater.from(context);
307    }

LayoutInflater:inflate

LayoutInflater:createViewFromTag这里入参就带有context

View的所有构造函数都带有Context这个参数

《对强引用的思考(什么时候不该使用强引用?)》
《对强引用的思考(什么时候不该使用强引用?)》 View中的mContext也是对Context的强引用,所以View的不释放除了造成其自身的泄漏也会造成Activity的泄漏。

3、匿名类和非静态内部类

这两种类的对象都会持有其外部类的引用导致外部类对象无法释放

匿名内部类和非静态内部类看来是没办法使用weakreference作为solution

这篇文章的本意是想看看什么时候使用weakreference比较合适,因为我在这方面意识还比较薄弱需要加强-.-|||。网络上有很多分析weakreference的文章,我这里就找一下Android系统中framework层对weakreference的实际使用。

在Framework层中对WeakReference的使用主要有以下几种:

1、线性集合中保存的均是弱引用

《对强引用的思考(什么时候不该使用强引用?)》

2、直接的弱引用

《对强引用的思考(什么时候不该使用强引用?)》

3、Map结构,key-value中的value使用weakreference

《对强引用的思考(什么时候不该使用强引用?)》
《对强引用的思考(什么时候不该使用强引用?)》

发现一个比较常见的模式:静态内部类中都会对外部类的引用都会使用WeakReference

先不去讨论这些内部类的具体功能是什么,例如TextView的静态内部类Marquee,其成员mView持有外部类的WeakReferece

SurfaceView的静态内部类MyWindow的成员mSurfaceView持有外部类的WeakReference

这两个例子之所以这么设计也是为了避免使用内部类导致外部类泄漏,进而导致Activity泄漏。

按照前面讲的逻辑,非静态内部类会引用到外部类,solution就是使用静态内部类,但是静态内部类的话如何对外部类得到引用?进而推导出使用WeakReference去引用外部类的对象,这样的模式才能防止内存泄漏。但是就是在内部类使用外部类的时候会存在对象已经被回收的状况出现。

—END—

    原文作者:Kelvin wu
    原文地址: https://zhuanlan.zhihu.com/p/25479542
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞