web-dev-qa-db-fra.com

Le premier lancement d'Activity avec Google Maps est très lent

Je souhaite avoir SupportMapFragment dans l'une de mes activités. J'ajoute ce fragment directement à la mise en page XML et cette mise en page définie comme vue du contenu. Mais lorsque Activity est lancé pour la première fois, cela prend trop de temps (plus d'une seconde). Les prochains lancements sont ok et prennent quelques millisecondes.

J'ai essayé:

  • supprimer toute initialisation
  • utiliser MapFragment au lieu de SupportMapFragment
  • ajouter MapFragment par programmation

mais rien n'a aidé. La carte est affichée sans problème ni journal suspect. 

Avez-vous des suggestions, quelles en sont les causes et comment l’améliorer? 

edit: J'ai un ListView et lorsque l'utilisateur clique sur Item, il lance DetailActivity avec MapFragment. Après avoir cliqué sur un élément, un délai notable s’écoule avant l’apparition de DetailActivity. Seule la méthode onCreate, où j'appelle setContentView, dure plus d'une seconde. Et tandis que l'activité est dans la méthode onCreate, il n'y a pas de contenu visible de cette activité. Ce délai entre le clic et l'affichage du contenu n'est pas très convivial.

Je vous remercie

37
nonahex

La raison pour laquelle le premier chargement prend autant de temps est due au fait que les API Play Services doivent être chargées comme indiqué dans les lignes de journal:

I/Google Maps Android API﹕ Google Play services client version: 6587000
I/Google Maps Android API﹕ Google Play services package version: 6768430

Malheureusement, le "package" prend environ une seconde à charger et en utilisant uniquement MapsInitializer, vous obtenez le "client". Donc, voici un travail pas très joli: Initialisez une carte fictive dans votre activité principale de lancement.

mDummyMapInitializer.getMapAsync(new OnMapReadyCallback() {
  @Override
  public void onMapReady(GoogleMap googleMap) {
    Log.d(TAG, "onMapReady");
  }
});

Désormais, lorsque vous chargerez votre carte réelle ultérieurement, il ne devrait pas être nécessaire d'initialiser les API de services de lecture. Cela ne devrait pas non plus causer de retard dans votre activité principale, car la méthode asynchrone s'exécute à partir du thread principal. 

Etant donné que vous devez faire l'initialisation quelque part, je pense qu'il est logique de le faire correctement lorsque l'application démarre, afin que, lorsque vous chargez une activité nécessitant une carte, vous n'ayez pas à attendre du tout.

Remarque: mDummyMapInitializer doit être MapFragment ou SupportMapFragmentet doit être ajouté à l'activité, sinon les API de Play Services ne seront pas chargées. La méthode getMapAsync elle-même doit également être appelée à partir du thread principal.

19
clocksmith

Ok, donc je viens d'avoir le même problème et pense, après avoir examiné cette question, qu'il n'y a pas de solution "de Nice".

Mon hack actuel consiste à retarder l’ajout du fragment, ce qui donne à l’activité une chance de restituer le reste avant d’ajouter la carte.

Maintenant, j'intègre la carte en tant qu'enfant, donc mon code ressemble à ceci:

    // inside onCreateView
    new Handler().postDelayed(new Runnable() {

        @Override
        public void run() {
            if (isAdded()) {
                FragmentManager fm = getChildFragmentManager();
                GoogleMapFragment mapFragment = GoogleMapFragment
                        .newInstance();
                fm.beginTransaction()
                        .replace(R.id.mapContainer, mapFragment).commit();
            }
        }
    }, 1000);

si vous ajoutez directement à Activity, cela pourrait ressembler à ceci:

    // inside onCreate
    new Handler().postDelayed(new Runnable() {

        @Override
        public void run() {
            if (!isFinishing()) {
                FragmentManager fm = getFragmentManager();
                GoogleMapFragment mapFragment = GoogleMapFragment
                        .newInstance();
                fm.beginTransaction()
                        .replace(R.id.mapContainer, mapFragment).commit();
            }
        }
    }, 1000);

Néanmoins, une vérification à l'intérieur du Runnable est nécessaire pour s'assurer que nous n'essayons pas d'ajouter la carte à une activité ou un fragment non existant.

Je ne suis pas un fan des retards codés en dur comme celui-ci, donc je reviendrai si je trouve quelque chose de mieux. 1 seconde devrait cependant suffire et pourrait probablement être encore moins. 

15
cYrixmorten

Je l'ai résolu en utilisant MapsInitializer dans mon Application.onCreate ():

MapsInitializer.initialize(this);

De bons résultats et une solution plus propre (et non pas hacky)!

10
Fidel Montesino

Je me suis également battu contre ce problème et j'ai constaté des améliorations considérables en procédant comme suit:

1) Débranchez votre câble USB (ou déconnectez votre session de débogage) et essayez à nouveau. Google Maps dans une application est beaucoup plus lent lorsqu'une session de débogage est active. Déconnectez le débogueur et ça va beaucoup plus vite ... ce n'est certainement pas le plus rapide, mais c'est au moins acceptable.

2) N'appelez pas setMapType () à moins que vous n'ayez déjà appelé getMapType () et confirmé qu'il est différent de celui que vous souhaitez définir. Plusieurs appels pour le même type de carte entraîneront toujours sa réinitialisation à chaque fois, ce qui peut prendre un certain temps.

3) Ajoutez le fragment de carte par programmation, semblable à ce que @cYrixmorten a posté, mais je le fais à partir d'un thread d'arrière-plan démarré à la fin de mon onResume (), qui attend ensuite 50 ms et l'exécute ensuite sur le thread d'interface utilisateur. Cela l'empêche de frapper immédiatement le thread d'interface utilisateur, ce qui donne à l'activité le temps de charger et d'afficher; vous devriez au moins être à l'écran pendant que la carte étouffe tout.

Le problème ici est que vous souhaitez créer une nouvelle instance MapFragment une seule fois par activité, et non à chaque rotation de l'orientation de l'écran. Ce que je fais est d’appeler "getFragmentManager (). FindFragmentById (R.id.mapContainer)", ce qui me donnera le descripteur de fragment de carte de la dernière fois ou un null si c’est la première fois (auquel cas je créerai le fragment de carte et faire le FragmentManager.replace ()).

8
Wookie

J'ai une activité "principale" - et une activité avec mapView. Lorsque cette activité-with-mapView démarre pour la première fois, elle est vraiment lente.

Le message de clocksmith m'a donné une idée pour commencer l'initialisation à partir de l'activité principale dans un thread séparé. Et cela résout vraiment le problème.

Voici mon code de l'activité "principale":

public void onCreate(Bundle savedInstanceState) {
    ...

    Runnable initMap = () -> {
        BaseApplication.d("Start init mapView");
        MapView mapView = new MapView(MainActivity.this);
        mapView.onCreate(null);
        BaseApplication.d("... done");
    };
    new Thread(initMap).start();
}

mapView n'est jamais utilisé - c'est uniquement à des fins d'initialisation.

Et voici une trace de pile - juste pour info:

12-09 19:31:54.442 17172-17341/my.app D/XXX: Start init mapView
12-09 19:31:54.525 17172-17341/my.app I/zzy: Making Creator dynamically
12-09 19:31:55.007 17172-17341/my.app D/ChimeraCfgMgr: Reading stored module config
12-09 19:31:55.153 17172-17341/my.app D/ChimeraCfgMgr: Loading module com.google.Android.gms.maps from APK /data/user/0/com.google.Android.gms/app_chimera/chimera-module-root/module-71c764a6f3cb92bdc5525a965b589e7c5ed304f3/MapsModule.apk

12-09 19:31:55.154 17172-17341/my.app D/ChimeraModuleLdr: Loading module APK /data/user/0/com.google.Android.gms/app_chimera/chimera-module-root/module-71c764a6f3cb92bdc5525a965b589e7c5ed304f3/MapsModule.apk
12-09 19:31:55.262 17172-17341/my.app D/ChimeraFileApk: Primary ABI of requesting process is armeabi-v7a
12-09 19:31:55.271 17172-17341/my.app D/ChimeraFileApk: Classloading successful. Optimized code found.
12-09 19:31:55.316 17172-17341/my.app W/System: ClassLoader referenced unknown path: /data/user/0/com.google.Android.gms/app_chimera/chimera-module-root/module-71c764a6f3cb92bdc5525a965b589e7c5ed304f3/native-libs/armeabi-v7a

12-09 19:31:55.317 17172-17341/my.app W/System: ClassLoader referenced unknown path: /data/user/0/com.google.Android.gms/app_chimera/chimera-module-root/module-71c764a6f3cb92bdc5525a965b589e7c5ed304f3/native-libs/armeabi
12-09 19:31:55.618 17172-17341/my.app I/Google Maps Android API: Google Play services client version: 7571000
12-09 19:31:55.630 17172-17341/my.app I/Google Maps Android API: Google Play services package version: 8489438
12-09 19:31:55.969 17172-17341/my.app I/e: Token loaded from file. Expires in: 423267993 ms.
12-09 19:31:55.969 17172-17341/my.app I/e: Scheduling next attempt in 422967 seconds.
12-09 19:31:56.338 17172-17341/my.app D/XXX: ... done

Comme on peut le constater, cela prend vraiment beaucoup de temps ...

3
cVoronin

Pour moi, c’était bien plus lent que 1sec, j’utilisais:

mapFragment.getMap();

Puis j'ai changé pour:

 mapFragment.getMapAsync(new OnMapReadyCallback() {
        @Override
        public void onMapReady(GoogleMap googleMap) {
            map = googleMap;
        }
 });

L'utilisation de getMapAsync () ne bloquera pas l'interface utilisateur. Votre activité se chargera donc avant la carte. C'est toujours lent, mais pour mes besoins, je pouvais simplement afficher un message de chargement.

1
sagits

Semblable aux autres solutions ici, mais en utilisant RxJava + RxAndroid.

Appelez simplement cet extrait de l'activité de lanceur onCreate.

Observable.fromCallable(new Callable<Void>() {
    @Override
    public Void call() {
        MapView mapView = new MapView(LaunchActivity.this); // Replace LaunchActivity with appropriate activity's name
        mapView.onCreate(null);
        return null;
    }
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(
    new Action1<Void>() {
        @Override
        public void call(Void ignorable) {
            Log.i("Google Maps", "Initialised Google Maps.");
        }
    }, new Action1<Throwable>() {
        @Override
        public void call(Throwable ignorable) {
            Log.w("Google Maps", "[EXPECTED] Initialized Google Maps but got: " + ignorable.getMessage());
        }
    });
1
Joao Sousa

J'ai eu le même problème et l'astuce MapsInitializer ne fonctionnait pas pour moi.

À mon humble avis, la meilleure solution au problème consiste à charger à la main le fragment de carte tel que décrit par les autres utilisateurs. Ce n'est pas une solution de hacky, vous devez juste vous débrouiller avec l'instance de fragment

mMapFragment = MapFragment.newInstance();
fragmentManager.beginTransaction().replace(R.id.map_fragment_container, fragment, FRAGMENT_GOOGLEMAPS_TAG).commit();
0
Vahn84