LeakCanary通过ArticleActivity
报告我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
答案 0 :(得分:3)
严格来说,它不会泄漏活动。但它在function loadRates(days) {
return window.fetch(url).then(function(response) {
return response.json(); // needed to parse raw response data
}).then(function(response) {
localStorage.setItem("rates" + days, response);
return response; // this return is mandatory, otherwise further `then` calls will get undefined as argument
});
}
Promise.all([
loadRates(0),
loadRates(1)
]).then(function(rates) {
return buildTable(rates)
})
之后保留了一段时间,直到unsubscribe
返回流量控制。该问题的完整描述如下:https://github.com/ReactiveX/RxJava/issues/1292。
基本上Observable
将保留对Observable
的引用,直到处理Subscriber
,onComplete
或onError
事件为止。在你的情况下,unsubscribe
将从睡眠中恢复。由于您在Observable.timer
完成之前请求unsubscribe
,因此Observable.timer
(释放资源和空订阅者引用)的处理将被延迟,直到触发该事件为止。
因此,您的unsubscribe
会保留对Observable.timer
的引用,该Subscriber
会引用您的fragment
引用您的活动(ArticleContainerFragment.componentCache)。解决方案非常简单:不要长时间运行Subscribers
Observables
处的活动。只需在Observable.timer
内创建此Presenter
而不是片段。或者使片段不能保持对活动的引用。