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
Похоже, что это разметка фоновой нити ('RxComputThreadPool-1'). Это довольно типично для большинства таймеров. Возможно, реализация RxJava оставляет поток даже после того, как вы отпишетесь от него. – CommonsWare
@CommonsWare, как это могло бы утечка моей активности? Если это не ошибка с LeakCanary – ZakTaccardi
Вы создаете анонимный внутренний экземпляр класса. Каждый экземпляр анонимного внутреннего класса имеет неявную ссылку на экземпляр внешнего класса, который его создал. Пока поток может достигнуть вашего «Абонента», он может достичь вашей «Деятельности». Я не знаю достаточно о внутренностях RxJava, чтобы понять, почему он все равно узнает о вашем подписчике после того, как вы отмените подписку. –
CommonsWare