Skip to content

Instantly share code, notes, and snippets.

@tokudu
Last active April 5, 2021 21:04
Show Gist options
  • Save tokudu/a68b26e899bf93940ed8f8b2308df89c to your computer and use it in GitHub Desktop.
Save tokudu/a68b26e899bf93940ed8f8b2308df89c to your computer and use it in GitHub Desktop.
StrictMode util that allows you to whitelist stack traces
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