Loupe

#Android, la limite des 65536 références, multi-DEX et le mettre en place avec #XAMARIN et #VisualStudio

Il existe une limite au nombre de références (principalement des méthodes) que vous pouvez mettre dans une application Android : 65536 et pas une de plus. J'ai atteint cette limite la semaine dernière et je vais vous expliquer dans cet article la solution à mettre en place dans le cadre d'une application Xamarin. Vous verrez qu'il suffit de cocher une case pour activer le "multi-Dex" .... et de modifier des scripts du SDK ainsi que votre code... des opérations pas si simples que j'ai eu du mal à glaner sur internet.

Pourquoi une limite ?

La machine virtuelle Android (Dalvik) utilise votre application sous la forme d'archive au format "DEX". Ces archives ne sont pas prévues pour contenir plus de 2^16 définitions de méthodes et c'est cette limitation que l'on rencontre aussi dans nos applications.

Depuis la version 21 des APIs, cette limite n'est plus présente mais vous pouvez encore la rencontrer lorsque vous ciblez les versions antérieures.

La solution consiste donc à faire du Multi-dexing : Dalvik va utiliser une sorte de proxy vers plusieurs archives Dex au lieu d'utiliser une seule archive Dex. Cela est supporté nativement par le SDK Android et par l'outillage Xamarin après quelques modifications que nous verrons plus tard dans cet article.

On pourrait imaginer que c'est un problème à la marge mais on y est en réalité vite confronté en référençant les librairies Google classiques (quasiment 61 000 références) :

  • L'ajout des librairies de support (v4 et v7) représente 37561 références.
  • L'ajout du framework de publicité (AdMob) représente 23360 références.

Mon application, relativement moyenne ajoute quant à elle 2562 références. Les quelques autres projets référencés font alors dépasser le quota.

 

Détecter que cela se produit

Le premier symptôme est bien sûr d'avoir Visual Studio refusant de compiler votre application à l'aide d'un message explicite : "Java.exe" exited with code 2.

#Android, la limite des 65536 références, multi-DEX et le mettre en place avec #XAMARIN et #VisualStudio

 Je vous recommande alors de configurer VisualStudio pour afficher un log standard (et non pas "discret" comme par défaut) des actions MsBuild :

buildAndRun.PNG

 

Vous pouvez alors un message d'erreur plus explicite lorsque cela se produit :

Too many field references: 131000; max is 65536.
You may try using --multi-dex option

 

Le SDK Android vous donne ensuite une liste du nombre de références par projet. Un traitement rapide dans Excel vous permet de déterminer si vous souhaitez en supprimer pour ne pas avoir à multi-dexer :

execel.PNG

Activer la génération de Multi-Dex 

La génération de multi Dex s'active simplement dans les propriétés du projet Android --> onglet "Android Options". Il faut aussi penser à enlever les options "Fast Deployment" et "Use Shared Runtime" si vous ciblez des versions antérieures à 20 (4.4).

activate.PNG

Si cela était si simple, il n'y aurait pas eu besoin d'un article de blog :) ... car cela ne suffit pas, notamment si vous avez défini une classe Application personnalisée. Il va encore falloir faire quelques modifications

Modifier le SDK Android

La première étape est de s'assurer que le SDK Android est accessible dans un dossier ne contenant pas d'espaces dans son chemin d'accès. Pour le déplacer, c'est assez simple, vous le coupez-collez à l'emplacement voulu (par exemple à la racine du C). Après avoir redémarrer Visual Studio il faudra aller indiquer le nouvel emplacement dans la configuration Xamarin Android :

optiond.PNG

 

Il faut ensuite aller modifier le fichier "mainDexClasses.bat" présent dans le sous-dossier du dossier "build-tools" correspondant à la version de votre SDK Android pour remplacer les lignes 106 (if DEFINED output goto redirect) à 111(:afterClassReferenceListBuilder) par celles-ci (tout est indiqué sur ce bug Xamarin) :

SET params=%params:'=%
if DEFINED output goto redirect
call "%java_exe%" -Djava.ext.dirs="%frameworkdir%" com.android.multidex.MainDexListBuilder %disableKeepAnnotated% "%tmpJar%" %params%
goto afterClassReferenceListBuilder
:redirect
call "%java_exe%" -Djava.ext.dirs="%frameworkdir%" com.android.multidex.MainDexListBuilder %disableKeepAnnotated% "%tmpJar%" %params% 1>"%output%"
:afterClassReferenceListBuilder

 

Finalement, si vous utilisez la dernière version des outils de builds du SDK, il faut aller mettre à jour la librairie proguard (même désactivée...) à la main. Pour cela, téléchargez Proguard (sur SourceForge) et remplacer le dossier lib du dossier "tools/Proguard" de votre SDK par le dossier lib de la dernière version téléchargée.

 

Modifier votre application

Si vous déclarez une classe Application personnalisée dans votre projet Android, il va falloir effectuer une dernière étape : la faire dériver de la classe MultiDexApplication définie ci-dessous et trouvée elle aussi dans un bug Xamarin.

[Register("android/support/multidex/MultiDexApplication", DoNotGenerateAcw = true)]
    public class MultiDexApplication : Application
    {
        internal static readonly JniPeerMembers _members =
            new XAPeerMembers("android/support/multidex/MultiDexApplication", typeof(MultiDexApplication));

        internal static IntPtr java_class_handle;

        private static IntPtr id_ctor;

        [Register(".ctor", "()V", "", DoNotGenerateAcw = true)]
        public MultiDexApplication()
            : base(IntPtr.Zero, JniHandleOwnership.DoNotTransfer)
        {
            if (Handle != IntPtr.Zero)
                return;

            try
            {
                if (GetType() != typeof(MultiDexApplication))
                {
                    SetHandle(
                        JNIEnv.StartCreateInstance(GetType(), "()V"),
                        JniHandleOwnership.TransferLocalRef);
                    JNIEnv.FinishCreateInstance(Handle, "()V");
                    return;
                }

                if (id_ctor == IntPtr.Zero)
                    id_ctor = JNIEnv.GetMethodID(class_ref, "<init>", "()V");
                SetHandle(
                    JNIEnv.StartCreateInstance(class_ref, id_ctor),
                    JniHandleOwnership.TransferLocalRef);
                JNIEnv.FinishCreateInstance(Handle, class_ref, id_ctor);
            }
            finally
            {
            }
        }

        protected MultiDexApplication(IntPtr javaReference, JniHandleOwnership transfer)
            : base(javaReference, transfer)
        {
        }

        internal static IntPtr class_ref
        {
            get { return JNIEnv.FindClass("android/support/multidex/MultiDexApplication", ref java_class_handle); }
        }

        protected override IntPtr ThresholdClass
        {
            get { return class_ref; }
        }

        protected override Type ThresholdType
        {
            get { return typeof(MultiDexApplication); }
        }
    }

 

Tester le bon fonctionnement

Une fois toutes ces actions effectuées, il est aussi nécessaire d'aller supprimer à la main les dossiers obj et bin du projet et de compiler votre application.

 

Happy coding :)

 

Photo de profil

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus