前一段时间,leader 找我说,我们的程序启动后,打印了很多警告
W/ResourceType: No package identifier when getting name for resource number 0x00000000
让我找一下原因,并解决一下,现在原因已经找到,并且问题也完美解决,在此记录一下
直接 Google 了一下关键字,发现只有一个是关键字完全匹配的,其他的虽然能匹配大部分但是和我不是同一个问题,贴出链接
去除 No package identifier when getting name ...
点进去看了一下,主要的修改就是将 View 的 background 的具体颜色值修改为 @color/xxx 的形式
为了验证这个修改是否真的有用,于是就去看了一下 View 是如何设置背景色的
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
final TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
...
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
switch (attr) {
case com.android.internal.R.styleable.View_background:
// 获取 drawable
background = a.getDrawable(attr);
break;
...
}
}
...
}
View 的构造函数里 调用了 TypedArray.java 的 getDrawable() 函数
public Drawable getDrawable(int index) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
final TypedValue value = mValue;
// 向 TypedValue 里面填充信息
if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
if (value.type == TypedValue.TYPE_ATTRIBUTE) {
throw new UnsupportedOperationException(
"Failed to resolve attribute at index " + index + ": " + value);
}
// 调用 Resources.java 的 loadDrawable() 函数
return mResources.loadDrawable(value, value.resourceId, mTheme);
}
return null;
}
private boolean getValueAt(int index, TypedValue outValue) {
final int[] data = mData;
final int type = data[index+AssetManager.STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return false;
}
outValue.type = type;
outValue.data = data[index+AssetManager.STYLE_DATA];
outValue.assetCookie = data[index+AssetManager.STYLE_ASSET_COOKIE];
// 为 resourceId 赋值
outValue.resourceId = data[index+AssetManager.STYLE_RESOURCE_ID];
outValue.changingConfigurations = data[index+AssetManager.STYLE_CHANGING_CONFIGURATIONS];
outValue.density = data[index+AssetManager.STYLE_DENSITY];
outValue.string = (type == TypedValue.TYPE_STRING) ? loadStringValueAt(index) : null;
return true;
}
在 getValueAt() 函数中,有一步是对 resourceId 赋值,但这里的值是 0,因为在布局文件中,背景使用的是具体颜色值,所以是没有 id 的
接下来继续看 Resources.java 的 loadDrawable() 函数,这个函数乐视的 Eui 系统组开发人员修改过,增加了一段逻辑
Drawable loadDrawable(TypedValue value, int id, Theme theme) throws NotFoundException {
...
String resourceName = "";
String drawableName = "";
try {
resourceName = getResourceName(id);
drawableName = ResourceHelper.getResName(resourceName);
} catch(Exception e) {
}
...
}
getResourceName() 函数很可疑,继续向下跟踪
public String getResourceName(@AnyRes int resid) throws NotFoundException {
String str = mAssets.getResourceName(resid);
if (str != null) return str;
throw new NotFoundException("Unable to find resource ID #0x"
+ Integer.toHexString(resid));
}
这里调用了 AssetManager.java 的 getResourceName() 函数,它是个native 函数
native final String getResourceName(int resid);
对应的 C++ 实现是在 android_util_AssetManager.cpp 中
static jstring android_content_AssetManager_getResourceName(JNIEnv* env, jobject clazz, jint resid)
{
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
return NULL;
}
ResTable::resource_name name;
if (!am->getResources().getResourceName(resid, true, &name)) {
return NULL;
}
...
}
getResources() 定义在 AssetManager.h 中
const ResTable& getResources(bool required = true) const;
实现在 AssetManager.cpp 中
const ResTable& AssetManager::getResources(bool required) const
{
const ResTable* rt = getResTable(required);
return *rt;
}
const ResTable* AssetManager::getResTable(bool required) const
{
ResTable* rt = mResources;
if (rt) {
return rt;
}
...
mResources = new ResTable();
...
return mResources;
}
ResTable 定义在 ResourceTypes.h 里,我们来看 ResourceTypes.cpp
#define Res_GETPACKAGE(id) ((id>>24)-1)
class ResTable
{
public:
ResTable();
ResTable(const void* data, size_t size, const int32_t cookie,
bool copyData=false);
~ResTable();
...
bool getResourceName(uint32_t resID, bool allowUtf8, resource_name* outName) const;
...
};
// Mapping from resource package IDs to indices into the internal
// package array.
uint8_t mPackageMap[256];
uint8_t mNextPackageId;
这里也列出了其他后面要用到的一些声明,
// 构造函数
ResTable::ResTable()
: mError(NO_INIT), mNextPackageId(2) // 初始化 mNextPackageId 为 2
{
...
// mPackageMap 数组所有元素设置为 0
memset(mPackageMap, 0, sizeof(mPackageMap));
...
}
ResTable::ResTable(const void* data, size_t size, const int32_t cookie, bool copyData)
: mError(NO_INIT), mNextPackageId(2) // 初始化 mNextPackageId 为 2
{
...
// mPackageMap 数组所有元素设置为 0
memset(mPackageMap, 0, sizeof(mPackageMap));
...
}
bool ResTable::getResourceName(uint32_t resID, bool allowUtf8, resource_name* outName) const
{
if (mError != NO_ERROR) {
return false;
}
const ssize_t p = getResourcePackageIndex(resID);
if (p < 0) {
if (Res_GETPACKAGE(resID)+1 == 0) {
ALOGW("No package identifier when getting name for resource number 0x%08x", resID);
} else {
ALOGW("No known package when getting name for resource number 0x%08x", resID);
}
return false;
}
...
}
inline ssize_t ResTable::getResourcePackageIndex(uint32_t resID) const
{
return ((ssize_t)mPackageMap[Res_GETPACKAGE(resID)+1])-1;
}
Res_GETPACKAGE() 是将 id 右移 24 位后减 1,这里 id 是 0 所以返回值是 -1,Res_GETPACKAGE(resID)+1 的结果是 0
mPackageMap[0] = 0,因为在 ResTable 的构造函数中 mNextPackageId 被初始化为 2 ,所以 0 和 1 位置没有被修改
所以 getResourcePackageIndex() 返回 -1 ,所以,会进入 p < 0 的分支,Res_GETPACKAGE() 返回 -1 ,所以执行了
ALOGW("No package identifier when getting name for resource number 0x%08x", resID);
0x%08x 是表示:以 0x 开头,将 resID 格式化为 8 位的 16 进制数字,不足 8 位用 0 补齐,x 表示 16 进制
进而打印出了 No package identifier when getting name for resource number 0x00000000 警告
至此,这个问题已经分析完毕,在 ResourceTypes.cpp 内搜索 No package identifier when getting 发现,不止 getResourceName() 会打印这个警告,还有 getResource() , getBagLocked() 以及 getResourceFlags() 函数都包含有和 getResourceName() 相同的逻辑,这三个函数具体什么时候会调用就不再深入分析了
总结,在开发 Android 程序的时候,颜色资源最好放到 values/colors.xml 文件内,不要硬编码,对于管理也是很方便,何乐而不为呢