java - Why does this Observable.timer() cause a memory leak? -
leakcanary reports leak articleactivity
via rxcomputationthreadpool-1
. so, identified articlecontainerfragment.starttimer()
method 1 that's causing it. after removing creation of observable.timer()
call, no more memory leak reported. still need use timer though, can me identify why leak occurring? unsubscribing in right places believe - not sure why getting leak in 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 article categories have 1 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 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 reading article"); } @override public void onerror(throwable e) { timber.e(e, "error observing whether user reading article"); } @override public void onnext(long along) { presenter.userhasreadarticle(); } }); } @override public void stoptimer() { if (subscription != null) { timber.v("stopping timer 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 leakcanary log leak.
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. holds reference after unsubscribe
time until observable
returns flow control. full description of issue here: https://github.com/reactivex/rxjava/issues/1292.
basically observable
hold reference subscriber
until oncomplete
, onerror
or unsubscribe
event processed. in case until observable.timer
come sleep. since requesting unsubscribe
before observable.timer
finishes, processing of unsubscribe
(releasing resources , null subscriber reference) delayed until event triggered.
so observable.timer
holds reference subscriber
holds reference fragment
holds reference activity (articlecontainerfragment.componentcache). solution quite easy: don't hold reference activity @ subscribers
long-running observables
ever. create observable.timer
inside of presenter
, not fragment. or make fragment not hold reference activity.
Comments
Post a Comment