Google Maps v2 MapFragment is extremely behind backtracking

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 .

+5
source share
1 answer

I had a similar problem. Let's say I have a FragmentA with a child of SupportMapFragment. Through the navigation box menu, I call up the dialog that will be displayed. At this point, the child map in FragmentA goes into "background mode" - it is updated about twice a second (I have animated markers on this map, so this behavior is well known to me). This "background" mode is turned on, showing any element with an overlay, i.e. dialog box or menu AppBar on and off when this item is hidden.

In the dialog box, I have a list with elements that trigger the fragment to replace the transaction on click. At this point, FragmentA is replaced by FragmentB. Here is the code snippet of OnClickListener:

 final SmartFragment fragment = new SmartFragment(); FragmentManager supportFragmentManager = getActivity().getSupportFragmentManager(); supportFragmentManager .beginTransaction() .replace(R.id.fragment_container, fragment, SmartFragment.FRAGMENT_TAG) .addToBackStack(null) .commit(); dismiss(); 

But after I return to FragmentA from the backstack, the map that was saved remains in this "background", even if the dialog no longer exists. Moving the dissmiss () operator before a transaction does not help, it looks like the dialog is not rejected until all the code has been executed. Thus, the only reasonable solution (except for turning the “background mode” directly on the map fragment that I cannot find how to do this) is to postpone the fragment transaction until the dialog is really dismissed. As I suggested earlier, this only happens when all the code in OnClick is executed (which is part of the dialog), so we need to make the transaction not in the instance that was declared, but a little later, placing it in Runnable:

 dismiss(); final SmartFragment fragment = new SmartFragment(); Handler handler = new Handler(); final FragmentManager supportFragmentManager = getActivity().getSupportFragmentManager(); handler.post(new Runnable() { @Override public void run() { supportFragmentManager .beginTransaction() .replace(R.id.fragment_container, fragment, StopsSmartFragment.FRAGMENT_TAG) .addToBackStack(null) .commit(); } }); 

To do this, we need to make our fragment and support the final FragmentManager. Now everything works, the background map is disabled, and if you return to FragmentA from backstack, the map works as it should.

If someone has an idea how to directly turn off "background mode" in a map fragment, tell me, I will be glad to hear.

+2
source

Source: https://habr.com/ru/post/1211222/


All Articles