I am working on an application that uses v2 MapFragment , and I come across very strange behavior. I created a subclass of MapFragment to handle specific user behavior (handling Marker s, adjusting menu options, etc.), and on first loading everything works beautifully. Then I insert a new fragment into my activity by clicking on the custom MapFragment on the back. However, when I return the card from the back, everything becomes strange; panning the map becomes extremely slow (we say ~ 1 FPS), both for manually dragging / zooming and for animations caused by clicking on the contacts. And then, if I interact with any part of the overflow menu, even just opening it and dropping it again, the lag is immediately cleared. It seems that nothing fixes (closing / reopening the application); interacting with menu items without overflowing, and the navigation box helps nothing. I have never seen anything like this, and I can not find anyone who has previously described a similar problem. Any ideas, suggestions and / or corrections would be welcome.
To answer a few questions before they are asked:
- Yes, I call the
super versions of all lifecycle methods that I override ( onCreate() , onCreateView() [I also return what super returns for this], and onDestroyView() ). - As far as I can tell, I'm clearing the map correctly. Each time I update contacts, I call
remove() for each of them, and then clean() on the map itself, and also do it all in onDestroyView() .
And finally, for reference, this is the code that adds a new snippet:
getFragmentManager().beginTransaction().replace(R.id.main_content_container, new JoinGroupFragment()).addToBackStack(null).commit();
And when I am done with this, I just call:
getFragmentManager().popBackStack();
EDIT: I'm not sure how much this will help, but here's a custom MapFragment :
public class CustomMapFragment extends MapFragment { private static final String DIALOG_TAG = "CUSTOM_MAP_FRAGMENT_DIALOG"; private static final int DEFAULT_ZOOM = 14; private static final int MARKER_ZOOM = 15; private static final int DEFAULT_PADDING = 80; private static final int ORANGE_THRESHOLD_MINUTES = 7; private static final int BLUE_THRESHOLD_MINUTES = 20; public static final String KEY_GROUP_NAME = "GROUP_NAME"; public static final String KEY_GROUP_ID = "GROUP_ID"; private TextView mGroupNameOverlay; private GoogleMap mMap; private ArrayList<Marker> mMarkers; private Marker mSelectedMarker; private ArrayList<Group> mAllGroups; private Group mCurrentGroup; private ArrayList<Location> mAllLocations; private boolean mMapReady; private String mUsername; private boolean mCenterOnUser; public CustomMapFragment() { } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); mMarkers = new ArrayList<>(); mAllLocations = new ArrayList<>(); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); mUsername = prefs.getString(PreferenceUtils.KEY_USERNAME, null); mCenterOnUser = prefs.getBoolean(PreferenceUtils.KEY_CENTER_ON_ME, false); mSelectedMarker = null; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { ViewGroup view = (ViewGroup)super.onCreateView(inflater, container, savedInstanceState); if (view != null) { // View should never be null; MapFragments have a FrameLayout as their top level parent mGroupNameOverlay = (TextView)inflater.inflate(R.layout.group_name_overlay, view, false); view.addView(mGroupNameOverlay); } Bundle results = ((MainActivity)getActivity()).getFragmentResults(); if (results != null) { String name = results.getString(KEY_GROUP_NAME); String id = results.getString(KEY_GROUP_ID); if (!StringUtils.isNullOrEmpty(name) && !StringUtils.isNullOrEmpty(id)) { mCurrentGroup = new Group(name, id); mAllGroups.add(mCurrentGroup); } } if (mCurrentGroup != null) { updateGroupNameOverlay(mCurrentGroup.getGroupName()); } getMapAsync(new OnMapReadyCallback() { @Override public void onMapReady(GoogleMap googleMap) { mMap = googleMap; mMap.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() { @Override public boolean onMarkerClick(Marker marker) { mSelectedMarker = marker; getActivity().invalidateOptionsMenu(); return false; } }); mMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() { @Override public void onMapClick(LatLng latLng) { mSelectedMarker = null; getActivity().invalidateOptionsMenu(); } }); populateMap(true, false); } }); GetGroupsRequest request = new GetGroupsRequest(); request.setListener(new GetGroupsRequestListener()); RequestProcessor.getInstance(getActivity()).queueRequest(request); return view; } @Override public void onDestroyView() { mSelectedMarker = null; for (Marker marker : mMarkers) { marker.remove(); } mMarkers.clear(); mMap.clear(); mMap = null; mMapReady = false; super.onDestroyView(); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { if (mSelectedMarker == null) { inflater.inflate(R.menu.menu_map, menu); } else { inflater.inflate(R.menu.menu_marker, menu); } super.onCreateOptionsMenu(menu, inflater); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.map_menu_refresh_pins: performLocationsRequest(false); return true; case R.id.map_menu_recenter_zoom: populateMap(true, true); return true; case R.id.map_menu_select_group: DialogFragment selectDialog = new DialogFragment() { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { String[] groups = new String[mAllGroups.size()]; for (int i = 0; i < groups.length; i++) { groups[i] = mAllGroups.get(i).getGroupName(); } return new AlertDialog.Builder(getActivity()) .setItems(groups, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (!mAllGroups.get(which).equals(mCurrentGroup)) { mCurrentGroup = mAllGroups.get(which); updateGroupNameOverlay(mCurrentGroup.getGroupName()); performLocationsRequest(true); } } }) .create(); } }; selectDialog.show(getFragmentManager(), DIALOG_TAG); return true; case R.id.map_menu_join_group: getFragmentManager().beginTransaction().replace(R.id.main_content_container, new JoinGroupFragment()).addToBackStack(null).commit(); return true; case R.id.map_menu_create_group: CreateDialogFragment createDialog = new CreateDialogFragment(); createDialog.show(getFragmentManager(), DIALOG_TAG); return true; case R.id.map_marker_zoom: if (mSelectedMarker != null) { mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(mSelectedMarker.getPosition(), MARKER_ZOOM)); } return true; default: return super.onOptionsItemSelected(item); } } private void performLocationsRequest(boolean autoZoom) { GetLocationsRequest request = new GetLocationsRequest(mCurrentGroup.getGroupId()); request.setListener(new GetLocationsRequestListener(autoZoom)); RequestProcessor.getInstance(getActivity()).queueRequest(request); } private void updateGroupNameOverlay(final String groupName) { if (mGroupNameOverlay != null) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { if (groupName == null) { mGroupNameOverlay.setText(R.string.map_group_overlay_no_group); } else { mGroupNameOverlay.setText(getString(R.string.map_group_overlay_group, groupName)); } } }); } } private void populateMap(boolean zoom, boolean animate) { if (!mMapReady) { mMapReady = true; } else { CameraUpdate update = null; mSelectedMarker = null; for (Marker marker : mMarkers) { marker.remove(); } mMarkers.clear(); mMap.clear(); if (mAllLocations.size() == 1) { Location location = mAllLocations.get(0); mMarkers.add(addMarker(location)); update = CameraUpdateFactory.newLatLngZoom(new LatLng(location.getLatitude(), location.getLongitude()), DEFAULT_ZOOM); } else if (mAllLocations.size() > 1) { LatLngBounds.Builder builder = new LatLngBounds.Builder(); for (Location location : mAllLocations) { mMarkers.add(addMarker(location)); if (mCenterOnUser) { if (location.getUsername().equals(mUsername)) { update = CameraUpdateFactory.newLatLngZoom(new LatLng(location.getLatitude(), location.getLongitude()), DEFAULT_ZOOM); } } else { builder.include(new LatLng(location.getLatitude(), location.getLongitude())); } } if (!mCenterOnUser) { update = CameraUpdateFactory.newLatLngBounds(builder.build(), DEFAULT_PADDING); } } if (update != null && zoom) { if (animate) { mMap.animateCamera(update); } else { mMap.moveCamera(update); } } } } private Marker addMarker(Location location) { String timestamp; long minutesOld = (new Date().getTime() - location.getLastReported()) / 60000; float hue = BitmapDescriptorFactory.HUE_RED; if (minutesOld < 1) { timestamp = getString(R.string.map_timestamp_just_now); } else if (minutesOld < 2) { timestamp = getString(R.string.map_timestamp_one_minute); } else { timestamp = getString(R.string.map_timestamp_n_minutes, minutesOld); if (minutesOld >= ORANGE_THRESHOLD_MINUTES) { hue = BitmapDescriptorFactory.HUE_ORANGE; } if (minutesOld >= BLUE_THRESHOLD_MINUTES) { hue = BitmapDescriptorFactory.HUE_BLUE; } } LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude()); return mMap.addMarker(new MarkerOptions() .position(latLng) .icon(BitmapDescriptorFactory.defaultMarker(hue)) .title(location.getUsername()) .snippet(timestamp)); } private class GetGroupsRequestListener extends RequestListener<GetGroupsResponse> { public GetGroupsRequestListener() { super(getActivity()); } @Override protected void onRequestComplete(GetGroupsResponse response) { mAllGroups = response.getGroups(); if (mAllGroups.size() > 0) { if (mCurrentGroup == null) { mCurrentGroup = mAllGroups.get(0); updateGroupNameOverlay(mCurrentGroup.getGroupName()); } performLocationsRequest(true); } } } private class GetLocationsRequestListener extends RequestListener<GetLocationsResponse> { private boolean mmAutoZoom; public GetLocationsRequestListener(boolean autoZoom) { super(getActivity()); mmAutoZoom = autoZoom; } @Override protected void onRequestComplete(GetLocationsResponse response) { mAllLocations = response.getLocations(); getActivity().runOnUiThread(new Runnable() { @Override public void run() { populateMap(mmAutoZoom, false); } }); } } }
PS I understand that this is probably not the best practice for creating a capture capture type and for this to impose your own overlay, but for what it costs, I tried to comment on this part, and this did not solve the problem, so I doubt that this is due .