square / leakcanary

A memory leak detection library for Android.
https://square.github.io/leakcanary
Apache License 2.0
29.3k stars 3.97k forks source link

Google play pre launched report warns non SDK api used violation #2199

Open niqo01 opened 2 years ago

niqo01 commented 2 years ago

Description

Google play pre launched report warns of non SDK api used violation by LeakCanary.

Steps to Reproduce

  1. Add the following dependency com.squareup.leakcanary:leakcanary-object-watcher-android:2.7
  2. Publish a release build to the Play store

Expected behavior: No NonSdkApiUsedViolation warnings in the reports from LeakCanary

Version Information

Additional Information

Google play pre launch reports: Report 1

StrictMode policy violation: android.os.strictmode.NonSdkApiUsedViolation: Landroid/app/ActivityThread;->mServices:Landroid/util/ArrayMap;
    at android.os.StrictMode.lambda$static$1(StrictMode.java:407)
    at android.os.-$$Lambda$StrictMode$lu9ekkHJ2HMz0jd3F8K8MnhenxQ.accept(Unknown Source:2)
    at java.lang.Class.getDeclaredField(Native Method)
    at leakcanary.ServiceWatcher$activityThreadServices$2.invoke(ServiceWatcher.kt:3)
    at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:5)
    at leakcanary.ServiceWatcher$install$3$2.handleMessage(ServiceWatcher.kt:4)
    at android.os.Handler.dispatchMessage(Handler.java:103)
    at android.os.Looper.loop(Looper.java:237)
    at android.app.ActivityThread.main(ActivityThread.java:8016)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:496)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1076)

Report 2

StrictMode policy violation: android.os.strictmode.NonSdkApiUsedViolation: Landroid/app/ActivityThread;->mServices:Landroid/util/ArrayMap;
    at android.os.StrictMode.lambda$static$1(StrictMode.java:416)
    at android.os.-$$Lambda$StrictMode$lu9ekkHJ2HMz0jd3F8K8MnhenxQ.accept(Unknown Source:2)
    at java.lang.Class.getDeclaredField(Native Method)
    at leakcanary.ServiceWatcher$activityThreadServices$2.invoke(ServiceWatcher.kt:3)
    at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:5)
    at leakcanary.ServiceWatcher$install$3$2.handleMessage(ServiceWatcher.kt:4)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:223)
    at android.app.ActivityThread.main(ActivityThread.java:7664)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

Report 3

StrictMode policy violation: android.os.strictmode.NonSdkApiUsedViolation: Landroid/app/ActivityThread;->mServices:Landroid/util/ArrayMap;
    at android.os.StrictMode.lambda$static$1(StrictMode.java:428)
    at android.os.-$$Lambda$StrictMode$lu9ekkHJ2HMz0jd3F8K8MnhenxQ.accept(Unknown Source:2)
    at java.lang.Class.getDeclaredField(Native Method)
    at leakcanary.ServiceWatcher$activityThreadServices$2.invoke(ServiceWatcher.kt:3)
    at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:5)
    at leakcanary.ServiceWatcher$install$3$2.handleMessage(ServiceWatcher.kt:4)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:193)
    at android.app.ActivityThread.main(ActivityThread.java:6718)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
pyricau commented 2 years ago

Until Google provides the APIs we need to do this, there's really no way around it. This strict mode thing is just sad. You can always disable the ServiceWatcher if you really care.

pyricau commented 1 year ago

We could potentially provide utilities to help ignore LeakCanary strict mode violations. Not sure about any auto install though.

Something inspired from this:

  private fun initializeStrictMode() {
    StrictMode.setVmPolicy(
      StrictMode.VmPolicy.Builder(StrictMode.getVmPolicy())
        .run {
          if (Build.VERSION.SDK_INT >= 28) {
            detectNonSdkApiUsage()
              .penaltyListener(
                {
                  it.run()
                },
                { violation ->
                  handleViolation(violation)
                }
              )
          } else {
            this
          }
        }
        .build()
    )
  }

  @RequiresApi(28)
  private fun handleViolation(violation: Violation) {
    when (violation) {
      is NonSdkApiUsedViolation -> handleNonSdkApiUsedViolation(violation)
    }
  }

  // non-sdk API calls in code that we have control of.
  private val knownViolations = setOf(
    // Called in com.squareup.shark.config.ImmCurRootViewFix on API 28 only, where it was on the
    // greylist. Later it was moved on the black list, but that's not important anymore.
    "Landroid/view/inputmethod/InputMethodManager;->mCurRootView:Landroid/view/View;",
    "Ljava/lang/Throwable;->detailMessage:Ljava/lang/String;",
  )

  private fun handleNonSdkApiUsedViolation(violation: NonSdkApiUsedViolation) {
    // Ignore the known violations.
    if (violation.message in knownViolations) return

    // Exceptions look something like that:
    //
    // Caused by: android.os.strictmode.NonSdkApiUsedViolation: Landroid/view/View;->mKeyedTags:Landroid/util/SparseArray;
    //         at android.os.StrictMode.lambda$static$1(StrictMode.java:416)
    //         at android.os.-$$Lambda$StrictMode$lu9ekkHJ2HMz0jd3F8K8MnhenxQ.accept(Unknown Source:2)
    //         at java.lang.Class.getDeclaredField(Native Method)
    //         at com.facebook.flipper.plugins.inspector.descriptors.ViewDescriptor.<clinit>(ViewDescriptor.java:63)
    //
    // This code gives us the important line after the reflection call.
    val offendingCaller = violation.stackTrace.first {
      !it.className.startsWith("android.os") && !it.className.startsWith("java.lang.Class")
    }

    // These callers use elements from the greylist. Unfortunately, the violation object doesn't
    // give us any hint about what kind of violation it is. We can safely ignore greylist filters and
    // filter them here.
    //
    // Note that these callers are only third party dependencies. Our own violations are listed in
    // the 'knownViolations' collection.
    //
    // When we upgrade the target SDK, we should check them all again.
    when {
      // This is fiiiine
      offendingCaller.className.startsWith("papa.") -> return
      offendingCaller.className.startsWith("curtains.") -> return
      offendingCaller.className.startsWith("leakcanary.") -> return
      offendingCaller.className.startsWith("radiography.") -> return
      // Google probably knows what they're doing.
      offendingCaller.className.startsWith("androidx.") -> return

      // etc etc
     }

    throw violation
  }
bubenheimer commented 2 months ago

I am running into a new NonSdkApiUsedViolation from LeakCanary 2.14 related to android.view.WindowManagerGlobal. Not sure what introduced this, Android 15 Beta 3 or new LeakCanary version may be to blame.

android.os.strictmode.NonSdkApiUsedViolation: Landroid/view/WindowManagerGlobal;->getInstance()Landroid/view/WindowManagerGlobal;
    at android.os.StrictMode.lambda$static$1(StrictMode.java:432)
    at android.os.StrictMode$$ExternalSyntheticLambda2.accept(D8$$SyntheticClass:0)
    at java.lang.Class.getDeclaredMethodInternal(Native Method)
    at java.lang.Class.getPublicMethodRecursive(Class.java:2957)
    at java.lang.Class.getMethod(Class.java:2944)
    at java.lang.Class.getMethod(Class.java:2450)
    at curtains.internal.WindowManagerSpy$windowManagerInstance$2.invoke(WindowManagerSpy.kt:37)
    at kotlin.UnsafeLazyImpl.getValue(Lazy.kt:81)
    at curtains.internal.WindowManagerSpy.getWindowManagerInstance(Unknown Source:2)
    at curtains.internal.WindowManagerSpy.swapWindowManagerGlobalMViews(WindowManagerSpy.kt:54)
    at curtains.internal.RootViewsSpy$Companion.install(RootViewsSpy.kt:39)
    at curtains.Curtains$rootViewsSpy$2.invoke(Curtains.kt:33)
    at curtains.Curtains$rootViewsSpy$2.invoke(Curtains.kt:30)
    at kotlin.UnsafeLazyImpl.getValue(Lazy.kt:81)
    at curtains.Curtains.getRootViewsSpy(Unknown Source:2)
    at curtains.Curtains.getOnRootViewsChangedListeners(Curtains.kt:68)
    at leakcanary.RootViewWatcher.install(RootViewWatcher.kt:84)
    at leakcanary.AppWatcher.manualInstall(AppWatcher.kt:113)
    at com.bubenheimer.rucksack.leakcanary.LeakCanaryUtilKt.initLeakCanary(LeakCanaryUtil.kt:66)
    at com.bubenheimer.rucksack.d.D.injection(Application.kt:164)
    at com.bubenheimer.rucksack.d.D.onCreate(Application.kt:100)
    at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1386)
    at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7498)
    at android.app.ActivityThread.-$$Nest$mhandleBindApplication(Unknown Source:0)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2415)
    at android.os.Handler.dispatchMessage(Handler.java:107)
    at android.os.Looper.loopOnce(Looper.java:232)
    at android.os.Looper.loop(Looper.java:317)
    at android.app.ActivityThread.main(ActivityThread.java:8699)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:886)
bubenheimer commented 2 months ago

I also see other WindowManagerGlobal exceptions triggered by LeakCanary. Perhaps the whole class was made unavailable.

@pyricau is there a standing request with Google to provide the APIs LeakCanary needs?

bubenheimer commented 2 months ago

I see quite a lot more triggers for NonSdkApiUsedViolation now. I've had to filter all violations with "curtains.internal" and "leakcanary.ServiceWatcher" in the stacktraces to put an end to it.

pyricau commented 2 months ago

Yep, the curtains ones are from: https://github.com/square/curtains

We can't fix that until Google provides proper APIs to listen to when new windows are added and removed.

Same with services, with need the equivalent of ActivityLifecycleCallbacks but for services.

Someone ask the AOSP team to fix this. In the meantime, Google Play reports can go to hell.

bubenheimer commented 2 months ago

Thanks. Just FYI: I use StrictMode things in regular development, not just with Google Play reports.

pyricau commented 2 months ago

Relevant issues:

https://issuetracker.google.com/issues/290313765 https://issuetracker.google.com/issues/348333887