最近寫需求,動態匹配字符串key,用到了context.getResources().getIdentifier()方法。
經測試同學反饋,低版本機型上通過中文key獲取資源id失敗,返回0。
val resourceID = resources.getIdentifier("中國大陸", "string", packageName)
val string = resources.getString(resourceID)
此處resourceID在6.0機型上返回0。搶救一下,試試英文key。
val resourceID = resources.getIdentifier("china", "string", packageName)
正常獲取到資源id,中文字符猜測是編碼問題。下面翻翻源碼。
Resources.getIdentifier()
public int getIdentifier(String name, String defType, String defPackage) {
return mResourcesImpl.getIdentifier(name, defType, defPackage);
}
ResourcesImpl.getIdentifier()
int getIdentifier(String name, String defType, String defPackage) {
if (name == null) {
throw new NullPointerException("name is null");
}
try {
return Integer.parseInt(name);
} catch (Exception e) {
// Ignore
}
return mAssets.getResourceIdentifier(name, defType, defPackage);
}
AssetManager.getResourceIdentifier()
@AnyRes int getResourceIdentifier(@NonNull String name, @Nullable String defType,
@Nullable String defPackage) {
synchronized (this) {
ensureValidLocked();
// name is checked in JNI.
return nativeGetResourceIdentifier(mObject, name, defType, defPackage);
}
}
private static native @AnyRes int nativeGetResourceIdentifier(long ptr, @NonNull String name,
@Nullable String defType, @Nullable String defPackage);
調用到native方法,先看低版本Android4.4.4源碼android_util_AssetManager.cpp
static jint android_content_AssetManager_getResourceIdentifier(JNIEnv* env, jobject clazz,
jstring name,
jstring defType,
jstring defPackage)
{
ScopedStringChars name16(env, name);
if (name16.get() == NULL) {
return 0;
}
...
}
ScopedStringChars
class ScopedStringChars {
public:
ScopedStringChars(JNIEnv* env, jstring s) : env_(env), string_(s), size_(0) {
if (s == NULL) {
chars_ = NULL;
jniThrowNullPointerException(env, NULL);
} else {
chars_ = env->GetStringChars(string_, NULL);
if (chars_ != NULL) {
size_ = env->GetStringLength(string_);
}
}
}
...
};
接下來看9.0.0源碼getResourceIdentifier()android_util_AssetManager.cpp
static jint NativeGetResourceIdentifier(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring name,
jstring def_type, jstring def_package) {
ScopedUtfChars name_utf8(env, name);
if (name_utf8.c_str() == nullptr) {
// This will throw NPE.
return 0;
}
...
}
ScopedUtfChars
class ScopedUtfChars {
public:
ScopedUtfChars(JNIEnv* env, jstring s) : env_(env), string_(s) {
if (s == NULL) {
utf_chars_ = NULL;
jniThrowNullPointerException(env, NULL);
} else {
utf_chars_ = env->GetStringUTFChars(s, NULL);
}
}
...
};
ScopedStringChars和ScopedUtfChars內部區別就在於GetStringChars()、GetStringUTFChars()方法。
google一下,得到以下結論:
Java默認使用Unicode編碼,而C/C++默認使用UTF編碼,所以在本地代碼中操作字符串的時候,必須使用合適的JNI函數把jstring轉換成C風格的字符串。JNI支持字符串在Unicode和UTF-8兩種編碼之間轉換,GetStringUTFChars可以把一個jstring指針(指向JVM內部的Unicode字符序列)轉換成一個UTF-8格式的C字符串。
string.xml文件編碼格式爲utf-8,而低版本native方法nativeGetResourceIdentifier()調用GetStringChars()處理,此時遇到中文key自然就不兼容啦。
最終解決方案爲反射
val resourceID = R.string.class.getField(key).getInt(null)
當然,如果能把key改爲非中文,就沒那麼多事了。