Разрешение значений ресурсов в пользовательском правиле lint

У меня есть большая база данных 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".

Есть ли способ разрешить "@dimen/standard_anim_factor" фактическое значение ресурса (т.е. "1.85" ) при обработке элемента MyCustomComponent?

Ответ 1

Общая проблема с разрешением значений заключается в том, что они зависят от контекста среды выполнения Android, в котором вы находитесь. Может быть несколько папок values с разными конкретными значениями для вашего ключа @dimen/standard_anim_factory, поэтому просто вам известно из.

Тем не менее, AFAIK существует два варианта:

  • Выполните двухфазное обнаружение:

    • Этап 1. Сканирование ваших ресурсов
    • Сканируйте свой атрибут и поместите его в список (вместо того, чтобы немедленно его оценить)
    • Сканируйте свои значения измерения и поместите их в список.
    • Фаза 2:
    • переопределить Detector.afterProjectCheck и разрешить ваши атрибуты, итерации по двум спискам, заполненным в фазе 1
  • обычно класс 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());
    }

Примечание: Я еще не тестировал вышеуказанный код. Поэтому, пожалуйста, см. Теоретические советы: -)

Еще один небольшой совет для вашего пользовательского кода правила 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){
    ...
}

Но это просто вопрос предпочтения: -)

Ответ 3

Предположив xml node после анализа ваших данных, попробуйте выполнить

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);

}