解决自定义 lint 规则中的资源值问题

12

我有一个庞大的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(...) 返回的是属性的字符串值而不是实际解析后的值。

例如,当我有一个像这样的 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")?


无法理解您的问题,如果您得到了值0.7,那么它怎么会指向standard_anim_factor?或者如果它指向它,那么问题是什么? - Murtaza Khursheed Hussain
谢谢指出。我编辑了问题,使其更易读。 - Mike Laren
3个回答

2
一般解析值的问题在于它们取决于您所处的Android运行时上下文。可能会有几个具有不同具体值的`values`文件夹,用于您的键`@dimen/standard_anim_factory`,所以请注意这一点。
尽管如此,据我所知存在两个选项:
1.进行两阶段检测: - 阶段1:扫描资源 - 扫描属性并将其放入列表中(而不是立即评估) - 扫描维度值并将其也放入列表中 - 阶段2:重写`Detector.afterProjectCheck`,通过迭代在第一阶段中填充的两个列表来解析您的属性
2.通常情况下,`LintUtils`类 [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){
    ...
}

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

2阶段方法肯定可以在运行时不需要太多工作的情况下解决问题。遗憾的是,Android Lint没有更好的界面(或者只是更好的模板)来完成这个任务。 - Mike Laren

0

问题在于getResources()android.content.Context的一个方法,但Lint使用的是没有获取/解析资源方法的com.android.tools.lint.detector.api.Context - Mike Laren

0
假设在解析数据后得到了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);

}

谢谢Murtaza,但这只是打印了Found attribute: my:animation_factor with value: @dimen/standard_anim_factory,这就是我已经有的...我想要它打印Found attribute: my:animation_factor with value: 1.85 - Mike Laren
如果你找到了这个节点,那就重复一下那个元素。希望你明白我想说的意思。 - Murtaza Khursheed Hussain

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接