Darsen Lu Darsen Lu - 5 months ago 154
Android Question

MapView in ListView hangs after resetting marker location or moving camera (Google Map Android API v2)

I have a MapView (lite mode) inside a ListView. This has been done following the approach in the following example:
https://github.com/googlemaps/android-samples/blob/master/ApiDemos/app/src/main/java/com/example/mapdemo/LiteListDemoActivity.java

The XML code for this MapView looks like this:

<com.google.android.gms.maps.MapView android:id="@+id/mapLocation"
android:layout_width="match_parent"
android:layout_height="@dimen/google_map_height"
map:liteMode="true"
map:mapType="none"
android:layout_below="@+id/textTitle"
android:visibility="gone"
android:padding="@dimen/image_padding"
android:apiKey="AIzaSyBDOINR0EcGYu9RDUY1******"/>


When the map was first loaded, I was able to place a marker and set the camera properly. Everything looks normal and scrolling of the ListView appears smooth. The MapView is also able to generate onMapClick() events when it was clicked by the user. However, later on when I attempted to move the marker or reset the camera view with the following code, the MapView hangs:

// Update GUI
boolean hasLocation = (targetLatLng != null);
MapView mapView = (MapView) view.findViewById(R.id.mapLocation);
if (mapView != null) {
if (hasLocation) {
mapView.setVisibility(View.VISIBLE);
if (listViewGoogleMap != null) {
listViewGoogleMap.moveCamera(
CameraUpdateFactory.newLatLngZoom(targetLatLng, 13f));
// if (markerListViewMap != null)
// markerListViewMap.setPosition(targetLatLng);
// else
// markerListViewMap = listViewGoogleMap.addMarker(new MarkerOptions()
// .position(targetLatLng));
} else {
mapView.setVisibility(View.GONE);
}
}
}


Note that I have commented out the Marker.setPosition() part, which had been used to move a marker on the map. If I comment out both the moveCamera() statement and the Marker.setPosition() part, then hanging no longer occurs.

When hanging occurs, the MapView no longer responds to mouse clicks (no onMapClick() events are trigerred). The scrolling of the ListView becomes noticeably sluggish. In addition, lots of warnings regarding CPU and memory consumption appears in the debug log:

06-05 10:35:55.731 11672-11672/cards.myb.mybusinesscards D/CardEditorMapRow: Longitude/latitude obtained from dialog : (23.0238082, 120.2276839)
06-05 10:35:55.731 11672-11672/cards.myb.mybusinesscards D/CardEditorMapRow: Address obtained from dialog:
06-05 10:35:56.557 11672-11683/cards.myb.mybusinesscards I/art: Background sticky concurrent mark sweep GC freed 626(33KB) AllocSpace objects, 5(13MB) LOS objects, 22% free, 46MB/59MB, paused 21.757ms total 32.972ms
06-05 10:35:57.653 11672-11678/cards.myb.mybusinesscards W/art: Suspending all threads took: 11.688ms
06-05 10:35:58.155 11672-11678/cards.myb.mybusinesscards W/art: Suspending all threads took: 13.849ms
06-05 10:35:58.410 11672-11683/cards.myb.mybusinesscards I/art: Background partial concurrent mark sweep GC freed 701(35KB) AllocSpace objects, 10(16MB) LOS objects, 26% free, 43MB/59MB, paused 5.653ms total 28.354ms
06-05 10:35:58.551 11672-11683/cards.myb.mybusinesscards I/art: Background sticky concurrent mark sweep GC freed 587(32KB) AllocSpace objects, 5(13MB) LOS objects, 22% free, 46MB/59MB, paused 10.637ms total 20.616ms
06-05 10:36:00.283 11672-11683/cards.myb.mybusinesscards I/art: Background partial concurrent mark sweep GC freed 791(40KB) AllocSpace objects, 11(19MB) LOS objects, 26% free, 43MB/59MB, paused 9.291ms total 34.966ms
06-05 10:36:01.802 11672-11683/cards.myb.mybusinesscards I/art: Background partial concurrent mark sweep GC freed 833(42KB) AllocSpace objects, 11(19MB) LOS objects, 26% free, 43MB/59MB, paused 5.594ms total 31.557ms
06-05 10:36:04.676 11672-11678/cards.myb.mybusinesscards W/art: Suspending all threads took: 18.995ms


A workaround would be to recreate the entire activity. (Hiding and showing the MapView again does not help) I appreciate any comments!

Edit 6/6/2016:
After some further debugging I realized this was due to some sort of infinite loop related to the MapView component. After adding a statement to show debug message each time the MapView's ViewTreeObserver calls OnGlobalLayout(), I see thousands of messages (and counting...) like this:

06-06 06:39:48.532 11208-11208/cards.myb.mybusinesscards D/CardEditorMapRow:
onGlobalLayout() count=2681
06-06 06:39:48.581 11208-11208/cards.myb.mybusinesscards D/CardEditorMapRow:
onGlobalLayout() count=2682
06-06 06:39:48.658 11208-11208/cards.myb.mybusinesscards D/CardEditorMapRow:
onGlobalLayout() count=2683
06-06 06:39:48.688 11208-11219/cards.myb.mybusinesscards I/art: Background
sticky concurrent mark sweep GC freed 511(27KB) AllocSpace objects, 4(10MB)
LOS objects, 16% free, 53MB/64MB, paused 10.956ms total 23.201ms
06-06 06:40:04.141 11208-11208/cards.myb.mybusinesscards D/CardEditorMapRow:
onGlobalLayout() count=2684
06-06 06:40:04.183 11208-11208/cards.myb.mybusinesscards D/CardEditorMapRow:
onGlobalLayout() count=2685
06-06 06:40:04.212 11208-11208/cards.myb.mybusinesscards D/CardEditorMapRow:
onGlobalLayout() count=2686

Answer

Sorry for the ill-posted question. It turns out that the problem was due to the fact that I have multiple different types of layouts inside my ListView.

For this reason, each layout type is handled by a different class. My ListAdapter passes getView() calls to different handler classes for each layout type. For example, when the layout is one that consists of a MapView, the following function handles that:

public class MapRow {
    View view = null;

    public View getView(ViewGroup parent, Context context)
    {
        // Create view if it does not exist yet
        if(view == null) {
            view = LayoutInflater.from(context).inflate(R.layout.map_row, parent, false);
            initializeMap();

            view.setTag("map");
        }
        return view;
    }
}

The strategy had worked well for layouts that does not contain images. But when I have a MapView inside, once the map gets updated after its first creation, a cycling between MapView's invalidate() function to request global layout, and the outside ListView's getView() call occurs. It is this infinite loop that has caused the GUI slow-down.

Suggestion to everyone: when overwriting the getView() function, follow the convention of re-using "convertView" like this example. Otherwise bad things could happen.

The solution to my own problem is to replace ListView with a simple LinearLayout, with dynamic view creation. There is really no advantage of using a ListView if each item inside is different, and recycled view cannot be reused.