Если бы я публиковал код, мне нужно было бы опубликовать как минимум 1 тыс. строк о 6 разных классах... и 3 разных архитектурах. Поэтому я дам вам как можно более краткую информацию... также утечку путем передачи (это ) кажется запоминающимся, поэтому я надеюсь, что кто-нибудь знаком с этой проблемой.
Я использую
Код: Выделить всё
com.squareup.leakcanary:leakcanary-android:3.0-alpha-1
A Lifecycle has a beginning and an end, each of which has inner phases, that begin and end in a stacked type FIFO manner.
Where each element has 2 states, a "creating" state and a "destroying" state.
Whenever an element is pushed, it adopts a creating state, meanwhile, at the moment of pop, it adopts a destroying state.
This means that there is a preestablish immovable sequence... something that Android lacks when dealing with lifecycle method callbacks inside Fragments (onAttach, onCreate, onSavedInstanceState)...
Код: Выделить всё
CREATE --> DESTROY
| ^
v |
START --> STOP
| ^
v |
RESUME --> PAUSE
Lifecycle A BEGINS FIRST, then B, then C.
C will DIE FIRST than B, than A.
Like a Matryoshka doll of nested lifecycles.
THIS IS A CORRECT Lifecycle'd architecture. In a correct implementation, each of the destructive phases should be a MIRROR of its creating phases, and the events should be triggered in a FIFO manner...
If Lifecycle C needs to LISTEN something within Lifecycle A, IT's OBSERVER NEEDS to have the SAME LIFESPAN of its owner, in this case of C. (If you are familiar with Android's ViewLifeCycleOwner this is the principle.)
This avoids Lapsed Listeners... aka memory leaks.
BUT LISTENING is one thing...
Код: Выделить всё
register(t -> {});
Код: Выделить всё
unregister();
In reality the "reactive" in a
Код: Выделить всё
register(t -> {});
Код: Выделить всё
{}
But the action itself... is proactive... the
Код: Выделить всё
unregister();
Now let me present you with the false positive that is "leaking" according to LeakCanary...
Код: Выделить всё
private final LazyHolder.Supplier summaryVM2 =
syncActivator(Phase.resume,
acquireContext(
context -> new ByDaySummaryVM2(((MainActivity)
context).getModel()) {
@Override
protected void onStateChange(boolean isActive) {
if (isActive) {
memberStatsManager.setVM(this);
elementStatsManager.setVM(this);
}
}
}
)
);
Код: Выделить всё
Phase.resume
Код: Выделить всё
.activate()
Код: Выделить всё
acquireContext(context ->
Код: Выделить всё
Function
Код: Выделить всё
Phase.resume
Now... the
Код: Выделить всё
.setVM(this);
Both managers build their own inner Lifecycled Objects... with a Lifespan "NARROWER/SHORTER" than the scope that owns the
Код: Выделить всё
LazyHolder.Supplier
When
Код: Выделить всё
setVM(this);
At entry point... the same reactive library is used to capture itself.... this is the '
Код: Выделить всё
this
Код: Выделить всё
private final In.Consume builderConsumer = new In.Consume();
private final In.Consume type = new In.Consume();
private final Path resolve =
builderConsumer.switchMap(
type::map
);
This means that there is a reactive architecture WITHIN it's own reactive architecture...
that... based on a 'displayType' value PLUS a displayResolve variable... создает серию преобразований и switchMaps, создавая целую новую реактивную ветвь, хранящуюся в
Код: Выделить всё
resolve
THEN the 'leaves' of these newly created components are attached to the Lifecycle of the shorter-lived life-cycled objects.
To match the example:
The bigger scope (the class containing the
Код: Выделить всё
LazyHolder.Supplier
The view model
Код: Выделить всё
ByDaySummaryVM2
Both Managers containing the smaller Objects, would be both Lifecycle C (even tho they may be more like a B.1 since their lifespans are more 'parallel' to that of the view-model).
When C dies, B will die after, then A... This is the moment that a "leak" appears.
Now...
The ViewModel (Lifecycle B) CONNECTS to a reactive source... this is the "WIDEST/LONGEST" Lifecycle of them all... "Lifecycle Zed"...
The issue...
It appears as if a Listener that belongs to "Lifecyle A"... (the outer scope), is attached DIRECTLY to something upstream (Lifecycle Zed).
So, the order of the "dominators" according to LeakCannary is:
A -> Zed.
paraphrasing the error message:
Код: Выделить всё
"Zed scope static storage"
LeakCanary D │ ↓ OuterScope$2.this$0
LeakCanary D │ ~~~~~~
LeakCanary D ╰→ OuterScope instance
LeakCanary D Leaking: YES (ObjectWatcher was watching this because OuterScope received Fragment#onDestroy() callback. Conflicts with Fragment.mLifecycleRegistry.state is
LeakCanary D INITIALIZED)
LeakCanary D Retaining 65.6 kB in 1980 objects
LeakCanary D key = 9e3903cd-4cd8-4e9e-bdda-24ba91735925
LeakCanary D watchDurationMillis = 960661
LeakCanary D retainedDurationMillis = 955661
I am not even touching the LifecycleRegistry!!
When testing the Observers registered one by one... the REALITY seems to be
A -> C -> B -> A -> Zed.
What's wrong with this indictment??? ...
There are NO listeners from A registered to Zed... None whatsoever.
IN THEORY:
Once the listeners (of C) connected to these NEWLY CREATED branches (created during B's initialization) ... disconnect... the entire newly created reactive branch... gets deactivated.
Then C gets popped, B gets popped... and finally A gets popped from their Collections...
The inactive reactive branch... will remain in the
Код: Выделить всё
metaspace
Код: Выделить всё
metaspace
Even if the inactive (newly created) branch (of the reactive architecture) that was built inside the 'A -> B -> C' structure remains tied to some portion of the reactive... active portion of the branch that's still being used("Lifecycle Zed")... it does so in a PROACTIVE manner... without any Observers registered.
So....
When the Virtual Machine acknowledges that interdependencies are NO LONGER connected, that CONTAIN instances of the branch (ABC structure) ... they SHOULD GET garbage collected... AS LONG as there are NO Observers of a "WIDER" lifespan registered to this reactive branch.
In this case the widest lifespan would be that of "Context"(Zed)... YET everything happening with Context... at the point of destruction... IS PROACTIVELY interacted with... (this is corroborated by the '2-second-delayed assertion' at the end of the solution bellow...)
**Will the reactive architecture WITHIN reactive architecture make a memory leak???...
NON whatsoever.... the same PROACTIVE rule applies.**
What is the fix???
Код: Выделить всё
private final LazyHolder.Supplier summaryVM2 =
syncActivator(Phase.resume,
acquireContext(
context ->
new FalsePositiveLeakagePrevention(
memberStatsManager
, elementStatsManager
, ((MainActivity)context).getModel()
)
)
);
Код: Выделить всё
//Both managers' lifecycle are shorter than that of OuterScope... ДА, OuterScope считается "захваченным"
// узлом из статически хранимой ветки (в Zed) ЧЕРЕЗmemberStatsManager.
частный статический класс FalsePositiveLeakagePrevention расширяет ByDaySummaryVM2 {
частный окончательный менеджер
memberStatsManager;
частный окончательный менеджер elementStatsManager;
public FalsePositiveLeakagePrevention(
МенеджерmemberStatsManager
, Менеджер elementStatsManager
, Поставщик< Element.Constant.DAOS.MemoryModel> model
) {
super(model);
this.memberStatsManager =memberStatsManager;
this.elementStatsManager = elementStatsManager;
}< br />
protected Final void onStateChange(boolean isActive) {
if (isActive) {
memberStatsManager.setVM(this);
elementStatsManager.setVM(this) ;
//Абсолютное подтверждение отсутствия утечки...
else {
Post.TWO_SEC_delay(
() -> {
try {
AssertNotActive();
} catch (Exception | Ошибка e) {
Log.println(Log.ERROR, TAG, "onStateChange: ");
выдать новое исключение RuntimeException(e);
) ;
>
Могут быть некоторые причины, по которым это устраняет утечку...
-
Может быть, есть некоторые забавные вещи происходит в пространстве памяти,
поскольку ViewModel объединяет 2 статически сохраненных источника во время выполнения....
возможно, все построенное тело сохраняется в OldGeneration... и потому что действие
выполняется в полувидимой области видимости OuterScope... область действия хранится в
этом пространстве памяти... следовательно, выполняя эту транзакцию внутри статического отдельного тела, OuterScope больше не становится частью OldGen. (Это не объясняет предполагаемое несоответствие в состоянии реестра жизненного цикла) -
Другой возможностью может быть ложное срабатывание, когда LeakCanary
останавливается наand loops past the direct scope of theКод: Выделить всё
.setVM(this);
ViewModel in the reference graph and goes straight to the outer
scope. - A third possibility is that the 'Reactive-Within-Reactive' is "messing up" the reference graph when analyzed by LeakCannary.
-
Now the fourth possibility is... there is still a memory leak... but
LeakCannary cannot identify it, (unlikely because of the type of
fix, and the corroboration on all fronts.).
Источник: https://stackoverflow.com/questions/781 ... may-be-a-f