Skip to content

Commit 7dca42a

Browse files
Fabio Carballofacebook-github-bot
authored andcommitted
Use after pooling fields state to identify changes on fields state before release.
Summary: In order for a more accurate verification of the improper release of items, we are adding extra checks. We are now recording the state of the fields right when the item is created/pooled. This state is then compared to the one at the moment of the item release back into the pool. This allow us to ignore situations where there is a field/listener that is already set from the initial moment (as part of custom view for example). Reviewed By: adityasharat Differential Revision: D60234841 fbshipit-source-id: 7f4cd8a7d5a8d2f09365e8b90663b503c9fedf66
1 parent 71bc4cb commit 7dca42a

File tree

2 files changed

+58
-29
lines changed

2 files changed

+58
-29
lines changed

litho-rendercore/src/main/java/com/facebook/rendercore/MountItemPoolsReleaseValidator.kt

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import android.widget.TextView
2424
import androidx.core.view.children
2525
import com.facebook.kotlin.compilerplugins.dataclassgenerate.annotation.DataClassGenerate
2626
import java.lang.reflect.Field
27+
import java.util.WeakHashMap
2728

2829
/**
2930
* Inspects a [View] to understand if it has any view properties that were not cleaned up before the
@@ -43,6 +44,8 @@ internal class MountItemPoolsReleaseValidator(
4344
extraFields: List<FieldExtractionDefinition> = emptyList()
4445
) {
4546

47+
private val pooledViewsToInitialState = WeakHashMap<View, Set<FieldState>>()
48+
4649
private val fields =
4750
setOf(
4851
FieldExtractionDefinition("touchListener") {
@@ -87,6 +90,15 @@ internal class MountItemPoolsReleaseValidator(
8790
}
8891
}) + extraFields
8992

93+
fun registerAcquiredViewState(view: View) {
94+
pooledViewsToInitialState[view] =
95+
fields.map { field -> FieldState(field.id, field.extractor(view)) }.toSet()
96+
97+
if (view is ViewGroup) {
98+
view.children.forEach { child -> registerAcquiredViewState(child) }
99+
}
100+
}
101+
90102
fun assertValidRelease(view: View, hierarchyIdentifiers: List<String>) {
91103
if (!BuildConfig.DEBUG) {
92104
return
@@ -108,25 +120,34 @@ internal class MountItemPoolsReleaseValidator(
108120
return
109121
}
110122

111-
val unreleasedFields = fields.filter { field -> field.extractor(view) != null }
112-
if (unreleasedFields.isNotEmpty()) {
113-
val result = buildString {
114-
append("Improper release detected: ${currentHierarchyIdentifier}\n")
115-
unreleasedFields.forEach { field -> append("- ${field.id} | ${field.extractor(view)}\n") }
116-
117-
if (view is TextView) {
118-
append("- text=${view.text}\n")
123+
val beforeReleaseFieldsState =
124+
fields.map { field -> FieldState(field.id, field.extractor(view)) }.toSet()
125+
126+
val afterPoolFieldsState = pooledViewsToInitialState.remove(view)
127+
if (beforeReleaseFieldsState != afterPoolFieldsState) {
128+
val differentFieldsState =
129+
if (afterPoolFieldsState == null) beforeReleaseFieldsState
130+
else beforeReleaseFieldsState.minus(afterPoolFieldsState)
131+
132+
val unreleasedFields = differentFieldsState.filter { field -> field.value != null }
133+
if (unreleasedFields.isNotEmpty()) {
134+
val result = buildString {
135+
append("Improper release detected: ${currentHierarchyIdentifier}\n")
136+
unreleasedFields.forEach { field -> append("- ${field.id} | ${field.value}\n") }
137+
138+
if (view is TextView) {
139+
append("- text=${view.text}\n")
140+
}
141+
append("\n")
119142
}
120-
append("\n")
121-
}
122143

123-
onInvalidRelease?.invoke(InvalidReleaseToMountPoolException(result))
144+
onInvalidRelease?.invoke(InvalidReleaseToMountPoolException(result))
124145

125-
if (failOnDetection) {
126-
assert(false) { result }
127-
} else {
128-
Log.d(TAG, currentHierarchyIdentifier)
129-
Log.d(TAG, result)
146+
if (failOnDetection) {
147+
assert(false) { result }
148+
} else {
149+
Log.d(TAG, result)
150+
}
130151
}
131152
}
132153
}
@@ -170,6 +191,8 @@ internal class MountItemPoolsReleaseValidator(
170191

171192
@DataClassGenerate
172193
data class FieldExtractionDefinition(val id: String, val extractor: (View) -> Any?)
194+
195+
@DataClassGenerate data class FieldState(val id: String, val value: Any?)
173196
}
174197

175198
private const val TAG = "MountReleaseValidator"

litho-rendercore/src/main/java/com/facebook/rendercore/MountItemsPool.kt

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -92,19 +92,25 @@ object MountItemsPool {
9292
}
9393

9494
return if (content != null) {
95-
content
96-
} else {
97-
val isTracing = RenderCoreSystrace.isTracing()
98-
if (isTracing) {
99-
RenderCoreSystrace.beginSection(
100-
"MountItemsPool:createMountContent ${poolableMountContent.getPoolableContentType().simpleName}")
101-
}
102-
val content = poolableMountContent.createPoolableContent(context)
103-
if (isTracing) {
104-
RenderCoreSystrace.endSection()
105-
}
106-
content
107-
}
95+
content
96+
} else {
97+
val isTracing = RenderCoreSystrace.isTracing()
98+
if (isTracing) {
99+
RenderCoreSystrace.beginSection(
100+
"MountItemsPool:createMountContent ${poolableMountContent.getPoolableContentType().simpleName}")
101+
}
102+
val content = poolableMountContent.createPoolableContent(context)
103+
if (isTracing) {
104+
RenderCoreSystrace.endSection()
105+
}
106+
107+
content
108+
}
109+
.also { content ->
110+
if (content is View) {
111+
mountItemPoolsReleaseValidator?.registerAcquiredViewState(content)
112+
}
113+
}
108114
}
109115

110116
@JvmStatic

0 commit comments

Comments
 (0)