最近写需求,动态匹配字符串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改为英文,就没那么多事了。
网友评论