leakcanary icon indicating copy to clipboard operation
leakcanary copied to clipboard

Google play pre launched report warns non SDK api used violation

Open niqo01 opened this issue 4 years ago • 8 comments

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

  • LeakCanary version: 2.7

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)

niqo01 avatar Nov 06 '21 02:11 niqo01

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 avatar Dec 15 '21 23:12 pyricau

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
  }

pyricau avatar May 18 '23 04:05 pyricau