在自定义 lint 规则中解析资源值

Resolving resource values in custom lint rule

我有一个很大的 Android 代码库,我正在编写一个自定义 lint 规则来检查某些属性的值是否在给定范围内。

例如,我有这个组件:

<MyCustomComponent
    my:animation_factor="0.7"
    ...>
</MyCustomComponent>

并且我想编写一个 lint 规则,提醒开发人员应谨慎使用 my:animation_factor >= 1 的值。

我按照 http://tools.android.com/tips/lint-custom-rules 中的说明进行操作,并使用以下代码设法检索了 my:animation_factor 的值:

import com.android.tools.lint.detector.api.*;

public class XmlInterpolatorFactorTooHighDetector {

    ....
    @Override
    public Collection<String> getApplicableElements() {
        return ImmutableList.of("MyCustomComponent");
    }

    @Override
    public void visitElement(XmlContext context, Element element) {
        String factor = element.getAttribute("my:animation_factor");
        ...
        if (value.startsWith("@dimen/")) {
            // How do I resolve @dimen/xyz to 1.85?
        } else {
            String value = Float.parseFloat(factor);
        }
    }
}

my:animation_factor 等属性具有文字值(例如 0.7)时,此代码可以正常工作。

但是,当属性值是资源时(例如 @dimen/standard_anim_factor),则 element.getAttribute(...) returns 属性的字符串值而不是实际解析值。

例如,当我有一个看起来像这样的 MyCustomComponent 时:

<MyCustomComponent
    my:animation_factor="@dimen/standard_anim_factory"
    ...>
</MyCustomComponent>

@dimen/standard_anim_factor在别处定义:

<dimen name="standard_anim_factor">1.85</dimen>

然后字符串 factor 变为“@dimen/standard_anim_factor”而不是“1.85”。

有没有办法在处理 MyCustomComponent 元素时将“@dimen/standard_anim_factor”解析为资源的实际值(即“1.85”)?

我相信您正在寻找 getResources().getDimension()

来源:http://developer.android.com/reference/android/content/res/Resources.html#getDimension%28int%29

假设 xml 节点在解析你的数据后,尝试以下

Element element = null; //It is your root node.
NamedNodeMap attrib = (NamedNodeMap) element;

int numAttrs = attrib.getLength ();

for (int i = 0; i < numAttrs; i++) {
    Attr attr = (Attr) attrib.item (i);

    String attrName = attr.getNodeName ();
    String attrValue = attr.getNodeValue ();

    System.out.println ("Found attribute: " + attrName + " with value: " + attrValue);

}

值解析的一般问题是,它们取决于您所在的 Android 运行时上下文。可能有几个 values 文件夹具有不同的密钥具体值 @dimen/standard_anim_factory,所以你知道。

然而,AFAIK 存在两个选项:

  • 执行两相检测:

    • 第 1 阶段:扫描您的资源
    • 扫描您的属性并将其放入列表中(而不是立即对其进行评估)
    • 扫描您的维度值并将它们也放入列表中
    • 第 2 阶段:
    • 覆盖 Detector.afterProjectCheck 并通过遍历阶段 1
    • 中填充的两个列表来解析您的属性
  • 通常 LintUtils class [1] 是处理这些问题的最佳地点,但不幸的是没有解决维度值的方法。但是,有一个名为 getStyleAttributes 的方法演示了如何解析资源值。因此,您可以编写自己方便的方法来解析维度值:


    private int resolveDimensionValue(String name, Context context){
       LintClient client = context.getDriver().getClient();
       LintProject project = context.getDriver().getProject();
       AbstractResourceRepository resources = client.getProjectResources(project, true);

       return Integer.valueOf(resources.getResourceItem(ResourceType.DIMEN, name).get(0).getResourceValue(false).getValue());
    }

注意:上面的代码我还没有测试过。所以请将其视为理论建议:-)

  1. https://android.googlesource.com/platform/tools/base/+/master/lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/LintUtils.java

关于您的自定义 Lint 规则代码的一点小建议,因为您只对属性感兴趣:

而不是在 visitElement 中做这样的事情:

String factor = element.getAttribute("my:animation_factor");

...你可能想做这样的事情:

@Override
public Collection<String> getApplicableAttributes() {
    return ImmutableList.of("my:animation_factor");
}

@Override    
void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute){
    ...
}

但这只是个人喜好问题:-)