context.getResources().getIdentifier()

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章