Toast 为什么会造成内存泄漏

我们知道,在使用 Toast 时,如果第一个参数传入的是 Activity 的话,可能会造成内存泄漏,只要把 Activity 改为 Application 即可解决问题,那么为什么会造成内存泄漏呢?


前文分析过 Toast 的源码及执行流程,这里再简单复述一遍,我们先看看调用的方法

    public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
        return makeText(context, null, text, duration);
    }

    public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
            @NonNull CharSequence text, @Duration int duration) {
        Toast result = new Toast(context, looper);

        LayoutInflater inflate = (LayoutInflater)
                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
        TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
        tv.setText(text);

        result.mNextView = v;
        result.mDuration = duration;

        return result;
    }

在这个方法中,会通过 Toast 的构造方法来创建它,传入的参数就是上下文 context 和 looper,我们再看看它的构造方法

   

public Toast(@NonNull Context context, @Nullable Looper looper) {
        mContext = context;
        mTN = new TN(context.getPackageName(), looper);
        mTN.mY = context.getResources().getDimensionPixelSize(
                com.android.internal.R.dimen.toast_y_offset);
        mTN.mGravity = context.getResources().getInteger(
                com.android.internal.R.integer.config_toastDefaultGravity);
    }

注意,这里是把 context 赋值给了成员变量,因此 Toast 此时就持有 传入的上下文了。我们分析过 Toast 的创建和弹出的流程,在 NotificationManagerService 的成员变量 IBinder mService 中执行 Toast 的存储,注意,Toast 是会被存贮在 mToastQueue 这个集合中,所以如果集合中元素个数比较多, Activity 已经执行了 finish() 方法,但由于 Toast 还没有从集合中移除,仍旧持有Activity 的对象,导致 Activity 不能被真正的关闭回收,因此造成了内存泄漏。

以上好理解,但我debug时,发现 Toast 内的 view 持有了 Activity,然后我就纳闷了,这是怎么回事?通过 Toast 的 makeText() 方法,可以看到创建了 LayoutInflater 对象,方法为 LayoutInflater inflate = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 此时 context 是 Activity 类型,那么看看 Activity 中的代码   

    @Override
    public Object getSystemService(@ServiceName @NonNull String name) {
        if (getBaseContext() == null) {
            throw new IllegalStateException(
                    "System services not available to Activities before onCreate()");
        }

        if (WINDOW_SERVICE.equals(name)) {
            return mWindowManager;
        } else if (SEARCH_SERVICE.equals(name)) {
            ensureSearchManager();
            return mSearchManager;
        }
        return super.getSystemService(name);
    }

还得看它的父类,ContextThemeWrapper 中的方法

   

    @Override
    public Object getSystemService(String name) {
        if (LAYOUT_INFLATER_SERVICE.equals(name)) {
            if (mInflater == null) {
                mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
            }
            return mInflater;
        }
        return getBaseContext().getSystemService(name);
    }

原来, LayoutInflater 对象是这么来的。我们认为的 LayoutInflater 对象是全局唯一的,是因为 LayoutInflater.from() 对应的方法中,

   

    public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }

context 是 ContextImpl 类型,看看它里面

   

    @Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }

在  SystemServiceRegistry 类中,是在静态模版块中创建了 PhoneLayoutInflater 对象,然后添加到 map 集合中,如果使用的话就从 map 中获取,这样才保持了全局的唯一。但在 ContextThemeWrapper 中,LayoutInflater.from(getBaseContext()) .cloneInContext(this) 注意了,调用了clone的方法,此时是 BasicInflater ,

    private static class BasicInflater extends LayoutInflater {
        private static final String[] sClassPrefixList = {
            "android.widget.",
            "android.webkit.",
            "android.app."
        };

        BasicInflater(Context context) {
            super(context);
        }

        @Override
        public LayoutInflater cloneInContext(Context newContext) {
            return new BasicInflater(newContext);
        }
    }

context 原来是这样持用的,并且把 Activity 传到了 LayoutInflater 中,转换xml时用的就是 Activity,故此 Toast 中的 View 会持有 Activity。

发布了176 篇原创文章 · 获赞 11 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/Deaht_Huimie/article/details/104434510