We're trying to track down a memory leak happening on the GoogleMap in our Android app, which ends in an OOM after about 40-50 device rotations. The map gets set around 3500 markers.
The App has a minSDK of 9 and therefore using the SupportMapFragment from the V4 Support Library.
We've tried multiple things including:
Caching the LatLng's
Caching CameraUpdates
Removing markers from map
Removing listeners from map
Removing all listeners, markers etc so that we just have a plain map
Updating Google Play Services library
Updating Support library
Analyzing the memory dump in MAT shows that we accumulate lots of instances of
com.google.android.gms.location.internal.LocationClientHelper$ListenerTransport
which we have no clue where they are coming from.
Anyone has an idea on what could be the cause of this memory leak?
The following code has already all markes and listeners removed and still leaks. First the base class:
public abstract class BaseMapFragment extends Fragment {
public static final int MENU_ITEM_ID_SEARCH= 102;
public static final int MENU_ITEM_ID_SHOW_LIST= 100;
public static final int ZOOM_LEVEL_DEFAULT= 14;
private static final String SAVED_INSTANCE_LATITUDE= "savedLatitude";
private static final String SAVED_INSTANCE_LONGITUDE= "savedLongitutde";
private static final String SAVED_INSTANCE_ZOOM= "savedZoom";
protected static final String CLASSTAG= BaseMapFragment.class.getSimpleName();
private GoogleMap mMap;
private CameraUpdate mResumeCameraUpdate= null;
private double mSavedLatitude;
private double mSavedLongitude;
private float mSavedZoom;
private static View mView;
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mMap != null) {
outState.putDouble(SAVED_INSTANCE_LATITUDE, mMap.getCameraPosition().target.latitude);
outState.putDouble(SAVED_INSTANCE_LONGITUDE, mMap.getCameraPosition().target.longitude);
outState.putFloat(SAVED_INSTANCE_ZOOM, mMap.getCameraPosition().zoom);
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
if (savedInstanceState != null) {
mSavedLatitude= savedInstanceState.getDouble(SAVED_INSTANCE_LATITUDE, Constants.EXTRA_VALUE_NONE);
mSavedLongitude= savedInstanceState.getDouble(SAVED_INSTANCE_LONGITUDE, Constants.EXTRA_VALUE_NONE);
mSavedZoom= savedInstanceState.getFloat(SAVED_INSTANCE_ZOOM, Constants.EXTRA_VALUE_NONE);
}
if (mView != null) {
ViewGroup parent= (ViewGroup) mView.getParent();
if (parent != null)
parent.removeView(mView);
}
try {
mView= inflater.inflate(R.layout.map_layout, container, false);
} catch (InflateException e) {
/* map is already there, just return view as it is */
}
return mView;
}
protected GoogleMap initializeMap() {
if (mMap != null) {
if (mSavedLatitude != Constants.EXTRA_VALUE_NONE && mSavedLatitude != 0.0) {
mResumeCameraUpdate= Context.getCamUpdate(mSavedZoom, mSavedLatitude, mSavedLongitude);
} else {
mResumeCameraUpdate= Context.getCamUpdate(mMap.getCameraPosition().zoom, mMap.getCameraPosition().target.latitude, mMap.getCameraPosition().target.longitude);
}
}
SupportMapFragment mapFragment= (SupportMapFragment) getActivity().getSupportFragmentManager().findFragmentById(R.id.map);
if (mapFragment == null) {
mapFragment= (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.map);
if (mapFragment == null) {
MapsInitializer.initialize(getActivity());
mapFragment= SupportMapFragment.newInstance();
mMap= mapFragment.getMap();
} else {
mMap= mapFragment.getMap();
}
} else {
mMap= mapFragment.getMap();
}
// check if map is created successfully or not
if (mMap == null) {
Toast.makeText(getActivity().getApplicationContext(), R.string.map_create_unable, Toast.LENGTH_SHORT).show();
} else {
mMap.setMyLocationEnabled(true);
mMap.setOnMyLocationButtonClickListener(new OnMyLocationButtonClickListener() {
#Override
public boolean onMyLocationButtonClick() {
if (mMap.getMyLocation() != null) {
CameraUpdate newLatLngZoom= Context.getCamUpdate(ZOOM_LEVEL_DEFAULT, mMap.getMyLocation());
mMap.animateCamera(newLatLngZoom);
} else {
Toast.makeText(getActivity().getApplicationContext(), R.string.map_location_services_disabled, Toast.LENGTH_SHORT).show();
}
return true;
}
});
}
return mMap;
}
}
Subclass
public class MySupportMapFragment extends BaseMapFragment {
private LinearLayout mStaoButtonsLayout;
private ToggleButton mStaoButton;
private ToggleButton mGasStaoButton;
private Boolean mInitialLocationChange;
private CameraUpdate mResumeCameraUpdate;
private GoogleMap mMap;
private double mBundleLatitude;
private double mBundleLongitude;
#Override
public void addRequiredModelClasses(LinkedHashSet<Class<? extends ComergeModel<?>>> set) {
set.add(AboModel.class);
set.add(StationModel.class);
super.addRequiredModelClasses(set);
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putDouble(BUNDLE_EXTRA_CENTER_LATITUDE, mBundleLatitude);
outState.putDouble(BUNDLE_EXTRA_CENTER_LONGITUDE, mBundleLongitude);
}
#Override
public void onActivityCreated(final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(showSearchButton());
final StationModel stationModel= getContext().getModel(StationModel.class);
mStaoButtonsLayout= (LinearLayout) getActivity().findViewById(R.id.mapStaoButtons);
mStaoButtonsLayout.setVisibility(View.VISIBLE);
mStaoButton= (ToggleButton) mStaoButtonsLayout.findViewById(R.id.staoButton);
mStaoButton.setChecked(stationModel.isStationButtonChecked());
mGasStaoButton= (ToggleButton) mStaoButtonsLayout.findViewById(R.id.gasStaoButton);
mGasStaoButton.setChecked(stationModel.isGasStationButtonChecked());
mMap= initializeMap();
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
addSearchButton(menu);
}
}
I had a similar problem before. I added following code to solve my problem:
#Override
public void onDestroy() {
if (mMap != null) {
mMap.setMyLocationEnabled(false);
}
}
It seems that LocationClientHelper$ListenerTransport is related to setMyLocationEnabled(). I had to unregister some callbacks to prevent memory leak.
Related
In my Android application I have an Activity which shows list of map fragments, following is the code for it:
public class TTMapFragment extends TTFragment {
private final static String LOG_AREA = "TTMapFragment";
private SupportMapFragment mMapFragment;
private Bundle mBundle;
protected int leftPosition;
protected int topPosition;
private GoogleMap googleMap;
public GoogleMap getMap() {
if (googleMap == null) {
final CountDownLatch latch = new CountDownLatch(1);
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
getSupportMapFragment().getMapAsync(new OnMapReadyCallback() {
#Override
public void onMapReady(GoogleMap googleMap) {
TTMapFragment.this.googleMap = googleMap;
latch.countDown();
}
});
}
});
try {
latch.await(2, TimeUnit.SECONDS);
} catch (InterruptedException e) {}
}
return googleMap;
}
public int getMapViewResourceID(return R.id.trip_small_map;);
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View inflatedView = super.onCreateView(inflater, container, savedInstanceState);
MapsInitializer.initialize(getActivity());
setUpMapIfNeeded();
mMapFragment = getSupportMapFragment();
mMapFragment.onCreate(mBundle);
mMapFragment.getMapAsync(new OnMapReadyCallback() {
#Override
public void onMapReady(GoogleMap googleMap) {
TTMapFragment.this.googleMap = googleMap;
int width = 0;
int height = 0;
if (mMapFragment.getView() != null) {
width = mMapFragment.getView().getWidth();
height = mMapFragment.getView().getHeight();
}
// code to get data for map and add markers on the map
//Then I call this
CameraUpdate cu = CameraUpdateFactory.newLatLngBounds(bound, width, height, padding);
try {
getMap().moveCamera(cu);
} catch (IllegalStateException e) {
// here IO get this exception : view size too small
TTLog.d(LOG_AREA, "handled view size too small issue");
}
}
});
return inflatedView;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBundle = savedInstanceState;
}
public SupportMapFragment getSupportMapFragment() {
setUpMapIfNeeded();
return mMapFragment;
}
private void setUpMapIfNeeded() {
if (mMapFragment == null) {
mMapFragment = ((SupportMapFragment) getChildFragmentManager()
.findFragmentById(getMapViewResourceID()));
}
}
public void clearMap(){
getMap().clear();
System.gc();
TTLog.d(LOG_AREA, "clearMap done");
}
#Override
public void onDestroy() {
TTLog.d(LOG_AREA, "onDestroy");
clearMap();
super.onDestroy();
}
#Override
public void onLowMemory() {
super.onLowMemory();
System.gc();
}
}
When I call getMap().moveCamera(cu) I get a IllegalStateException: View size too small after padding. Can Anyone suggest me when can I call moveCamera() function?
Another thing I observed is, width and height are zero for the first fragment but after that It gives me non zero width and height.
I'm working with Google Maps. I am able to successfully create and can show a Google Map. Now I want to add CameraUpdate(latitude, longitude). I googled and I found some source code but I'm getting a NullPointerException.
The LogCat error message is:
java.lang.NullPointerException: CameraUpdateFactory is not initialized
This is my source. What am I doing wrong?
public class StradaContact extends Fragment {
public final static String TAG = StradaContact.class.getSimpleName();
private MapView mMapView;
private GoogleMap mMap;
private Bundle mBundle;
double longitude = 44.79299800000001, latitude = 41.709981;
public StradaContact() {
}
public static StradaContact newInstance() {
return new StradaContact();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.strada_contact, container,false);
mMapView = (MapView) rootView.findViewById(R.id.pointMap);
mMapView.onCreate(mBundle);
setUpMapIfNeeded(rootView);
return rootView;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBundle = savedInstanceState;
}
private void setUpMapIfNeeded(View inflatedView) {
if (mMap == null) {
mMap = ((MapView) inflatedView.findViewById(R.id.pointMap)).getMap();
if (mMap != null) {
CameraUpdate center = CameraUpdateFactory.newLatLng(new LatLng(latitude, longitude));
CameraUpdate zoom = CameraUpdateFactory.zoomTo(15);
mMap.moveCamera(center);
mMap.animateCamera(zoom);
}
}
}
#Override
public void onResume() {
super.onResume();
mMapView.onResume();
}
#Override
public void onPause() {
super.onPause();
mMapView.onPause();
}
#Override
public void onDestroy() {
mMapView.onDestroy();
super.onDestroy();
}
}
<com.google.android.gms.maps.MapView
android:id="#+id/pointMap"
android:name="com.google.android.gms.maps.MapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.google.android.gms.maps.MapFragment" />
Try this in "onCreate":
try {
MapsInitializer.initialize(this);
} catch (GooglePlayServicesNotAvailableException e) {
e.printStackTrace();
}
Try with following method initialize google map api before using
MapsInitializer.initialize(getActivity());
I've the next problem, I'm trying to show some markers in a Google Map, but only show the first marker, not the others. It don't throw any exception, but only shows the first marker (the phone position).
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
context = getActivity().getApplicationContext();
v = inflater.inflate(R.layout.fragment_map, container, false);
Bundle b = getArguments();
receiveExtraData(b);
MapsInitializer.initialize(getActivity());
mMapView = (MapView) v.findViewById(R.id.map);
mMapView.onCreate(mBundle);
setUpMapIfNeeded(v);
return v;
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
listener = (FragmentsListener) activity;
} catch (ClassCastException e) {}
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBundle = savedInstanceState;
}
private void setUpMapIfNeeded(View inflatedView) {
if (mMap == null) {
mMap = ((MapView) inflatedView.findViewById(R.id.map)).getMap();
if (mMap != null) {
setUpMap();
}
}
}
private void setUpMap() {
GPSPoint point = new GPSPoint(context);
LatLng latLng = point.getCurrentPositionLatLng();
MarkerOptions phoneMarker = new MarkerOptions().position(latLng).title("Marker");
mMap.addMarker(phoneMarker);
phoneMarker.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN));
CameraPosition cameraPosition = new CameraPosition.Builder().target(latLng).zoom(20).build();
mMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
addDevicesMarkers(connectedDevices);
}
#Override
public void onResume() {
super.onResume();
mMapView.onResume();
}
#Override
public void onPause() {
super.onPause();
mMapView.onPause();
}
#Override
public void onDestroy() {
mMapView.onDestroy();
super.onDestroy();
}
private void receiveExtraData(Bundle b){
connectedDevices = b.getParcelableArrayList(BundlesKeys.BLUETOOTH_DEVICES_ARRAY);
}
private void getDevicePosition(BluetoothDevice bDevice){
listener.getDeviceLocation(bDevice);
}
private void addDevicesMarkers(ArrayList<BluetoothDevice> list){
if (!list.isEmpty()){
BluetoothDevice device = list.get(0);
getDevicePosition(device);
}
}
public void addMarker(LatLng pos){
MarkerOptions deviceMarker = new MarkerOptions().position(pos).title("Device");
mMap.addMarker(deviceMarker);
}
Somebody knows the fix?? Somebody knows how to program a listener for devices location events?
You just have to use mMap.addMarker() as many times as the number of markers that you want to show. If you are using it only one time, like you are doing inside setUpMap(), you will only see one marker.
Don't forget to change your MarkerOptions attributes for each marker.
I have a very simple SupportMapFragment to display a small Google map which I use in this view
The idea is that the user can click it to view a full screen map.
How can I get rid of the +/- button from the map?
If it's not possible, is there an alternative method to get a map?
Here is my MapFragment code:
public class CustomMapFragment extends SupportMapFragment {
private static LatLng mPosFija;
public CustomMapFragment() {
super();
}
public static CustomMapFragment newInstance(LatLng position) {
CustomMapFragment fragment = new CustomMapFragment();
mPosFija = position;
return fragment;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (getMap() != null) {
initMap();
Log.d(getClass().getSimpleName(), "Map ready for use!");
}
}
#Override
public void onResume() {
super.onResume();
initMap();
}
private void initMap() {
Log.v("CustomMapFragment", "initMap");
if (getMap() != null) {
UiSettings settings = getMap().getUiSettings();
settings.setAllGesturesEnabled(true);
settings.setMyLocationButtonEnabled(false);
getMap().clear();
getMap().moveCamera(CameraUpdateFactory.newLatLngZoom(mPosFija, 5));
getMap().addMarker(new MarkerOptions().position(mPosFija).draggable(false));
}
}
}
This is the code for my DialogFragment that adds the mapFragment to the view:
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (GooglePlayServicesUtil.isGooglePlayServicesAvailable(getActivity()) == ConnectionResult.SERVICE_INVALID
|| GooglePlayServicesUtil.isGooglePlayServicesAvailable(getActivity()) == ConnectionResult.SERVICE_MISSING) {
Log.e("HERE", "Google play not active");
TextViewFragment tvf = new TextViewFragment();
getChildFragmentManager().beginTransaction().replace(R.id.mapview, tvf).commit();
} else {
CustomMapFragment mMapFragment = CustomMapFragment.newInstance(new LatLng(offer.latitude, offer.longitude));
getChildFragmentManager().beginTransaction().replace(R.id.mapview, mMapFragment).commit();
}
}
Ok, so this was staring me in the face and was as simple as
UiSettings settings = getMap().getUiSettings();
settings.setAllGesturesEnabled(false);
settings.setMyLocationButtonEnabled(false);
settings.setZoomControlsEnabled(false);
I have a question and a problem. First of all I don't know is this possible:
I have a fragment activity with tabs. On one tab (which is a fragment) I have a map with a hidden list of items. Map is putted in a frame of that fragment. The list is downloaded and on a button press is visible again. On a map I have markers that represents that items.
My problem is this: I always get map to be null when a use getMap() from a fragment that I put in a frame.
Is this possible? And if not what do you recommend? Thanks in advance.
EDITED
public class MapExplore extends Fragment{
private FrameLayout frame;
private ListView list;
private String api_key;
private GoogleMap map;
private MapExploreAdapter adapter;
private SupportMapFragment map_fragment;
#Override
public void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
api_key = Configuration.get_prefereence_string(getActivity(), "user_activation_key", null);
adapter = new MapExploreAdapter();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.map_explore, null);
frame = (FrameLayout) view.findViewById(R.id.frameMap);
return view;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onActivityCreated(savedInstanceState);
setUpMapFragment();
}
private void setUpMapFragment(){
if(map_fragment == null){
map_fragment = SupportMapFragment.newInstance();
FragmentTransaction ft = getActivity().getSupportFragmentManager().beginTransaction();
ft.add(frame.getId(), map_fragment, Utils.MAP_FRAGMENT_TAG);
ft.commit();
}
}
private void setUpMap(){
Log.i("SET UP MAP", "Started");
if(map == null){
map = ((SupportMapFragment) map_fragment).getMap();
}
if(map != null){
map.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(Utils.SF_LAT, Utils.SF_LON), 13));
}
}
#Override
public void onResume() {
// TODO Auto-generated method stub
super.onResume();
setUpMap();
}
I edited my question with some code...
If you could post some of you code it would be easier to diagnose your problem:
meantime try creating your SupportMap fragment dynamically as follows:
mMapFragment = new SupportMapFragment() {
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
GoogleMap map = mMapFragment.getMap();
if (map != null) {
//Your initialization code goes here
}
}
};
Altenative approach to use Mapview inside fragment as follows:
public class Yourfragment extends Fragment {
private MapView mMapView;
private GoogleMap mMap;
private Bundle mBundle;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View inflatedView = inflater.inflate(R.layout.map_fragment, container, false);
try {
MapsInitializer.initialize(getActivity());
} catch (GooglePlayServicesNotAvailableException e) {
// TODO handle this situation
}
mMapView = (MapView) inflatedView.findViewById(R.id.map);
mMapView.onCreate(mBundle);
setUpMapIfNeeded(inflatedView);
return inflatedView;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBundle = savedInstanceState;
}
private void setUpMapIfNeeded(View inflatedView) {
if (mMap == null) {
mMap = ((MapView) inflatedView.findViewById(R.id.map)).getMap();
if (mMap != null) {
setUpMap();
}
}
}
private void setUpMap() {
mMap.addMarker(new MarkerOptions().position(new LatLng(0, 0)).title("Marker"));
}
#Override
public void onResume() {
super.onResume();
mMapView.onResume();
}
#Override
public void onPause() {
super.onPause();
mMapView.onPause();
}
#Override
public void onDestroy() {
mMapView.onDestroy();
super.onDestroy();
}
}
And put map view in XML as follows:
<com.google.android.gms.maps.MapView
android:id="#+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Here is a link to the official documentation for MapView