[英]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
的引用,直到处理onComplete
, onError
或unsubscribe
事件为止。 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.