2016-07-20 25 views
5

Mam projekt z wieloma modułami w Android Studio. Moduł może być zależnym od innego modułu, na przykład:Wielomodułowe przetwarzanie adnotacji w Android Studio

Module PhoneApp -> Moduł FeatureOne -> Module Usługi

podaję mój przetwarzania adnotacji w module głównym, ale android-apt przetwarzanie występuje adnotacja tylko na najwyższym poziomie (PhoneApp), aby teoretycznie miał dostęp do wszystkich modułów podczas kompilacji. Jednak w generowanym pliku java widzę tylko klasy opisane w PhoneApp i żadne z pozostałych modułów.

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

W innych modułach znajduję wygenerowany plik w katalogu półproduktów, który zawiera tylko adnotowane pliki z tego modułu.

Moim celem jest posiadanie pojedynczego wygenerowanego pliku w aplikacji PhoneApp, który umożliwia mi dostęp do plików z przypisami ze wszystkich modułów. Nie do końca wiadomo, dlaczego proces generowania kodu działa dla każdego i nie można zebrać wszystkich adnotacji w aplikacji PhoneApp. Każda pomoc doceniona.

Code jest dość proste i prosto do przodu tak daleko, checkIsValid() pomija się, jak to działa prawidłowo:

Adnotacja Procesor:

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

To właśnie na demo przetwarzania adnotacji, który współpracuje z Guice , jeśli ktoś jest ciekawy.

Jak mogę więc wszystkie klasy z adnotacjami dołączyć do wygenerowanego pliku .java PhoneApp ze wszystkich modułów?

+0

to dobre pytanie.Myślę, że wiele osób doceni, jeśli podzielisz się swoim rozwiązaniem, gdy je znajdziesz, jeszcze nie. Istnieje dyskusja na temat ograniczeń procesorów w sekcji [dbFlow project issues section] (https://github.com/Raizlabs/DBFlow/issues/266), co można uznać za dowód na to, że niemożliwe jest wdrożenie pożądanego połączenia moduł adnotacji procesora. – konata

+0

Ponieważ procesor adnotacji działa dla każdego modułu osobno, możesz spróbować podejścia przyrostowego, ale to trochę zależy od twojej sprawy. Może działać, jeśli nie trzeba zmieniać całej klasy po przetworzeniu następnego modułu, a jedynie dodawać nowe linie do istniejącej klasy: 1. Podczas przetwarzania pierwszego modułu należy wygenerować klasę w określonej lokalizacji (gdzieś w module modułu PhonApp ** wygenerowane ** drzewo); 2. Podczas przetwarzania następnego modułu sprawdź, czy wygenerowana klasa istnieje i dodaj do niego nowy kod. – konata

+0

Ponieważ to była tylko prezentacja w prezentacji, nie kontynuowałem szukania rozwiązania. (Pomyślałem, że powstało pytanie, dlaczego wygenerowany plik nie zawiera wszystkich danych modułów) Rozważałem użycie zadań Gradle do skopiowania wygenerowanych plików z każdego modułu, ale wolałbym rozwiązanie, które nie musiałoby polegać na narzędziach do kompilacji . – fakataha

Odpowiedz

0

Zamiast używać Filer do zapisywania wygenerowanego pliku, należy zamiast tego używać zwykłego zapisu pliku Java. Będziesz musiał serializować obiekty do plików tymczasowych podczas przetwarzania, ponieważ nawet zmienne statyczne nie będą zapisywane pomiędzy modułami. Skonfiguruj gradle, aby usunąć pliki tymczasowe przed kompilacją.

0

Nigdy nie jest zbyt późno, aby odpowiedzieć na pytanie na SO, więc ...

podczas jednego z zadań w pracy mam do czynienia z bardzo podobną komplikację.

I udało mi się go rozwiązać.

Krótka wersja

Wszystko, co musisz wiedzieć o wygenerowanych klas z moduleB w moduleA jest opakowanie i nazwa klasy. To może być przechowywane w jakiejś klasie generowanej MyClassesRegistrar umieszczonej w znanym pakiecie. Użyj sufiksów, aby uniknąć konfliktów nazw, zdobądź rejestratorów w pakiecie. Wywołaj je i użyj danych z nich.

Lond wersja

Przede wszystkim - nie będą mogły zawierać zależność kompilacji tylko tylko na najwyższym modułu (pozwala wywołać moduł „app” jako typowy android struktura projektu robi) . Przetwarzanie adnotacji po prostu nie działa w ten sposób i, o ile mogłem się dowiedzieć, nic na to nie można poradzić.

Teraz szczegóły.Moje zadanie było następujące: Mam napisane przez ludzi adnotowane klasy. Nazwałbym je "wydarzeniami". W czasie kompilacji muszę wygenerować klasy pomocnicze dla tych zdarzeń w celu włączenia ich struktury i zawartości (zarówno statycznie dostępnych (wartości adnotacji, const itp.) I dostępnego czasu pracy (przekazuję obiekty zdarzeń do tych pomocników, gdy używam ich później). Nazwa klasy zależy od nazwy klasy zdarzenia z sufiksem, więc nie wiem, aż do zakończenia generowania kodu

Po wygenerowaniu pomocników tworzę fabrykę i generuję kod, aby dostarczyć nową instancję pomocniczą w oparciu o podaną MyEvent.class. problem: potrzebowałem tylko jednej fabryki w module aplikacji, ale powinien być w stanie zapewnić pomocników dla wydarzeń z modułu bibliotecznego - nie można tego zrobić prosto.

Co zrobiłem:

  1. pomiń tworzenie fabryki modułów, od których zależy mój moduł aplikacji;

  2. w modułach non-app wygenerować tzw realizację HelpersRegistrar (s):

    - wszystkie one mają ten sam pakiet (będziesz wiedzieć dlaczego później);

    - ich nazwy nie kolidują z powodu przyrostka (patrz poniżej);

    - rozróżnienie między modułem aplikacji a modułem bibliotecznym odbywa się za pośrednictwem parametru javac "-Amylib.suffix=MyModuleName", który użytkownik MUSI ustawić - jest to ograniczenie, ale pomniejsze. Nie trzeba określać przyrostka dla modułu aplikacji;

    - Wygenerowana implementacja HelpersRegistrar może zapewnić wszystko, czego potrzebuję do wygenerowania przyszłego kodu fabrycznego: nazwa klasy zdarzenia, nazwa klasy pomocnika, pakiet (te dwa pakiety dzielenia dla widoczności pakietu między helper i zdarzenia) - wszystkie łańcuchy, włączone w POJO;

  3. w module aplikacji Generuję pomocników - jak zwykle, uzyskuję HelperRegistrars według ich pakietu, tworzę ich, przechodzę przez ich zawartość, aby wzbogacić moją fabrykę o kod dostarczający pomocników z innych modułów. Potrzebowałem tylko nazw klas i paczki.

  4. Voilà! Moja fabryka może dostarczać instancje pomocników zarówno z modułu aplikacji, jak iz innych modułów.

Pozostała jedyna niepewność w zakresie tworzenia i uruchamiania instancji klasy procesorowej w module aplikacji i innych modułach. Nie znalazłem żadnych solidnych informacji na ten temat, ale uruchomienie mojego przykładu pokazuje, że kompilator (a zatem i generowanie kodu) uruchamia się najpierw w module, od którego jesteśmy zależni, a następnie - w module aplikacji (inaczej kompilacja modułu aplikacji będzie f). .cked). To daje nam powód, by oczekiwać znanej kolejności wykonywania procesorów kodu w różnych modułach.

Innym, nieco podobnym podejściem jest: pomiń rejestratorów, wygeneruj fabryki we wszystkich modułach i napisz fabrykę w module aplikacji, aby użyć innych fabryk, które otrzymujesz i nazywasz tak samo, jak rejestratory powyżej.

Przykład można zobaczyć tutaj: https://github.com/techery/janet-analytics - jest to biblioteka, w której zastosowałem to podejście (ten bez rejestratorów, ponieważ mam fabryki, ale może to nie dotyczyć ciebie).

P. S .: sufiks param może być włączony do prostsze „-Amylibraryname.library = true” i fabryk/rejestrujących nazwy mogą być wygenerowany automatycznie/zwiększany