在 Android 17 上修改 static final 字段的方法

13 阅读1分钟

Android 17 禁止了在运行时修改 static final 字段: developer.android.com/about/versi…

但是我发现通过 unsafe 可以绕过这种检查,达成修改 static final 字段的目的:

fun reflectModifyStaticFinal(fieldString: String, obj: Class<*>, newValue: Any?) {
    try {
        try {
            // 必须这样先 set 一下,我知道他会失败,但是失败这一次后面才能成功...
            obj.getDeclaredField(fieldString).set(null, newValue)
            Log.d(TAG, "field.set succeeded")
        } catch (t: Throwable) {
            Log.w(TAG, "field.set failed, try Unsafe", t)
            setFieldUsingUnsafePublic(fieldString, obj, newValue)
            Log.d(TAG, "setFieldUsingUnsafe succeeded")
        }
    } catch (t: Throwable) {
        Log.e(TAG, "reflectModifyStaticFinal failed", t)
    }
}
@SuppressLint("DiscouragedPrivateApi")
private fun setFieldUsingUnsafePublic(fieldString: String, obj: Class<*>, newValue: Any?) {
    val field = obj.getDeclaredField(fieldString)
    try {
        field.isAccessible = true
        val fieldModifiersMask = field.modifiers
        val isFinalModifierPresent = (fieldModifiersMask and Modifier.FINAL) == Modifier.FINAL

        if (isFinalModifierPresent) {
            try {
                val unsafeClass = Class.forName("sun.misc.Unsafe")
                val field1 = unsafeClass.getDeclaredField("theUnsafe")
                field1.isAccessible = true
                val unsafe = field1.get(null)
                val offsetMethod = Field::class.java.getDeclaredMethod("getOffset")
                offsetMethod.isAccessible = true
                val offset = offsetMethod.invoke(field)
                val putObjectMethod = unsafeClass.getMethod(
                    "putObject",
                    Any::class.java,
                    java.lang.Long.TYPE,
                    Any::class.java
                )

                Log.d(TAG, "offset=$offset obj=$obj newValue=$newValue")

                putObjectMethod.invoke(unsafe, obj, offset, newValue)
            } catch (t: Throwable) {
                Log.e(TAG, "inner failed, t=$t", t)
                Log.e(TAG, "inner failed cause=${t.cause}", t.cause)
                throw RuntimeException(t)
            }
        } else {
            field.set(obj, newValue)
        }
    } catch (ex: Throwable) {
        Log.e(TAG, "setFieldUsingUnsafe outer failed: $ex", ex)
        Log.e(TAG, "setFieldUsingUnsafe outer cause=${ex.cause}", ex.cause)
        throw RuntimeException(ex)
    }
}

后续访问也需要通过反射才能感知到修改,因为 static final 字段会被内联

测试代码:


override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    Log.d(TAG, "before: ${Hello.hello}")
    reflectModifyStaticFinal("hello", Hello::class.java, "modified!!!")
    try {
        val clazz = Hello::class.java
        val field = clazz.getDeclaredField("hello")
        field.isAccessible = true
        val value = field.get(null)
        Log.d(TAG, "after(reflection): $value")
    } catch (t: Throwable) {
        Log.e(TAG, "read via reflection failed", t)
    }
    Log.d(TAG, "after(no-reflection): ${Hello.hello}")
}