46

I want to have SupportMapFragment in one of my Activity. I add this fragment directly to layout xml and this layout set as content view. But when Activity is launched for the first time, it takes too long (over 1 second). Next launches are ok and take few milliseconds.

I tried:

  • remove any initialization
  • use MapFragment instead of SupportMapFragment
  • add MapFragment programatically

but nothing helped. Map is shown without any problem or suspicious log.

Do you have any suggestion, what it causes and how to improve it?

edit: I have a ListView and when user clicks on Item, it launches DetailActivity with MapFragment. After click on item there is a noticeable delay before DetailActivity shows up. Only method onCreate, where I call setContentView, runs over 1 second. And while activity is in onCreate method, there is no visible content from this activity. This delay between click and showing content is not very user friendly.

Thank you

nonahex
  • 677
  • 1
  • 6
  • 9
  • 1
    What do you mean by "It's not possible'? And by "fake"? – nonahex Oct 03 '14 at 12:31
  • Ya off course... Mapview need a time to load and it's around 10 second. and let me clear one another thing if you used Explicit Intent then it'll take time to navigate to another screen and it's around 10 second. – M D Oct 03 '14 at 12:33
  • Ok, now I understand, what you mean. Initialization of MapFragment doesn't take 1 second, but launching Activity with MapFragment takes over 1 second. I added to question, what's going on in application and what I mean by 1 second delay. – nonahex Oct 03 '14 at 12:35
  • ya exactly. That's what i am talk in about. – M D Oct 03 '14 at 12:37
  • Sorry, but I don't buy this as explenation and answer. Especially, when there are other application without this issue. For example Google I/O 2014 application. – nonahex Oct 03 '14 at 12:53
  • I'm also facing this issue. In my app I display the map in several activities. The first time I start any activity with a map, it takes longer than the subsequent times. – Marcelo Noguti Dec 02 '14 at 16:55

9 Answers9

24

The reason why the first load takes so long is because the Play Services APIs have to load as seen in log lines:

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

Unfortunately, the "package" one takes about a second to load and using the MapsInitializer only will get you the "client." So here is a a not so pretty work around: Initialize a dummy map in your main launcher activity.

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

Now when you load your actual map later on, it shouldn't have to initialize the Play services APIs. This shouldn't cause any delays in your main activity either because the async method executes off the main thread.

Since you have to do the initialization somewhere no matter what, I think it makes sense to do it right when the app starts, so that when you load an activity that actually needs a map, you do not have to wait at all.

Note: mDummyMapInitializer must be a MapFragment or SupportMapFragmentand must be added to the activity, or else the Play Services APIs won't be loaded. The getMapAsync method itself must also be called from the main thread.

clocksmith
  • 6,226
  • 3
  • 32
  • 45
  • About the reason, it seems you are right. On my other project, is loading a Map fragment ok. Different is, second projekt uses google play on more things, than just a map. And there I initialize google play services on application start. So the delay is caused by initialization of google play services. And it can be done much sooner than at the first use of Map fragment. – nonahex Feb 02 '15 at 08:54
  • 7
    I wish there was a way to initialize these services in the Application.java instead of having to load a map in an activity... – clocksmith Feb 11 '15 at 23:01
  • I have tried this solution but getMapAsync nevers returns in the Acvitity. That's really weird because in the Fragment where I have the actual map displayed, I don't do anything special before calling getMapAsync. Any idea? – JDenais Nov 04 '15 at 01:59
  • @JDenais, sounds like you're missing a call to mDummyMapInitializer.onCreate(). This will be quite slow. – Brian Attwell Jan 27 '16 at 21:27
20

Ok so I just had the same issue and think, after viewing this question, that there is no 'nice' solution.

My current hack is to delay adding the fragment, giving the Activity a chance to render everything else before adding the map.

Now, I am embedding the map as a childfragment, so my code looks like this:

    // 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);

if adding directly to Activity it might look like this:

    // 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);

Nevertheless, a check inside the Runnable is needed to ensure that we are not trying to add the map to some non-existing Activity or Fragment.

I am not a fan of hardcoded delays like this, so will return if I come up with something better. 1 second should be plenty though and could probably be even less.

cYrixmorten
  • 7,110
  • 3
  • 25
  • 33
  • 1
    Thank you. I'm glad, there is someone else with this issue and is not satisfied, how it works. Yes, this is possible solution and my is very similar. I was able to avoid hardcoded delay. Initialize MapFragment posrt method on holder view. Call it in onResume. – nonahex Oct 20 '14 at 12:06
  • @nonahex thanks but my problem is that my map is withing a fragment, which again is kept in a ViewPager. So if I move the initialization to onResume, it will be re-instantiated while swiping, which I do not want. Otherwise a great idea. – cYrixmorten Oct 20 '14 at 12:24
  • But you can initialize that fragment only if it's not initialized yet. There should be one simple condition enough. – nonahex Oct 20 '14 at 13:14
  • @nonahex I guess you are right about that, actually. Will give it a shot :) – cYrixmorten Oct 20 '14 at 13:37
  • This is the only one that worked for me. 100ms was enough for me. – Davi Alves Mar 29 '15 at 17:43
  • This is the best solution! – sebasira Sep 15 '17 at 19:51
18

I solved it by using MapsInitializer in my Application.onCreate():

MapsInitializer.initialize(this);

Good results and cleaner (and not hacky) solution!

Fidel Montesino
  • 337
  • 3
  • 7
  • Of my 1 second loading time this saved me 100 ms. –  Nov 28 '18 at 15:24
  • I measured map initialization without that command to be ~700 ms first time, ~200 ms afterwards. Then I've run 2 experiments: at first I've put this line in MainActivity's onCreate, after that to the Fragment just before the map in onViewCreated. Both were the same, ~700 ms, nothing changed. Tried to use both local and application contexts, doesn't work. – Kirill Starostin Jul 08 '21 at 07:02
11

I've been fighting this issue too, and have found considerable improvements by doing the following:

1) Unplug your USB cable (or otherwise disconnect your debugging session) and try it again. Google Maps in an app is much slower when a debugging session is active. Disconnect the debugger and it gets a lot faster... it's still certainly not the fastest, but it's at least acceptable.

2) Don't call setMapType() unless you've already called getMapType() and confirmed that it's different from what you want to set it to. Multiple calls for the same Map Type will still cause it to reset each time, which can take time.

3) Add the Map fragment programmatically, similar to what @cYrixmorten posted, but I do it from a background thread started at the end of my onResume(), which then waits 50ms and then runs it on the UI thread. This keeps it from hitting the UI thread right away, so that gives the Activity time to load and display; you should at least be on screen while the map is possibly choking everything up.

The catch here is that you want to create a new MapFragment instance only once per Activity, not every time the screen orientation is rotated. What I do is call "getFragmentManager().findFragmentById(R.id.mapContainer)", which will either give me the map fragment handle from last time, or a null if this is the first time (in which case I'll create the map fragment and do the FragmentManager.replace() ).

Wookie
  • 782
  • 8
  • 12
3

I have a "main" activity - and an activity with mapView. When that activity-with-mapView starts for a first time, it is really slow.

clocksmith's post gave me an idea to start initialization from main activity in a separate thread. And it really solves the problem.

Here is my code from "main" activity:

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 is never used - it's only for initialization purpose.

And here is a stack trace - just for 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

As we can see, it realy takes a lot of time...

Community
  • 1
  • 1
cVoronin
  • 1,341
  • 15
  • 21
3

For me it was way slower than 1sec beause i was using:

mapFragment.getMap();

Then i changed to:

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

Using the getMapAsync() it won't block the ui, so your activity will load before the map. It's still slow, but at least you can show a loading message.

Renato Probst
  • 5,914
  • 2
  • 42
  • 45
2

While this question has been here for years, the problem still persists without a clean and a straight-forward solution. I saw the Rx solution, but it no longer works, so here are my two cents:

  1. My launcher/splashscreen is not backed by a special activity - it is just the background XML with an image (no fancy animations, no routing on the background, just plain loading of resources by the system). This gives me the freedom to lock the main thread as the splash screen doesn't do anything interactive anyway.
  2. The SDK has changed and the function getMapAsync now actually requires you to call it on the main thread. So off-loading it to a Schedulers.io() thread no longer works.
  3. The API of Rx has changed and since version 3.0, null is not allowed to be sent as a "legal" output of an observable, thus I'm sending just true.

My kotlin solution lives in the main activity, annotated by Hilt as @AndroidEntryPoint in overridden function onCreate(savedInstanceState: Bundle?):

val mainThread = AndroidSchedulers.mainThread()
Observable.fromCallable {
    val mapView = MapView(this)
    mapView.onCreate(null)
    true
}.subscribeOn(mainThread).observeOn(mainThread).subscribe(
    // on success
    { Log.i("MAPS", "Initialized Google Maps.") },
    // on error
    { Log.w("MAPS", "Warming up of Google Maps failed: " + it.message) }
)
Michal
  • 15,429
  • 10
  • 73
  • 104
1

Similar to the other solutions here, but using RxJava + RxAndroid.

Just call this snippet from the launcher activity 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());
        }
    });
Joao Sousa
  • 4,055
  • 1
  • 26
  • 29
0

I had the same issue and the MapsInitializer trick didn't work for me.

The best solution to the problem, in my humble opinion, is to load by hand the Map Fragment as described from other users. It's not an hacky solution, you just have to deal yourself with the fragment instance

mMapFragment = MapFragment.newInstance();
fragmentManager.beginTransaction().replace(R.id.map_fragment_container, fragment, FRAGMENT_GOOGLEMAPS_TAG).commit();
Vahn84
  • 197
  • 1
  • 3
  • 6