Repository: square/leakcanary
Issue: AndroidLeakFixes$TEXT_LINE_POOL should not hold TextLine.sCached

Not sure why but Google's CI found that the code that's attempting to clear the sCached leak ends up on the shortest path for that leak, which makes no sense. The best way to avoid that is simply to not cache sCached but instance retrieve it each time, which isn't all that costly anyway.

Related to https://android-review.googlesource.com/c/platform/frameworks/support/+/2420704/2#message-f6a1392ab5b07a2aed5111e5ca410cf0eaaf00b6

```
┬───
│ GC Root: System class
│
├─ leakcanary.internal.InternalLeakCanary class
│    Leaking: NO (LeakCanaryApp↓ is not leaking and a class is never leaking)
│    ↓ static InternalLeakCanary._application
├─ androidx.fragment.app.LeakCanaryApp instance
│    Leaking: NO (Application is a singleton)
│    mBase instance of android.app.ContextImpl
│    ↓ Application.mActivityLifecycleCallbacks
│                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~
├─ java.util.ArrayList instance
│    Leaking: UNKNOWN
│    Retaining 440 B in 20 objects
│    ↓ ArrayList[2]
│               ~~~
├─ leakcanary.AndroidLeakFixes$Companion$onActivityDestroyed$1 instance
│    Leaking: UNKNOWN
│    Retaining 44 B in 3 objects
│    Anonymous class implementing android.app.Application$ActivityLifecycleCallbacks
│    ↓ AndroidLeakFixes$Companion$onActivityDestroyed$1.$block
│                                                       ~~~~~~
├─ leakcanary.AndroidLeakFixes$TEXT_LINE_POOL$apply$1$3 instance
│    Leaking: UNKNOWN
│    Retaining 16 B in 1 objects
│    Anonymous subclass of kotlin.jvm.internal.Lambda
│    ↓ AndroidLeakFixes$TEXT_LINE_POOL$apply$1$3.$sCached
│                                                ~~~~~~~~
├─ android.text.TextLine[] array
│    Leaking: UNKNOWN
│    Retaining 36.6 kB in 587 objects
│    ↓ TextLine[1]
│              ~~~
```

Changed files:
plumber-android-core/src/main/java/leakcanary/AndroidLeakFixes.kt

diff --git a/plumber-android-core/src/main/java/leakcanary/AndroidLeakFixes.kt b/plumber-android-core/src/main/java/leakcanary/AndroidLeakFixes.kt
index 088e130e0..796c25d8c 100644
--- a/plumber-android-core/src/main/java/leakcanary/AndroidLeakFixes.kt
+++ b/plumber-android-core/src/main/java/leakcanary/AndroidLeakFixes.kt
@@ -79,32 +79,32 @@ enum class AndroidLeakFixes {
         return
       }
       backgroundHandler.post {
-        // Pool of TextLine instances.
-        val sCached: Any?
         try {
           val textLineClass = Class.forName("android.text.TextLine")
           val sCachedField = textLineClass.getDeclaredField("sCached")
           sCachedField.isAccessible = true
-          sCached = sCachedField.get(null)
+          // One time retrieval to make sure this will work.
+          val sCached = sCachedField.get(null)
           // Can't happen in current Android source, but hidden APIs can change.
           if (sCached == null || !sCached.javaClass.isArray) {
             SharkLog.d { "Could not fix the $name leak, sCached=$sCached" }
             return@post
           }
+          application.onActivityDestroyed {
+            // Pool of TextLine instances.
+            val sCached = sCachedField.get(null)
+            // TextLine locks on sCached. We take that lock and clear the whole array at once.
+            synchronized(sCached) {
+              val length = Array.getLength(sCached)
+              for (i in 0 until length) {
+                Array.set(sCached, i, null)
+              }
+            }
+          }
         } catch (ignored: Exception) {
           SharkLog.d(ignored) { "Could not fix the $name leak" }
           return@post
         }
-
-        application.onActivityDestroyed {
-          // TextLine locks on sCached. We take that lock and clear the whole array at once.
-          synchronized(sCached) {
-            val length = Array.getLength(sCached)
-            for (i in 0 until length) {
-              Array.set(sCached, i, null)
-            }
-          }
-        }
       }
     }
   },