前一段时间,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 文件内,不要硬编码,对于管理也是很方便,何乐而不为呢