java - 为什么这个 Observable.timer() 会导致内存泄漏?

标签 java android memory-leaks rx-java leakcanary

LeakCanary通过 RxComputationThreadPool-1 报告我的 ArticleActivity 泄漏。因此,我将我的 ArticleContainerFragment.startTimer() 方法确定为导致该问题的方法。删除 Observable.timer() 调用的创建后,不再报告内存泄漏。不过我仍然需要使用这个计时器,所以你能帮我确定为什么会发生泄漏吗?我在所有我认为正确的地方取消订阅 - 所以我不确定为什么我一开始就被泄露了。

public class ArticleContainerFragment extends BaseFragment<ArticleContainerComponent, ArticleContainerPresenter> implements ArticleContainerView {
    @Bind(R.id.article_viewpager)
    ViewPager viewPager;

    @Inject
    ArticleContainerPresenter presenter;

    ArticleAdapter adapter;

    @Icicle
    @Nullable
    GenericArticleCategory genericArticleCategory;
    @Icicle
    ArticleStyle articleStyle;

    Subscription subscription;

    private Toolbar toolbar;

    @Nullable
    private Integer initialArticlePosition;

    public ArticleContainerFragment() {
    }

    public static ArticleContainerFragment newInstance(ArticleStyle articleStyle, GenericArticleCategory genericArticleCategory) {
        ArticleContainerFragment newFrag = new ArticleContainerFragment();
        newFrag.articleStyle = articleStyle;
        newFrag.genericArticleCategory = genericArticleCategory;
        return newFrag;
    }

    public static ArticleContainerFragment newInstance(@NonNull Integer initialArticlePosition) {
        ArticleContainerFragment newFrag = new ArticleContainerFragment();
        //TODO show facebook page for article categories that have one
        newFrag.articleStyle = ArticleStyle.MAIN;
        newFrag.initialArticlePosition = initialArticlePosition;
        return newFrag;
    }

    @Override
    public int getMenuResourceId() {
        return Utils.NO_MENU;
    }

    @Override
    public void loadArticlesIntoAdapter(List<ArticleViewModel> articleViewModelList) {
        adapter = getAdapter(articleViewModelList);
        viewPager.setAdapter(adapter);

        if (initialArticlePosition != null)
            viewPager.setCurrentItem(initialArticlePosition);

        startTimer();
    }

    @Override
    public void updateCounterText(int currentQuestion, int size) {
        getToolbar().setSubtitle(
                Html.fromHtml(
                        getString(R.string.article_toolbar_subtitle_counter, getViewPagerCurrentItem() + 1, size)
                )
        );
    }

    @Override
    public int getViewPagerCurrentItem() {
        return viewPager.getCurrentItem();
    }

    @Override
    public int getArticleTotalCount() {
        return adapter.getCount();
    }

    @Override
    public void startTimer() {
        Timber.v("Starting timer for article");
        subscription = Observable.timer(getResources().getInteger(R.integer.number_of_seconds_until_article_is_considered_viewed), TimeUnit.SECONDS)
                .take(1)
                .subscribe(new Subscriber<Long>() {
                    @Override
                    public void onCompleted() {
                        Timber.v("Completed observing whether user is reading article");
                    }

                    @Override
                    public void onError(Throwable e) {
                        Timber.e(e, "Error observing whether user is reading article");
                    }

                    @Override
                    public void onNext(Long aLong) {
                        presenter.userHasReadArticle();
                    }
                });
    }


    @Override
    public void stopTimer() {
        if (subscription != null) {
            Timber.v("Stopping timer for article");
            subscription.unsubscribe();
        }
    }

    @Override
    public String getCurrentArticlePermalink() {
        return adapter.getItem(getViewPagerCurrentItem())
                .getCurrentArticlePermalink();
    }

    @Override
    protected ArticleContainerComponent onCreateNonConfigurationComponent() {
        return DaggerArticleContainerComponent.builder()
                .appComponent(MyApplication.getComponent())
                .build();
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        getComponent().inject(this);
        super.onViewCreated(view, savedInstanceState);
        initViewPager();
    }

    @Override
    public void onDestroyView() {
        if (subscription != null)
            subscription.unsubscribe();

        super.onDestroyView();
    }

    @Override
    public int getLayoutResourceId() {
        return R.layout.article_container_fragment;
    }

    @Override
    public void onResume() {
        super.onResume();
        startTimer();
    }

    @Override
    public void onPause() {
        stopTimer();
        super.onPause();
    }

    private void initViewPager() {
        if (genericArticleCategory != null)
            presenter.loadArticles(genericArticleCategory.getId());
        else
            presenter.loadAllArticles();

        viewPager.setOffscreenPageLimit(3);
        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

            }

            @Override
            public void onPageSelected(int position) {
                MyAnimationUtils.showToolbar(getToolbar());
                presenter.pageChanged();

            }

            @Override
            public void onPageScrollStateChanged(int state) {

            }
        });
    }

    Toolbar getToolbar() {
        if (toolbar == null)
            toolbar = ((ArticleActivity) getActivity()).getToolbar();

        return toolbar;
    }

    public ArticleAdapter getAdapter(List<ArticleViewModel> articleViewModelList) {
        if (articleStyle == ArticleStyle.MAIN)
            return new MainArticleAdapter(getChildFragmentManager(), articleViewModelList);
        else
            return new UnitsArticleAdapter(getChildFragmentManager(), articleViewModelList);
    }
}

这是泄漏的 LeakCanary 日志。

In com.example:1.0:1.
* com.example.presentation.views.activities.ArticleActivity has leaked:
* GC ROOT thread java.lang.Thread.<Java Local> (named 'RxComputationThreadPool-1')
* references java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.queue
* references array java.util.concurrent.RunnableScheduledFuture[].[0]
* references java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.callable
* references java.util.concurrent.Executors$RunnableAdapter.task
* references rx.internal.schedulers.ScheduledAction.action
* references rx.internal.operators.OnSubscribeTimerOnce$1.val$child (anonymous class implements rx.functions.Action0)
* references rx.internal.operators.OperatorTake$1.val$child (anonymous class extends rx.Subscriber)
* references rx.observers.SafeSubscriber.actual
* references com.example.presentation.views.fragments.ArticleContainerFragment$1.this$0 (anonymous class extends rx.Subscriber)
* references com.example.presentation.views.fragments.ArticleContainerFragment.componentCache
* leaks com.example.presentation.views.activities.ArticleActivity instance

* Reference Key: 2606e3f1-ad28-4727-b8d2-60e084c6389c
* Device: motorola google Nexus 6 shamu
* Android Version: 5.1.1 API: 22 LeakCanary: 1.3.1
* Durations: watch=5161ms, gc=161ms, heap dump=10786ms, analysis=24578ms

* Details:
* Instance of java.lang.Thread
|   static $staticOverhead = byte[] [id=0x711bccc9;length=48;size=64]
|   static MAX_PRIORITY = 10
|   static MIN_PRIORITY = 1
|   static NANOS_PER_MILLI = 1000000
|   static NORM_PRIORITY = 5
|   static count = 14733
|   static defaultUncaughtHandler = com.google.android.gms.analytics.ExceptionReporter [id=0x12ea4500]
|   contextClassLoader = dalvik.system.PathClassLoader [id=0x12c92de0]
|   daemon = true
|   group = java.lang.ThreadGroup [id=0x71058148]
|   hasBeenStarted = true
|   id = 14703
|   inheritableValues = null
|   interruptActions = java.util.ArrayList [id=0x12f2b240]
|   localValues = null
|   lock = java.lang.Object [id=0x12f19c20]
|   name = java.lang.String [id=0x12f2b220]
|   nativePeer = -1264342016
|   parkBlocker = java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject [id=0x12f0e100]
|   parkState = 3
|   priority = 5
|   stackSize = 0
|   target = java.util.concurrent.ThreadPoolExecutor$Worker [id=0x12f25370]
|   uncaughtHandler = null
* Instance of java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue
|   static $staticOverhead = byte[] [id=0x12ee9401;length=8;size=24]
|   static INITIAL_CAPACITY = 16
|   available = java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject [id=0x12f0e100]
|   leader = java.lang.Thread [id=0x12f22340]
|   lock = java.util.concurrent.locks.ReentrantLock [id=0x12f02fa0]
|   queue = java.util.concurrent.RunnableScheduledFuture[] [id=0x12efdf60;length=16]
|   size = 3
* Array of java.util.concurrent.RunnableScheduledFuture[]
|   [0] = java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask [id=0x12fe3f00]
|   [1] = java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask [id=0x12c5e2c0]
|   [2] = java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask [id=0x12c5e0c0]
|   [3] = null
|   [4] = null
|   [5] = null
|   [6] = null
|   [7] = null
|   [8] = null
|   [9] = null
|   [10] = null
|   [11] = null
|   [12] = null
|   [13] = null
|   [14] = null
|   [15] = null
* Instance of java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask
|   heapIndex = 0
|   outerTask = java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask [id=0x12fe3f00]
|   period = 0
|   sequenceNumber = 53
|   this$0 = java.util.concurrent.ScheduledThreadPoolExecutor [id=0x12efde70]
|   time = 32159209571737
|   callable = java.util.concurrent.Executors$RunnableAdapter [id=0x1327bf20]
|   outcome = null
|   runner = null
|   state = 0
|   waiters = null
* Instance of java.util.concurrent.Executors$RunnableAdapter
|   result = null
|   task = rx.internal.schedulers.ScheduledAction [id=0x1314fb80]
* Instance of rx.internal.schedulers.ScheduledAction
|   static $staticOverhead = byte[] [id=0x12d794e1;length=8;size=24]
|   static serialVersionUID = -3962399486978279857
|   action = rx.internal.operators.OnSubscribeTimerOnce$1 [id=0x1327bef0]
|   cancel = rx.internal.util.SubscriptionList [id=0x1327bf00]
|   value = null
* Instance of rx.internal.operators.OnSubscribeTimerOnce$1
|   this$0 = rx.internal.operators.OnSubscribeTimerOnce [id=0x1314f7e0]
|   val$child = rx.internal.operators.OperatorTake$1 [id=0x1310fd30]
* Instance of rx.internal.operators.OperatorTake$1
|   completed = false
|   count = 0
|   this$0 = rx.internal.operators.OperatorTake [id=0x1327be60]
|   val$child = rx.observers.SafeSubscriber [id=0x1310fd00]
|   cs = rx.internal.util.SubscriptionList [id=0x1327bea0]
|   op = null
|   p = null
|   requested = -9223372036854775808
* Instance of rx.observers.SafeSubscriber
|   actual = com.example.presentation.views.fragments.ArticleContainerFragment$1 [id=0x1310fca0]
|   done = false
|   cs = rx.internal.util.SubscriptionList [id=0x1327be90]
|   op = com.example.presentation.views.fragments.ArticleContainerFragment$1 [id=0x1310fca0]
|   p = null
|   requested = -9223372036854775808
* Instance of com.example.presentation.views.fragments.ArticleContainerFragment$1
|   this$0 = com.example.presentation.views.fragments.ArticleContainerFragment [id=0x130683a0]
|   cs = rx.internal.util.SubscriptionList [id=0x1327be90]
|   op = null
|   p = null
|   requested = -9223372036854775808
* Instance of com.example.presentation.views.fragments.ArticleContainerFragment
|   adapter = com.example.presentation.views.adapters.MainArticleAdapter [id=0x13131f40]
|   articleStyle = com.example.presentation.views.enums.ArticleStyle [id=0x12f047c0]
|   genericArticleCategory = null
|   initialArticlePosition = java.lang.Integer [id=0x71054ef8]
|   presenter = com.example.presentation.presenters.ArticleContainerPresenter [id=0x13140b00]
|   subscription = rx.observers.SafeSubscriber [id=0x1340ff70]
|   toolbar = android.support.v7.widget.Toolbar [id=0x130ba400]
|   viewPager = null
|   presenterDelegate = com.example.presentation.presenters.base.PresenterControllerDelegate [id=0x1327b7f0]
|   componentCache = com.example.presentation.views.activities.ArticleActivity [id=0x12df6700]
|   componentDelegate = com.example.presentation.presenters.base.ComponentControllerDelegate [id=0x1313fc60]
|   componentFactory = com.example.presentation.presenters.base.ComponentControllerFragment$1 [id=0x1327b7e0]
|   mActivity = null
|   mAdded = false
|   mAllowEnterTransitionOverlap = null
|   mAllowReturnTransitionOverlap = null
|   mAnimatingAway = null
|   mArguments = null
|   mBackStackNesting = 0
|   mCalled = true
|   mCheckedForLoaderManager = false
|   mChildFragmentManager = null
|   mContainer = null
|   mContainerId = 0
|   mDeferStart = false
|   mDetached = false
|   mEnterTransition = null
|   mEnterTransitionCallback = null
|   mExitTransition = null
|   mExitTransitionCallback = null
|   mFragmentId = 0
|   mFragmentManager = null
|   mFromLayout = false
|   mHasMenu = false
|   mHidden = false
|   mInLayout = false
|   mIndex = -1
|   mInnerView = null
|   mLoaderManager = null
|   mLoadersStarted = false
|   mMenuVisible = true
|   mNextAnim = 0
|   mParentFragment = null
|   mReenterTransition = java.lang.Object [id=0x12e87aa0]
|   mRemoving = false
|   mRestored = false
|   mResumed = false
|   mRetainInstance = false
|   mRetaining = false
|   mReturnTransition = java.lang.Object [id=0x12e87aa0]
|   mSavedFragmentState = null
|   mSavedViewState = null
|   mSharedElementEnterTransition = null
|   mSharedElementReturnTransition = java.lang.Object [id=0x12e87aa0]
|   mState = 0
|   mStateAfterAnimating = 0
|   mTag = null
|   mTarget = null
|   mTargetIndex = -1
|   mTargetRequestCode = 0
|   mUserVisibleHint = true
|   mView = null
|   mWho = null
* Instance of com.example.presentation.views.activities.ArticleActivity
|   static $staticOverhead = byte[] [id=0x12fc8001;length=24;size=40]
|   static ARTICLE_CATEGORY_ID_KEY = java.lang.String [id=0x130a3f00]
|   static INITIAL_ARTICLE_TO_LOAD_KEY = java.lang.String [id=0x13083c20]
|   static TOOLBAR_TITLE_KEY = java.lang.String [id=0x130a3f80]
|   genericArticleCategory = null
|   initialArticleToLoad = 0
|   toolbar = null
|   toolbarTitle = java.lang.String [id=0x12dfe9c0]
|   delegate = com.example.presentation.presenters.base.ComponentCacheDelegate [id=0x1327b190]
|   mDelegate = android.support.v7.app.AppCompatDelegateImplV14 [id=0x1342e560]
|   mAllLoaderManagers = android.support.v4.util.SimpleArrayMap [id=0x1314b3a0]
|   mCheckedForLoaderManager = true
|   mContainer = android.support.v4.app.FragmentActivity$2 [id=0x1327b180]
|   mCreated = true
|   mFragments = android.support.v4.app.FragmentManagerImpl [id=0x130e4eb0]
|   mHandler = android.support.v4.app.FragmentActivity$1 [id=0x1311fd80]
|   mLoaderManager = null
|   mLoadersStarted = false
|   mOptionsMenuInvalidated = false
|   mReallyStopped = true
|   mResumed = false
|   mRetaining = false
|   mStopped = true
|   mActionBar = null
|   mActivityInfo = android.content.pm.ActivityInfo [id=0x1322f400]
|   mActivityTransitionState = android.app.ActivityTransitionState [id=0x12fa3380]
|   mAllLoaderManagers = android.util.ArrayMap [id=0x1313fd00]
|   mApplication = com.example.MyApplication [id=0x12c93620]
|   mCalled = true
|   mChangeCanvasToTranslucent = false
|   mChangingConfigurations = false
|   mCheckedForLoaderManager = true
|   mComponent = android.content.ComponentName [id=0x131881a0]
|   mConfigChangeFlags = 0
|   mContainer = android.app.Activity$1 [id=0x1327b150]
|   mCurrentConfig = android.content.res.Configuration [id=0x131fd580]
|   mDecor = null
|   mDefaultKeyMode

最佳答案

严格来说,它不会泄漏 Activity。但它会在unsubscribe之后保留引用一段时间,直到Observable返回流量控制。问题的完整描述如下:https://github.com/ReactiveX/RxJava/issues/1292

基本上,Observable 将保留对 订阅者 的引用,直到 onCompleteonErrorunsubscribe 事件已处理。在您的情况下,直到 Observable.timer 将从 sleep 中恢复。由于您在 Observable.timer 完成之前请求取消订阅,因此 unsubscribe 的处理(释放资源和空订阅者引用)将被延迟,直到该事件发生被触发。

因此,您的 Observable.timer 保存对您的 Subscriber 的引用,它保存对您的 fragment 的引用,而 fragment 保存对您的 Activity 的引用 (ArticleContainerFragment.componentCache )。解决方案非常简单:永远不要使用长期运行的 Observables 来引用 Subscribers 的 Activity 。只需在 Presenter 内部创建这个 Observable.timer 而不是 fragment 。或者使 fragment 不包含对 Activity 的引用。

关于java - 为什么这个 Observable.timer() 会导致内存泄漏?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31466215/

相关文章:

java - java中相对于绝对路径

java - 计算 HashMap 索引的平均值

java - 如何将图像保存到android中的Camera文件夹?

android - 未找到连接的设备;请连接设备

c# - 计时器和垃圾收集

java - 与 mySQL 一起使用的自动 java 类

java - 为什么以及何时在 android 中取消注册内容观察者

java - 如何在文本框下方显示三个标签和微调器?

模板函数 <queue> 中的 C++ 内存错误

java - 未关闭的流是否会导致 java 中的内存泄漏?