【问题标题】:Retained fragment leak保留的片段泄漏
【发布时间】:2015-08-29 16:15:24
【问题描述】:

我正在使用带有保留片段的简单活动,其中包含活动使用的一些数据。保留的片段使用加载器从内容提供者获取数据。在配置更改(屏幕旋转)时,活动被重新创建并且旧实例被泄漏,正如 LeakCanary 库报告的那样(保留片段 -> 加载器管理器 -> 旧活动)。这与 support-v4 23.0.0 库(以及以前的版本)一起复制。复制泄漏的保留片段的活动示例(此处没有有用的代码,仅用于演示泄漏):

package com.leaksample;

import android.database.Cursor;
import android.os.Bundle;
import android.provider.MediaStore;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v7.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    private ModelFragment mModelFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        FragmentManager fm = getSupportFragmentManager();
        mModelFragment = (ModelFragment) fm.findFragmentByTag(ModelFragment.TAG);
        if (mModelFragment == null) {
            mModelFragment = new ModelFragment();
            fm.beginTransaction()
                    .add(mModelFragment, ModelFragment.TAG)
                    .commit();
            fm.executePendingTransactions();
        }
    }

    public static class ModelFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {
        private static final String TAG = ModelFragment.class.getSimpleName();

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setRetainInstance(true);
            getLoaderManager().initLoader(0, null, this);
        }

        @Override
        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            return new CursorLoader(getActivity(), MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                    new String[]{MediaStore.Images.Media.DATA}, null, null, null);
        }

        @Override
        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        }

        @Override
        public void onLoaderReset(Loader<Cursor> loader) {
        }
    }
}

来自 LeakCanary 的堆栈:

08-29 19:52:34.129  15271-16632/com.leaksample D/LeakCanary﹕ In com.leaksample:1.0:1.
08-29 19:52:34.129  15271-16632/com.leaksample D/LeakCanary﹕ * com.leaksample.MainActivity has leaked:
08-29 19:52:34.129  15271-16632/com.leaksample D/LeakCanary﹕ * GC ROOT thread java.lang.Thread.<Java Local> (named 'Binder_1')
08-29 19:52:34.129  15271-16632/com.leaksample D/LeakCanary﹕ * references android.view.ViewRootImpl.mContext
08-29 19:52:34.129  15271-16632/com.leaksample D/LeakCanary﹕ * references com.leaksample.MainActivity.mModelFragment
08-29 19:52:34.129  15271-16632/com.leaksample D/LeakCanary﹕ * references com.leaksample.MainActivity$ModelFragment.mLoaderManager
08-29 19:52:34.129  15271-16632/com.leaksample D/LeakCanary﹕ * references android.support.v4.app.LoaderManagerImpl.mHost
08-29 19:52:34.129  15271-16632/com.leaksample D/LeakCanary﹕ * references android.support.v4.app.FragmentActivity$HostCallbacks.this$0
08-29 19:52:34.129  15271-16632/com.leaksample D/LeakCanary﹕ * leaks com.leaksample.MainActivity instance

也许我做错了什么,忘记调用closerelease 方法?我认为将保留片段与加载器一起使用是一种常见模式,这里不应该是内存泄漏。

【问题讨论】:

  • 您可以尝试在不调用 getActivity() 的情况下创建 CursorLoader 并查看是否仍有内存泄漏?
  • 我已将 getActivity() 替换为 getActivity().getApplicationContext() 并且泄漏仍然存在。
  • @user3640997 你能显示来自 LeakCanary 的堆栈吗?看到确切的泄漏痕迹会很有趣。
  • 在帖子中添加了 LeakCanary 堆栈

标签: android android-fragments memory-leaks loader leakcanary


【解决方案1】:

您在 ModelFragment 中调用 getLoaderManager() 最终会创建一个新的 LoaderManagerImpl 实例,其中包含对 FragmentActivity 的引用。见下文:

// FragmentActivity.java

LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create) {
        if (mAllLoaderManagers == null) {
            mAllLoaderManagers = new SimpleArrayMap<String, LoaderManagerImpl>();
        }
        LoaderManagerImpl lm = mAllLoaderManagers.get(who);
        if (lm == null) {
            if (create) {
                lm = new LoaderManagerImpl(who, this, started);
                mAllLoaderManagers.put(who, lm);
            }
        } else {
            lm.updateActivity(this);
        }
        return lm;
    }

从对 FragmentActivity 的进一步检查来看,当 Fragment 保留其实例时,加载程序并未被移除,并且仍然持有对已销毁活动的引用。

@Override
    public final Object onRetainNonConfigurationInstance() {
        ...
            for (int i=0; i<N; i++) {
                LoaderManagerImpl lm = loaders[i];
                if (lm.mRetaining) {
                    retainLoaders = true;
                } else {
                    lm.doDestroy();
                    mAllLoaderManagers.remove(lm.mWho);
                }
            }
    ...
    }

解决此问题的一种方法是更改​​ ModelFragment 以使 retainInstance 为 false。

【讨论】:

  • 但是我需要保留这个片段,因为在我的真实应用程序中它包含一个位图,我希望在配置更改时保留它。那么,这似乎是Android中的一个错误?
  • 是的,它看起来像一个错误。
【解决方案2】:

除了使用下一个解决方法之外,我还没有找到最佳解决方案。我已将 getLoaderManager().initLoader(0, null, this); 调用替换为 getActivity().getSupportLoaderManager().initLoader(0, null, this); 以使用活动的加载器管理器而不是片段的加载器管理器。我不知道这会产生哪些副作用,但似乎有效。如果在活动和片段中都使用加载器,则应确保没有潜在的加载器 ID 冲突。我还将initLoader 方法调用从onCreate 移动到onActivityCreated(当从onCreate 调用时,加载程序在配置更改后停止接收内容更新)。

【讨论】:

  • 有谁知道这在当前版本的支持库中是否已修复?
猜你喜欢
  • 2012-11-05
  • 1970-01-01
  • 2011-12-02
  • 1970-01-01
  • 2013-02-09
  • 1970-01-01
  • 2015-07-20
  • 2013-11-30
  • 1970-01-01
相关资源
最近更新 更多