1

LeakCanaryArticleActivity通过RxComputationThreadPool-1. _ 因此,我将我的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
4

1 回答 1

4

It doesn't leak Activity strictly speaking. But it holds reference after unsubscribe for some time until Observable returns flow control. Full description of the issue is here: https://github.com/ReactiveX/RxJava/issues/1292.

Basically Observable will hold a reference to the Subscriber until onComplete, onError or unsubscribe event is processed. In your case until Observable.timer will come back from sleep. 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.

So your Observable.timer holds reference to your Subscriber which holds reference to your fragment which holds reference to your activity (ArticleContainerFragment.componentCache). The solution is quite easy: don't hold reference to the activity at Subscribers with long-running Observables ever. Just create this Observable.timer inside of the Presenter and not fragment. Or make fragment to not hold reference to activity.

于 2015-07-17T20:35:53.607 回答