2015-07-16 4 views
0

LeakCanary сообщает об утечке для моего ArticleActivity через RxComputationThreadPool-1. Итак, я определил свой метод ArticleContainerFragment.startTimer() как тот, который его вызывает. После удаления создания моего Observable.timer() вызова больше не сообщалось о утечке памяти. Мне все еще нужно использовать этот таймер, так что вы можете помочь мне определить, почему происходит утечка? Я отказываюсь от подписки во всех правильных местах, которые, как я полагаю, поэтому я не уверен, почему я даже получаю утечку в первую очередь.Почему этот 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 
+0

Похоже, что это разметка фоновой нити ('RxComputThreadPool-1'). Это довольно типично для большинства таймеров. Возможно, реализация RxJava оставляет поток даже после того, как вы отпишетесь от него. – CommonsWare

+0

@CommonsWare, как это могло бы утечка моей активности? Если это не ошибка с LeakCanary – ZakTaccardi

+0

Вы создаете анонимный внутренний экземпляр класса. Каждый экземпляр анонимного внутреннего класса имеет неявную ссылку на экземпляр внешнего класса, который его создал. Пока поток может достигнуть вашего «Абонента », он может достичь вашей «Деятельности». Я не знаю достаточно о внутренностях RxJava, чтобы понять, почему он все равно узнает о вашем подписчике после того, как вы отмените подписку. – CommonsWare

ответ

2

Не протекает деятельность строго говоря. Но он сохраняет ссылку после unsubscribe в течение некоторого времени, пока Observable не вернет управление потоком. Полное описание проблемы находится здесь: https://github.com/ReactiveX/RxJava/issues/1292.

В основном Observable будет содержать ссылку на Subscriber до onComplete, onError или unsubscribe событие обрабатывается. В вашем случае до тех пор, пока Observable.timer не вернется из сна. Поскольку вы запрашиваете unsubscribe до завершения Observable.timer, обработка unsubscribe (освобождение ресурсов и нулевая ссылка подписчика) задерживается до тех пор, пока это событие не будет запущено.

Так что ваш Observable.timer держит ссылку на ваш Subscriber который содержит ссылку на ваш fragment который содержит ссылку на вашу деятельность (ArticleContainerFragment.componentCache). Решение довольно просто: не держите ссылку на активность на Subscribers с длинным Observables когда-либо. Просто создайте этот Observable.timer внутри Presenter, а не фрагмент. Или сделайте фрагмент, чтобы не ссылаться на активность.

+0

помещают подписку в презентаторе и больше не теряют память. Благодаря! – ZakTaccardi

Смежные вопросы