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改爲非中文,就沒那麼多事了。

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