简体   繁体   English

为什么此Observable.timer()导致内存泄漏?

[英]Why does this Observable.timer() cause a memory leak?

LeakCanary reports a leak for my ArticleActivity via a RxComputationThreadPool-1 . LeakCanary通过RxComputationThreadPool-1报告我的ArticleActivity泄漏。 So, I identified my ArticleContainerFragment.startTimer() method as the one that's causing it. 因此,我确定了我的ArticleContainerFragment.startTimer()方法是引起该问题的方法。 After removing the creation of my Observable.timer() call, no more memory leak reported. 删除我的Observable.timer()调用的创建后,不再报告内存泄漏。 I still need to use this timer though, so can you help me identify why a leak is occurring? 不过,我仍然需要使用此计时器,因此您可以帮助我确定为什么发生泄漏吗? I am unsubscribing in all the right places I believe - so I am not sure why I am even getting a leak in the first place. 我取消订阅我所相信的所有正确位置-因此我不确定为什么我一开始就出现漏洞。

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);
    }
}

Here is the LeakCanary log for the leak. 这是泄漏的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

It doesn't leak Activity strictly speaking. 严格来讲,它不会泄漏活动。 But it holds reference after unsubscribe for some time until Observable returns flow control. 但是它在unsubscribe一段时间后一直保持引用,直到Observable返回流控制为止。 Full description of the issue is here: https://github.com/ReactiveX/RxJava/issues/1292 . 问题的完整描述在这里: https : //github.com/ReactiveX/RxJava/issues/1292

Basically Observable will hold a reference to the Subscriber until onComplete , onError or unsubscribe event is processed. 基本上, Observable将保留对Subscriber的引用,直到处理onCompleteonErrorunsubscribe事件为止。 In your case until Observable.timer will come back from sleep. 在您的情况下,直到Observable.timer将从睡眠中恢复。 Since you are requesting unsubscribe before Observable.timer finishes, then processing of unsubscribe (releasing resources and null subscriber reference) is delayed until that event would be triggered. 由于您要在Observable.timer完成之前请求unsubscribe ,因此延迟了unsubscribe (释放资源和空订阅者引用)的处理,直到触发该事件为止。

So your Observable.timer holds reference to your Subscriber which holds reference to your fragment which holds reference to your activity (ArticleContainerFragment.componentCache). 因此,您的Observable.timer保留对Subscriber引用,而该Subscriber对您的fragment引用也对您的活动(ArticleContainerFragment.componentCache)进行引用。 The solution is quite easy: don't hold reference to the activity at Subscribers with long-running Observables ever. 解决方案非常简单:永远不要在具有长期运行的Observables Subscribers上引用活动。 Just create this Observable.timer inside of the Presenter and not fragment. 只需在Presenter内部创建此Observable.timer ,而不是片段。 Or make fragment to not hold reference to activity. 或制作片段以不引用活动。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM