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}")
}