2016-07-20 5 views
5

У меня есть проект с несколькими модулями в Android Studio. Модуль может иметь зависимость от другого модуля, например:Многомодульная обработка аннотаций в Android Studio

Модуля PhoneApp -> Модуля FeatureOne -> Модуль услуги

Я включил мою обработку аннотаций в корневом модуле, но происходит андроид-склонные обработки аннотаций только на самом верхнем уровне (PhoneApp), чтобы теоретически иметь доступ ко всем модулям во время компиляции. Однако то, что я вижу в сгенерированном java-файле, это только классы, аннотированные в PhoneApp, и ни один из других модулей.

PhoneApp/build/generated/source/apt/debug/.../GeneratedClass.java 

В других модулях, я найти сгенерированный файл в каталоге промежуточных, который содержит только аннотированные файлы из этого модуля.

FeatureOne/build/intermediates/classes/debug/.../GeneratedClass.class 
FeatureOne/build/intermediates/classes/debug/.../GeneratedClass.java 

Моя цель состоит в том, чтобы иметь один сгенерированный файл в PhoneApp, что позволяет мне получить доступ к аннотированным файлам из всех модулей. Не совсем понятно, почему процесс генерации кода работает для каждого и не может заполнить все аннотации в PhoneApp. Любая помощь оценивается.

код довольно простой и прямой вперед до тех пор, checkIsValid() опущена, как она работает правильно:

Аннотация Процессор:

@Override 
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 
    try { 

     for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(GuiceModule.class)) { 
      if (checkIsValid(annotatedElement)) { 
       AnnotatedClass annotatedClass = new AnnotatedClass((TypeElement) annotatedElement); 
       if (!annotatedClasses.containsKey(annotatedClass.getSimpleTypeName())) { 
        annotatedClasses.put(annotatedClass.getSimpleTypeName(), annotatedClass); 
       } 
      } 
     } 

     if (roundEnv.processingOver()) { 
      generateCode(); 
     } 

    } catch (ProcessingException e) { 
     error(e.getElement(), e.getMessage()); 
    } catch (IOException e) { 
     error(null, e.getMessage()); 
    } 

    return true; 
} 

private void generateCode() throws IOException { 
    PackageElement packageElement = elementUtils.getPackageElement(getClass().getPackage().getName()); 
    String packageName = packageElement.isUnnamed() ? null : packageElement.getQualifiedName().toString(); 

    ClassName moduleClass = ClassName.get("com.google.inject", "Module"); 
    ClassName contextClass = ClassName.get("android.content", "Context"); 
    TypeName arrayOfModules = ArrayTypeName.of(moduleClass); 

    MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("juice") 
      .addParameter(contextClass, "context") 
      .addModifiers(Modifier.PUBLIC, Modifier.STATIC) 
      .returns(arrayOfModules); 

    methodBuilder.addStatement("$T<$T> collection = new $T<>()", List.class, moduleClass, ArrayList.class); 

    for (String key : annotatedClasses.keySet()) { 

     AnnotatedClass annotatedClass = annotatedClasses.get(key); 
     ClassName className = ClassName.get(annotatedClass.getElement().getEnclosingElement().toString(), 
       annotatedClass.getElement().getSimpleName().toString()); 

     if (annotatedClass.isContextRequired()) { 
      methodBuilder.addStatement("collection.add(new $T(context))", className); 
     } else { 
      methodBuilder.addStatement("collection.add(new $T())", className); 
     } 

    } 

    methodBuilder.addStatement("return collection.toArray(new $T[collection.size()])", moduleClass); 

    TypeSpec classTypeSpec = TypeSpec.classBuilder("FreshlySqueezed") 
      .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 
      .addMethod(methodBuilder.build()) 
      .build(); 

    JavaFile.builder(packageName, classTypeSpec) 
      .build() 
      .writeTo(filer); 
} 

Это просто для демонстрации обработки аннотаций, который работает с Guice , если кому-то интересно.

Итак, как я могу получить все аннотированные классы, которые будут включены в сгенерированный файл PhoneApp .java из всех модулей?

+0

Это хороший вопрос.Я думаю, что многие люди оценят, если вы поделитесь своим решением, когда найдете его, я еще этого не сделал. В разделе [dbFlow project issues] (https://github.com/Raizlabs/DBFlow/issues/266) обсуждается ограничение процессора, что можно считать доказательством того, что невозможно реализовать требуемые кросс- процессор аннотации модуля. – konata

+0

Поскольку процессор аннотации работает для каждого модуля отдельно, вы можете попробовать инкрементный подход, но это немного зависит от вашего случая. Он может работать, если вам не нужно изменять весь класс после обработки следующего модуля, но добавлять только новые строки в существующий класс: 1. При обработке первого модуля сгенерируйте свой класс в указанном месте (где-то в модуле PhonApp ** сгенерированное ** дерево); 2. При обработке следующего модуля проверьте, существует ли сгенерированный класс и добавьте в него новый код. – konata

+0

Поскольку это была просто демонстрация в презентации, я больше не искал решения. (Думал, что возник вопрос о том, почему сгенерированный файл не включал все данные модулей) Я действительно рассматривал использование задач Gradle для копирования сгенерированных файлов из каждого модуля, но предпочел бы решение, которое не нужно было бы полагаться на инструменты сборки , – fakataha

ответ

0

Вместо использования Filer для сохранения сгенерированного файла вместо этого используйте обычную java-файл. Вам потребуется сериализовать объекты во временные файлы при обработке, потому что даже статические переменные не будут сохраняться между модулями. Настройте градуировку, чтобы удалить временные файлы перед компиляцией.

0

Это никогда не слишком поздно, чтобы ответить на вопрос о SO, так что ...

я столкнулся с очень похожим осложнением во время одной из задач на работе.

И я смог его решить.

Короткая версия

Все, что вам нужно знать о сгенерированных классов из moduleB в ModuleA является пакет и имя класса. Это может быть сохранено в некотором классе сгенерированного класса MyClassesRegistrar, помещенного в известный пакет. Используйте суффиксы, чтобы избежать конфликтов имен, получить регистраторов по пакетам. Создайте их и используйте данные из них.

Лонд версия

Прежде всего - вы не сможете включать в свой во время компиляции только зависимость только в верхнем модуле (позволяет называть его модуль «приложение», как ваш типичный андроида структура делает проект) , Обработка аннотаций не работает именно так и, насколько я могу судить, ничего не поделаешь.

Теперь к деталям.Моя задача была такова: У меня есть аннотированные классы, написанные человеком. Я назову их «событиями». Во время компиляции мне нужно создать вспомогательные классы для этих событий, чтобы включить их структуру и контент (как статически доступные (значения аннотации, константы и т. Д.), Так и время выполнения (я передаю объекты событий этим помощникам при использовании последних). имя класса зависит от имени класса события с суффиксом, поэтому я не знаю его до тех пор, пока не закончится генерация кода.

Итак, после создания помощников я создаю фабрику и создаю код для предоставления нового вспомогательного экземпляра на основе MyEvent.class. Проблема: мне нужен только один завод в модуле приложения, но он должен быть в состоянии предоставить помощники для событий из библиотечного модуля. Это невозможно сделать просто.

Что я наделал:

  1. Пропустить завод по производству модулей, от которых зависит мой модуль приложения;

  2. в модулях без приложения генерируют так называемую реализацию HelpersRegistrar (ы):

    - все они имеют тот же пакет (вы будете знать, почему позже);

    - их имена не сталкиваются из-за суффикса (см. Ниже);

    - Дифференциация между модулем приложения и библиотечным модулем осуществляется с помощью javac "-Amylib.suffix=MyModuleName" param, этот пользователь ДОЛЖЕН установить - это ограничение, но второстепенное. Для модуля приложения суффикс не должен указываться;

    - HelpersRegistrar сгенерированная реализация может обеспечить все, что мне нужно для генерации будущего кода: имя класса события, имя класса-помощника, пакет (эти два пакета для видимости пакетов между хелпером и событием) - все строки, включенные в POJO;

  3. в модуле приложения Я создаю помощников - как обычно, затем получаю HelperRegistrars своим пакетом, создавая их экземпляр, просматриваю их содержимое, чтобы обогатить мою фабрику кодом, предоставляющим помощники из других модулей. Все, что мне нужно для этого, это имена классов и пакет.

  4. Voilà! Моя фабрика может предоставлять экземпляры помощников как из модуля приложения, так и из других модулей.

Единственная неопределенность, оставленная в порядке, заключается в создании и запуске экземпляров процессорного класса в модуле приложения и в других модулях. Я не нашел какой-либо солидной информации об этом, но мой пример показывает, что компилятор (и, следовательно, генерация кода) сначала запускается в модуле, на который мы зависим, а затем - в модуле приложения (иначе компиляция модуля приложения будет f. .cked). Это дает нам основание рассчитывать на известный порядок выполнения кода в разных модулях.

Другой, слегка похожий подход: пропустить регистраторы, сгенерировать заводы во всех модулях и написать фабрику в модуле приложения, чтобы использовать другие заводы, которые вы получаете и называете так же, как и регистраторы выше.

Пример можно увидеть здесь: https://github.com/techery/janet-analytics - это библиотека, в которой я применял этот подход (тот, у кого нет регистраторов, с тех пор как у меня есть фабрики, но это может быть не так).

P. S .: суффикс пары может быть переключен проще «-Amylibraryname.library = истина» и заводы/регистраторов имена могут быть автоматически сгенерированы/увеличиваются