添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

I'm making use of the new Android Google Maps API .

I create an activity which includes a MapFragment. In the activity onResume I set the markers into the GoogleMap object and then define a bounding box for the map which includes all of the markers.

This is using the following pseudo code:

LatLngBounds.Builder builder = new LatLngBounds.Builder();
while(data) {
   LatLng latlng = getPosition();
   builder.include(latlng);
CameraUpdate cameraUpdate = CameraUpdateFactory
   .newLatLngBounds(builder.build(), 10);
map.moveCamera(cameraUpdate);

The call to map.moveCamera() causes my application to crash with the following stack:

Caused by: java.lang.IllegalStateException: 
    Map size should not be 0. Most likely, layout has not yet 
    at maps.am.r.b(Unknown Source)
    at maps.y.q.a(Unknown Source)
    at maps.y.au.a(Unknown Source)
    at maps.y.ae.moveCamera(Unknown Source)
    at com.google.android.gms.maps.internal.IGoogleMapDelegate$Stub
        .onTransact(IGoogleMapDelegate.java:83)
    at android.os.Binder.transact(Binder.java:310)
    at com.google.android.gms.maps.internal.IGoogleMapDelegate$a$a
        .moveCamera(Unknown Source)
    at com.google.android.gms.maps.GoogleMap.moveCamera(Unknown Source)
    at ShowMapActivity.drawMapMarkers(ShowMapActivity.java:91)
    at ShowMapActivity.onResume(ShowMapActivity.java:58)
    at android.app.Instrumentation
        .callActivityOnResume(Instrumentation.java:1185)
    at android.app.Activity.performResume(Activity.java:5182)
    at android.app.ActivityThread
        .performResumeActivity(ActivityThread.java:2732)

If - instead of the newLatLngBounds() factory method I use newLatLngZoom() method then the same trap does not occur.

Is the onResume the best place to draw the markers onto the GoogleMap object or should I be drawing the markers and setting the camera position somewhere else?

You can use simple newLatLngBounds method in OnCameraChangeListener. All will be working perfectly and you don't need to calculate screen size. This event occurs after map size calculation (as I understand).

Example:

map.setOnCameraChangeListener(new OnCameraChangeListener() {
    @Override
    public void onCameraChange(CameraPosition arg0) {
        // Move camera.
        map.moveCamera(CameraUpdateFactory.newLatLngBounds(builder.build(), 10));
        // Remove listener to prevent position reset on camera move.
        map.setOnCameraChangeListener(null);
                This works because setOnCameraChangeListener is guaranteed to run at least once, after the Map has undergone layout? Is this future proof?
– Glenn Bech
                Aug 7, 2013 at 7:45
                @SteelRat Thats why I would be against using the solution. You never know when google change this, or even if it works that way on all android versions or devices.
– Glenn Bech
                Aug 7, 2013 at 21:03
                @SteelRat I agree, but it might be called more than once in the lifetime of the activity? I am just making the argument that even if it works in this case, its not a very elegant solution. I think the OnGlobalLayoutListener/Treelistener is a more correct way to go about this.
– Glenn Bech
                Aug 8, 2013 at 21:42
                maybe adding map.moveCamera(CameraUpdateFactory.scrollBy(1, 1)); after the code above will make the trick?
– M.Y.
                Aug 22, 2013 at 16:04

Those answers are just fine, but I would go for a different approach, a simpler one. If the method only works after the map is layed out, just wait for it:

map.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() {
    @Override
    public void onMapLoaded() {
        map.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 30));
                I found after much experimentation that I needed to implement a combination of this answer and Daniele's above. Sometimes the map had not been loaded yet, sometimes layout had not completed yet. I couldn't move the camera as desired until BOTH of those happened.
– RobP
                Jun 14, 2014 at 19:19
                Warning for this method, from the docs: "This event will not fire if the map never loads due to connectivity issues, or if the map is continuously changing and never completes loading due to the user constantly interacting with the map." (emphasis mine).
– stkent
                Dec 15, 2014 at 18:38
                This delays longer than necessary because it waits for the map tiles to load  at the wrong position before moving to the correct position.
– John Patterson
                Feb 17, 2015 at 12:46
                This is the place to move camera. I would suggest use map.animateCamera rather than map.moveCamera
– Bharat Dodeja
                May 4, 2016 at 13:44

OK I worked this out. As documented here that API can't be used pre-layout.

The correct API to use is described as:

Note: Only use the simpler method newLatLngBounds(boundary, padding) to generate a CameraUpdate if it is going to be used to move the camera after the map has undergone layout. During layout, the API calculates the display boundaries of the map which are needed to correctly project the bounding box. In comparison, you can use the CameraUpdate returned by the more complex method newLatLngBounds(boundary, width, height, padding) at any time, even before the map has undergone layout, because the API calculates the display boundaries from the arguments that you pass.

To fix the problem I calculated my screen size and provided the width and height to

public static CameraUpdate newLatLngBounds(
    LatLngBounds bounds, int width, int height, int padding)

This then allowed me to specify the bounding box pre-layout.

In my case I just had assumed a larger screen than was in effect, and needed to calculate the padding more carefully so it in fact was not too large. A much simpler reason. – rfay Oct 29, 2014 at 19:21 This doesn't crash but the width and height to be used are close to pure guesses, the exact area to be drawn is really unpredictable. – Vince Aug 15, 2019 at 23:04
// Pan to see all markers in view.
try {
    this.gmap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 50));
} catch (IllegalStateException e) {
    // layout not yet initialized
    final View mapView = getFragmentManager()
       .findFragmentById(R.id.map).getView();
    if (mapView.getViewTreeObserver().isAlive()) {
        mapView.getViewTreeObserver().addOnGlobalLayoutListener(
        new OnGlobalLayoutListener() {
            @SuppressWarnings("deprecation")
            @SuppressLint("NewApi")
            // We check which build version we are using.
            @Override
            public void onGlobalLayout() {
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
                    mapView.getViewTreeObserver()
                        .removeGlobalOnLayoutListener(this);
                } else {
                    mapView.getViewTreeObserver()
                        .removeOnGlobalLayoutListener(this);
                gmap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 50));

Catch IllegalStateException and use the global listener on the view instead. Setting the bound size in advance (pre-layout) using the other methods means you have to compute the size of THE VIEW, not the screen device. They only match if you go full screen with your map and do not use fragments.

Till now I've been using your little snippet, but it still fail... the correct answer is the first one :-) – cesards Feb 28, 2013 at 9:05 How/when it fail? never had an issue with this. The first answer just pass an hardcoded size to it as a fallback. "It works" as in "it doesn't crash anymore" but it is not doing the same thing. – Daniele Segato Oct 18, 2013 at 17:42 @andrea.spot I do not remember why I said simplier. I guess the accepted solution has been edited in the meanwhile. The current accepted solution assume your map take all the screen, which is not always true. If you do not fall in that case you either need to know the size of the map or use my method or something similar. – Daniele Segato Jul 17, 2015 at 14:49

This is my fix, just wait untill the map is loaded in that case.

final int padding = getResources().getDimensionPixelSize(R.dimen.spacing);    
try {
    mMap.animateCamera(CameraUpdateFactory.newLatLngBounds(pCameraBounds, padding));
} catch (IllegalStateException ise) {
    mMap.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() {
        @Override
        public void onMapLoaded() {
            mMap.animateCamera(CameraUpdateFactory.newLatLngBounds(pCameraBounds, padding));

The accepted answer wouldn't work for me because I had to use the same code in various places of my app.

Instead of waiting the camera to change, I created a simple solution based on Lee's suggestion of specifying the map size. This is in case your map is the size of the screen.

// Gets screen size
int width = getResources().getDisplayMetrics().widthPixels;
int height = getResources().getDisplayMetrics().heightPixels;
// Calls moveCamera passing screen size as parameters
map.moveCamera(CameraUpdateFactory.newLatLngBounds(builder.build(), width, height, 10));

Hope it helps someone else!

Adding and removing markers can be done pre-layout completion, but moving the camera cannot (except using newLatLngBounds(boundary, padding) as noted in the OP's answer).

Probably the best place to perform an initial camera update is using a one-shot OnGlobalLayoutListener as shown in Google's sample code, e.g. see the following excerpt from setUpMap() in MarkerDemoActivity.java:

// Pan to see all markers in view.
// Cannot zoom to bounds until the map has a size.
final View mapView = getSupportFragmentManager()
    .findFragmentById(R.id.map).getView();
if (mapView.getViewTreeObserver().isAlive()) {
    mapView.getViewTreeObserver().addOnGlobalLayoutListener(
    new OnGlobalLayoutListener() {
        @SuppressLint("NewApi") // We check which build version we are using.
        @Override
        public void onGlobalLayout() {
            LatLngBounds bounds = new LatLngBounds.Builder()
                    .include(PERTH)
                    .include(SYDNEY)
                    .include(ADELAIDE)
                    .include(BRISBANE)
                    .include(MELBOURNE)
                    .build();
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
              mapView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
            } else {
              mapView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 50));
                I get this error on orientation change. I also found this from sample code but this didn't work for me. I still get the same error.
– osrl
                Dec 12, 2016 at 13:09

The accepted answer is, as pointed out in the comments, a little hacky. After implementing it I noticed an above-acceptable amount of IllegalStateException logs in analytics. The solution I used is to add an OnMapLoadedCallback in which the CameraUpdate is performed. The callback is added to the GoogleMap. After your map fully loads the camera update will be performed.

This does cause the map to briefly show the zoomed out (0,0) view before performing the camera update. I feel that this is more acceptable than causing crashes or relying on undocumented behavior though.

In very rare cases, MapView layout is finished, but GoogleMap layout is not finished, ViewTreeObserver.OnGlobalLayoutListener itself can't stop the crash. I saw the crash with Google Play services package version 5084032. This rare case may be caused by the dynamic change of the visibility of my MapView.

To solve this problem, I embedded GoogleMap.OnMapLoadedCallback in onGlobalLayout(),

if (mapView.getViewTreeObserver().isAlive()) {
    mapView.getViewTreeObserver().addOnGlobalLayoutListener(
    new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        @SuppressWarnings("deprecation")
        public void onGlobalLayout() {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
                mapView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
            } else {
                mapView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            try {
                map.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 5));
            } catch (IllegalStateException e) {
                map.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() {
                    @Override
                    public void onMapLoaded() {
                        Log.d(LOG_TAG, "move map camera OnMapLoadedCallback");
                        map.moveCamera(CameraUpdateFactory
                            .newLatLngBounds(bounds, 5));
                Interesting: If I refactor mapView.getViewTreeObserver() into a local variable the following error occurs: "IllegalStateException: This ViewTreeObserver is not alive, call getViewTreeObserver() again". Did you try this?
– JJD
                Oct 6, 2014 at 13:47
                If mapView.getViewTreeObserver().isAlive() is happen to be false I'd put one more fallback for map.setOnMapLoadedCallback()
– Kurovsky
                Apr 11, 2016 at 17:17

I used different approach which works for recent versions of Google Maps SDK (9.6+) and based on onCameraIdleListener. As I see so far it's callback method onCameraIdle called always after onMapReady. So my approach looks like this piece of code (considering it put in Activity):

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // set content view and call getMapAsync() on MapFragment
@Override
public void onMapReady(GoogleMap googleMap) {
    map = googleMap;
    map.setOnCameraIdleListener(this);
    // other initialization stuff
@Override
public void onCameraIdle() {
       Here camera is ready and you can operate with it. 
       you can use 2 approaches here:
      1. Update the map with data you need to display and then set
         map.setOnCameraIdleListener(null) to ensure that further events
         will not call unnecessary callback again.
      2. Use local boolean variable which indicates that content on map
         should be updated

I've created a way to combine the two callbacks: onMapReady and onGlobalLayout into one single observable which will emit only when both the events have been triggered.

https://gist.github.com/abhaysood/e275b3d0937f297980d14b439a8e0d4a

It's in fact the cleanest way to solve this. I'm doing this for long time now and no error is returned in analytics even on 1k users per day app. +1 – Skyle Dec 16, 2019 at 14:08

Ok I'm facing same issue. I have my fragment with my SupportmapFragment, ABS, and navigation drawer. What I did was:

public void resetCamera() {
    LatLngBounds.Builder builderOfBounds = new LatLngBounds.Builder();
    // Set boundaries ...
    LatLngBounds bounds = builderOfBounds.build();
    CameraUpdate cu;
        cu = CameraUpdateFactory.newLatLngBounds(bounds,10);
        // This line will cause the exception first times 
        // when map is still not "inflated"
        map.animateCamera(cu); 
        System.out.println("Set with padding");
    } catch(IllegalStateException e) {
        e.printStackTrace();
        cu = CameraUpdateFactory.newLatLngBounds(bounds,400,400,0);
        map.animateCamera(cu);
        System.out.println("Set with wh");
    //do the rest...

And, by the way, I'm calling resetCamera() from onCreateView after inflating and before returning.
What this does is catch the exception first time (while map "gets a size" as a way of saying it...) and then, other times I need to reset the camera, map already has size and does it through padding.

Issue is explained in documentation, it says:

Do not change the camera with this camera update until the map has undergone layout (in order for this method to correctly determine the appropriate bounding box and zoom level, the map must have a size). Otherwise an IllegalStateException will be thrown. It is NOT sufficient for the map to be available (i.e. getMap() returns a non-null object); the view containing the map must have also undergone layout such that its dimensions have been determined. If you cannot be sure that this has occured, use newLatLngBounds(LatLngBounds, int, int, int) instead and provide the dimensions of the map manually.

I think it's a pretty decent solution. Hope it helps someone.

Is the exception raised in newLatLngBounds() or in animateCamera(), or in both? Will not the catch-branch lead to a new exception? – CoolMind Mar 4, 2019 at 9:36

If you need to wait for possible user interactions to start, use OnMapLoadedCallbackas described in the previous answers. But, if all you need is to provide a default location for the map, there is no need for any of the solutions outlined in those answers. Both MapFragmentand MapView can accept a GoogleMapOptions at start that can provide the default location all right. The only trick is not to include them directly in your layout because the system will call them without the options then but to initialize them dynamically.

Use this in your layout:

<FrameLayout
    android:id="@+id/map"
    android:name="com.google.android.gms.maps.SupportMapFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

and replace the fragment in your onCreateView():

GoogleMapOptions options = new GoogleMapOptions();
options.camera(new CameraPosition.Builder().target(location).zoom(15).build());
// other options calls if required
SupportMapFragment fragment = (SupportMapFragment) getFragmentManager().findFragmentById(R.id.map);
if (fragment == null) {
  FragmentTransaction transaction = getFragmentManager().beginTransaction();
  fragment = SupportMapFragment.newInstance(options);
  transaction.replace(R.id.map, fragment).commit();
  getFragmentManager().executePendingTransactions();
if (fragment != null)
  GoogleMap map = fragment.getMap();

Besides being faster to start, there will be no world map shown first and a camera move second. The map will start with the specified location directly.

Another approach would something like (Assuming your topmost view is a FrameLayout named rootContainer, even though it will work as long as you always choose your topmost container no matter which type or name it has):

((FrameLayout)findViewById(R.id.rootContainer)).getViewTreeObserver()
    .addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
        public void onGlobalLayout() {
            layoutDone = true;

Modifying your camera functions to only work if layoutDone is true will solve all your problems without having to add extra functions or wire up logic to the layoutListener handler.

I found this to work and be more simple than the other solutions:

private void moveMapToBounds(final CameraUpdate update) {
    try {
        if (movedMap) {
            // Move map smoothly from the current position.
            map.animateCamera(update);
        } else {
            // Move the map immediately to the starting position.
            map.moveCamera(update);
            movedMap = true;
    } catch (IllegalStateException e) {
        // Map may not be laid out yet.
        getWindow().getDecorView().post(new Runnable() {
            @Override
            public void run() {
                moveMapToBounds(update);

This tries the call again after layout has run. Could probably include a safety to avoid an infinite loop.

Why not just use something like this:

CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngBounds(builder.build(), padding);
try {
    map.moveCamera(cameraUpdate);
} catch (Exception e) {
    int width = getResources().getDisplayMetrics().widthPixels;
    int height = getResources().getDisplayMetrics().heightPixels;
    cameraUpdate = CameraUpdateFactory.newLatLngBounds(builder.build(), width, height, padding);
    map.moveCamera(cameraUpdate);
                Why newLatLngBounds(builder.build(), padding) is not inside try-catch? Does this exception occur in moveCamera()?
– CoolMind
                Mar 4, 2019 at 9:33

There is a helper class in the Google Maps repo that you can leverage - it waits for both the layout and map to be ready before notifying a callback with the GoogleMap:

The original source is here:

https://github.com/googlemaps/android-samples/blob/7ee737b8fd6d39c77f8b3716ba948e1ce3730e61/ApiDemos/java/app/src/main/java/com/example/mapdemo/OnMapAndViewReadyListener.java

There is a Kotlin implementation too:

https://github.com/googlemaps/android-samples/blob/master/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/OnMapAndViewReadyListener.kt

public class OnMapAndViewReadyListener implements OnGlobalLayoutListener, OnMapReadyCallback {
/** A listener that needs to wait for both the GoogleMap and the View to be initialized. */
public interface OnGlobalLayoutAndMapReadyListener {
    void onMapReady(GoogleMap googleMap);
private final SupportMapFragment mapFragment;
private final View mapView;
private final OnGlobalLayoutAndMapReadyListener devCallback;
private boolean isViewReady;
private boolean isMapReady;
private GoogleMap googleMap;
public OnMapAndViewReadyListener(
        SupportMapFragment mapFragment, OnGlobalLayoutAndMapReadyListener devCallback) {
    this.mapFragment = mapFragment;
    mapView = mapFragment.getView();
    this.devCallback = devCallback;
    isViewReady = false;
    isMapReady = false;
    googleMap = null;
    registerListeners();
private void registerListeners() {
    // View layout.
    if ((mapView.getWidth() != 0) && (mapView.getHeight() != 0)) {
        // View has already completed layout.
        isViewReady = true;
    } else {
        // Map has not undergone layout, register a View observer.
        mapView.getViewTreeObserver().addOnGlobalLayoutListener(this);
    // GoogleMap. Note if the GoogleMap is already ready it will still fire the callback later.
    mapFragment.getMapAsync(this);
@Override
public void onMapReady(GoogleMap googleMap) {
    // NOTE: The GoogleMap API specifies the listener is removed just prior to invocation.
    this.googleMap = googleMap;
    isMapReady = true;
    fireCallbackIfReady();
@SuppressWarnings("deprecation")  // We use the new method when supported
@SuppressLint("NewApi")  // We check which build version we are using.
@Override
public void onGlobalLayout() {
    // Remove our listener.
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
        mapView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
    } else {
        mapView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
    isViewReady = true;
    fireCallbackIfReady();
private void fireCallbackIfReady() {
    if (isViewReady && isMapReady) {
        devCallback.onMapReady(googleMap);

As stated in OnCameraChangeListener() is deprecated, setOnCameraChangeListener is now deprecated. So you should replace it with one of three mtehods:

  • GoogleMap.OnCameraMoveStartedListener
  • GoogleMap.OnCameraMoveListener
  • GoogleMap.OnCameraIdleListener
  • In my case, I used OnCameraIdleListener and inside I removed it because it was invoked again and again on any movement.

    googleMap.setOnCameraIdleListener {
        googleMap.setOnCameraIdleListener(null) // It removes the listener.
        googleMap.moveCamera(track)
        googleMap.cameraPosition
        clusterManager!!.cluster()
        // Add another listener to make ClusterManager correctly zoom clusters and markers.
        googleMap.setOnCameraIdleListener(clusterManager)
    

    UPDATE

    I removed googleMap.setOnCameraIdleListener in my project, because it wasn't called sometimes when a map was shown, but retained googleMap.setOnCameraIdleListener(clusterManager).

    @ArslanMaqbool, thanks! I am in process now and will be thankful to you if you share your variant. – CoolMind Dec 19, 2018 at 15:57

    I had the same error trying to call mMap.animateCamera. I solved it by calling setOnMapLoadedCallback callback in my onMapReady function as shown below

    public void onMapReady(GoogleMap googleMap) {
            mMap = googleMap;
       // other codes go there
        mMap.setOnMapLoadedCallback(() -> {
                //Your code where exception occurs goes here...
    

    This should work fine.

    Thanks for contributing an answer to Stack Overflow!

    • Please be sure to answer the question. Provide details and share your research!

    But avoid

    • Asking for help, clarification, or responding to other answers.
    • Making statements based on opinion; back them up with references or personal experience.

    To learn more, see our tips on writing great answers.

    Determining suitable zoom level based on a set of locations on Google Map Android API v2 See more linked questions