Last active
April 5, 2021 21:04
-
-
Save tokudu/a68b26e899bf93940ed8f8b2308df89c to your computer and use it in GitHub Desktop.
StrictMode util that allows you to whitelist stack traces
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.myapp | |
import android.os.Build | |
import android.os.StrictMode | |
import com.snap.core.BuildConfig | |
import com.snapchat.android.framework.logging.Timber | |
import java.lang.reflect.Field | |
import java.lang.reflect.Modifier | |
/** | |
* Helper for enabling strict mode | |
*/ | |
class StrictModeManager { | |
companion object { | |
private const val TAG = "StrictModeManager" | |
/** | |
* Array of whitelisted stacktraces. If the violation stack trace contains any of these lines, the violations | |
* are ignored. | |
*/ | |
private val STACKTRACE_WHITELIST = listOf( | |
// This violation is related to Dex Loading optimization on Snapdragon devices. | |
"android.util.BoostFramework.<init>" | |
) | |
/** | |
* Enables strict mode if necessary based on the build config. | |
*/ | |
@JvmStatic | |
fun init() { | |
if (BuildConfig.DEBUG) { | |
enableStrictMode() | |
} | |
} | |
private fun enableStrictMode() { | |
val builder = StrictMode.ThreadPolicy.Builder() | |
.detectDiskReads() | |
.detectDiskWrites() | |
.detectNetwork() | |
.penaltyLog() | |
.penaltyDropBox() | |
// allow penaltyDeath on versions above N | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { | |
builder.penaltyDeath() | |
} | |
StrictMode.setThreadPolicy(builder.build()) | |
StrictMode.setVmPolicy(StrictMode.VmPolicy.Builder() | |
.detectLeakedSqlLiteObjects() | |
.detectLeakedClosableObjects() | |
.detectLeakedRegistrationObjects() | |
.penaltyLog() | |
.penaltyDropBox() | |
.penaltyDeath() | |
.build()) | |
// Let the dirty hacks begin. | |
// Source: https://atscaleconference.com/videos/eliminating-long-tail-jank-with-strictmode/ | |
// On API levels above N, we can use reflection to read the violationsBeingTimed field of strict | |
// to get notifications about reported violations | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { | |
val field = StrictMode::class.java.getDeclaredField("violationsBeingTimed") | |
field.setAccessible(true) // Suppress Java language access checking | |
// Remove "final" modifier | |
val modifiersField = Field::class.java.getDeclaredField("accessFlags") | |
modifiersField.setAccessible(true) | |
modifiersField.setInt(field, field.modifiers and Modifier.FINAL.inv()) | |
// Override the field with a custom ArrayList, which is able to skip whitelisted violations | |
field.set(null, object: ThreadLocal<ArrayList<out Object>>() { | |
override fun get(): ArrayList<out Object> { | |
return StrictModeHackArrayList() | |
} | |
}) | |
} | |
} | |
} | |
/** | |
* Special array list that skip additions for matching ViolationInfo instances as per | |
* hack described in https://atscaleconference.com/videos/eliminating-long-tail-jank-with-strictmode/ | |
*/ | |
class StrictModeHackArrayList: ArrayList<Object>() { | |
override fun add(element: Object): Boolean { | |
val crashInfoField = element.`class`.getDeclaredField("crashInfo") | |
crashInfoField.get(element)?.let { | |
val stackTraceField = it.javaClass.getDeclaredField("stackTrace") | |
stackTraceField.get(it)?.let { | |
for (whitelistedStacktraceCall in STACKTRACE_WHITELIST) { | |
if (it.toString().contains(whitelistedStacktraceCall)) { | |
Timber.d(TAG, "Skipping whitelisted StrictMode violation: $whitelistedStacktraceCall") | |
return false | |
} | |
} | |
} | |
} | |
// call super to continue with standard violation reporting | |
return super.add(element) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment