• Home
  • /
  • GAMEDEV
  • /
  • 3 sposoby na “program type already present” – czyli dlaczego gradle jest kobietą?

3 sposoby na “program type already present” – czyli dlaczego gradle jest kobietą?

Problem powtarzalności

Znacie tę historię o kobiecie, która musi iśc na przyjęcie? Tydzień wcześniej stwierdza, że nie ma się w co ubrać, więc idzie do sklepu kupić nową, wyjątkową sukienkę, do tego pasujące buty a na końcu torebkę, która pasuje do butów. W dniu przyjęcia od rana stroi się, maluje, zakłada sukienkę… a na przyjęciu jest już inna kobieta w tej kreacji?

Gradle jest kobietą i też nie lubi jak coś się powtarza, szczególnie w sposób, którego nie można połączyć. 😉

Jestem pewna, że chociaż raz, gdy macie projekt z wieloma pluginami, spotkaliście się z błędem D8, czyli “D8: Program type already present”. Po wpisie tłumaczącym jak działa gradle w projektach Unity, możesz się domyślić (i wyczytać z logów), że gradle buntuje się, bo nie może połączyć dexów – czyli plików z kodem bajtowym uruchamianych na Android Virtual Machine (dalvik). To jak z sukienką ze wstępu, dany typ już raz występuje.

Sprawdźmy teraz w jakich przypadkach ten błąd może wystąpić i dlaczego czasem jest trudny do wykrycia.

Przykładowa sytuacja z “type already present”

W naszym przykładzie będziemy bazować na sytuacji, gdy ktoś w projekcie miał plugin reklam facebookowych z oficjalnego SDK Facebooka i dołożył do tego AdMoba z adapterami: m.in facebookiem.

Sytuacja wygląda więc tak: mamy SDK Facebooka, z biblioteką com.facebook.android.audience-network-sdk-5.6.0. Zostaje podjęta decyzja biznesowa, że sam SDK Facebooka zostaje do działań społecznościowych, lecz dodajemy mediatora reklamowowego AdMob z zintegrowanymi reklamami facebooka. Ściągamy więc Admoba oraz adapter FAN (Facebook Audience Network) z biblioteką: audience-network-sdk-5.6.0. Co się dzieje? Pojawia się problem z buildem, a na konsoli wyświetla się błąd D8.

Objawy

Jak rozpoznamy, że mamy problem z błędem D8? Na pewno go nie przegapimy, ponieważ w momencie w którym konsola wyświetli nam błąd proces budowania naszej gry zostanie przerwany. W samej konsoli też pojawią się stosowne informacje. W przypadku Unity 2019+ sytuacja jest prosta i przyjemna, gdyż w logach pokaże nam się taka informacja, która prosto i czytelnie pokazuje gdzie leży problem:

java.lang.RuntimeException: Duplicate class com.facebook.ads.AbstractAdListener found in modules classes.jar (:audience-network-sdk-5.6.0:) and classes.jar (:com.facebook.android.audience-network-sdk-5.6.0:)

Gdy zaś podobny przypadek otworzymy na Unity 2018, błąd, który dostaniemy będzie wyglądał w taki sposób:

Execution failed for task : transformDexArchiveWithExternalLibsDexMergerForRelease.
Program type already present: com.facebook.ads.AbstractAdListener

Jak widzicie po tak zdawkowej informacji o wiele ciężej jednoznacznie wskazać biblioteki, które powodują błędy. Oczywiście w podanym wyżej przykładzie mamy informację, że jest to facebook, lecz w przypadku, gdy naszą jedyną wskazówką będzie “com.android.support”, których możemy mieć kilka różnych rodzajów, sprawa się komplikuje.

Rozwiązania

Poniżej przedstawiam trzy sposoby na rozwiązanie tego błędu i przywrócenie bezbolesnego budowania naszej gry. Polegają one na metodycznym przeszukiwaniu folderów z bibliotekami oraz plików konfiguracyjnych.

Sprawdzenie 1. Biblioteki w plikach gry

Najpowszechniejszym powodem, który występował w moich projektach było powielenie danego typu w plikach gry. Mówię konkretnie o katalogu Plugins w Assetach. Sprawdźmy więc na przykładzie co robić, gdy w plikach mamy dwie biblioteki:

  • audience-network-sdk-5.6.0
  • com.facebook.android.audience-network-sdk-5.6.0

Gdy spotkacie się z błędem: “program type already present”, polecam wam postępować w następujący sposób:

  1. Sprawdzamy, czy w projekcie nie występuje dwa razy ta sama biblioteka w dwóch różnych wersjach. Wiemy, że chodzi o facebooka, więc jeśli mamy świadomość jakie pluginy występują w projekcie, możemy bezpośrednio sprawdzić po nazwach:
    • wyszukujemy w zakładce “project”: "com.facebook.android.audience-network-sdk"
    • sprawdzamy wersje wyświetlonych bibliotek, jeśli występują dwie, mamy potencjalnego winowajcę:
      (gdyby była starsza wersja, mogłoby to wyglądać tak) com.facebook.android.audience-network-sdk-5.1.0
      com.facebook.android.audience-network-sdk-5.6.0
  2. Jeśli nie występuje w dwóch różnych wersjach, sprawdzamy “słowa kluczowe” z com.facebook.ads.AbstractAdListener:
    facebook i ads. Niestety audience-network-sdk-5.6.0 nie zawiera w swojej nazwie żadnego słowa kluczowego. Nasza znajomość dodawanych do gry pluginów powinna jednak zwrócić naszą uwagę na to, że reklamy facebookowe są nazywane facebook audience network i pozwolić wyszukać drugą bibliotekę.
  3. Na końcu sprawdzamy w internecie konkretny typ, czyli z com.facebook.ads.AbstractAdListener szukamy w google słowa AbstractAdListener. Wyniki wyszukiwania powinny uzmysłowić nam w jakich pluginach, będących w naszym projekcie, może czaić się dodatkowa biblioteka tego typu, w tym przypadku: w adapterze do FAN’a w Admobie.

Rozwiązaniem jest usunięcie zbędnej biblioteki.

Sprawdzenie 2. Biblioteki: jedna w plikach gry, druga w mainTemplate

Kojarzycie plik mainTemplate.gradle w Unity? Jeśli nie, to zerknijcie do wpisu, który dość szczegółowo go opisuje, gdyż będzie on potrzebny, by zrozumieć, dlaczego D8 występuje w tej sytuacji.

W przypadku, gdy nie znaleźliśmy dzięki pierwszej metodzie żadnych powielonych bibliotek, powinniśmy sprawdzić plik konfiguracyjny gradle’a. Plik konfiguracyjny gradle pozwala na dołączanie potrzebnych bibliotek na etapie budowania i kompilowania gry, naturalnym jest więc, że jeśli jedna z powielonych bibliotek znajduje się w plikach gry w katalogu Plugins, a druga zostanie dynamicznie pobrana w czasie budowania gry – gradle wyświetli błąd i zatrzyma cały proces.

    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation 'com.android.support:multidex:1.0.3'
    // Android Resolver Dependencies Start
        compile 'com.android.support:support-v4:28.0.0' 
    // Assets/MoPub/Scripts/Editor/MoPubDependencies.xml:3
        compile 'com.applovin:applovin-sdk:9.7.1' 
    // Assets/MoPub/Mediation/AppLovin/Editor/AppLovinDependencies.xml:4
        compile 'com.facebook.android:audience-network-sdk:5.6.0' 
    // Assets/MoPub/Mediation/FacebookAudienceNetwork/Editor/FacebookAudienceNetworkDependencies.xml:4
    // Android Resolver Dependencies End
    **DEPS**}

Powinniśmy w takim przypadku sprawdzić plik mainTemplate.gradle. Przeglądając dependencies możemy wyróżnić biblioteki:

  • com.android.support:multidex
  • com.android.support:support-v4
  • com.applovin:applovin-sdk
  • com.facebook.android:audience-network-sdk

Przypomnijmy sobie, jaki błąd wyświetla wersja Unity 2018:

Execution failed for task : transformDexArchiveWithExternalLibsDexMergerForRelease.
Program type already present: com.facebook.ads.AbstractAdListener

Sprawdzając po kolei (lub typując najbardziej pradopodobne) bliblioteki możemy postąpić w taki sposób:

  1. Wiemy, że problem leży po stronie facebook lub ads, co wynika z błędu.
  2. Odszukujemy bibliotekę w pliku konfiguracyjnym, która potencjalnie może powodować problemy. Będą to:
    • com.applovin:applovin-sdk ponieważ dotyczy reklam (ads)
    • com.facebook.android:audience-network-sdk ponieważ dotyczy reklam i facebooka
  3. Gdy uda nam się określić, która z nich powoduje problem (np. budując projekt bez jednej z nich i sprawdzając w folderach Plugins czy jest jej duplikat) polecam usunąć duplikat i zostawić:
    • wersję, która jest najodpowiedniejsza dla projektu
    • wersję z pliku mainTemplate, by zachować porządek.

Sprawdzenie 3. Biblioteki w mainTemplate

Ostatnim, najłatwiejszym do wykrycia przypadkiem, jest obecność dwóch bibliotek w dwóch różnych wersjach w pliku mainTemplate.gradle.

    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        compile 'com.facebook.android:audience-network-sdk:5.3.1' 
        implementation 'com.android.support:multidex:1.0.3'
    // Android Resolver Dependencies Start
        compile 'com.android.support:support-v4:28.0.0' 
    // Assets/MoPub/Scripts/Editor/MoPubDependencies.xml:3
        compile 'com.applovin:applovin-sdk:9.7.1' 
    // Assets/MoPub/Mediation/AppLovin/Editor/AppLovinDependencies.xml:4
        compile 'com.facebook.android:audience-network-sdk:5.6.0' 
    // Assets/MoPub/Mediation/FacebookAudienceNetwork/Editor/FacebookAudienceNetworkDependencies.xml:4
    // Android Resolver Dependencies End
    **DEPS**}

Posłużę się tu abstrakcyjnym przykładem, gdy wcześniej ktoś ręcznie dopisał do pliku konfiguracyjnego linijkę:

compile 'com.facebook.android:audience-network-sdk:5.3.1' .

W takiej sytuacji należy pozostawić odpowiednią wersję biblioteki, usuwając drugą.

Podsumowanie

Powyżej przedstawiłam trzy sposoby na rozwiązanie problemu z powtarzalną biblioteką, jednak każdy sposób na uporanie się z błędami jeśli działa i nie polega na wyrzuceniu projektu do kosza 😉

Ustrukturyzowane ścieżki debugowania i radzenia sobie z błędami są bardzo ważne – pozwalają unikać frustracji i zirytowania, tak częstego w trakcie programowania. Dlatego poniżej skrót moich trzech metod radzenia sobie z “program type already present”:

  1. Sprawdź czy powtarzalne bibliteki nie znajdują się w katalogach Plugins.
  2. Sprawdź, czy jedna z nich nie jest pobierana przez mainTemplate.gradle.
  3. Na koniec sprawdź, czy przypadkiem w mainTemplate.gradle nie ma dwóch wersji tej samej biblioteki.

Jeśli chcesz dowiedzieć się więcej o gradle – polecam ci zajrzeć do oficjalnej dokumentacji, przeczytaj też jak gradle działa z Unity.

Zapisz się do newslettera, odbieraj darmowe materiały do nauki i bądź na bieżąco z nowymi artykułami!

Invalid email address
Zgadzam się na wysyłkę newslettera.
Polityka prywatności