From 428ed71f79a2bf46e0f5f33cb9ab4dcc48e11c79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Paczos?= Date: Tue, 22 May 2018 20:18:26 +0200 Subject: [android] - unregister observers when destroying the map to prevent leaks --- .../com/mapbox/mapboxsdk/maps/MapFragment.java | 3 +- .../com/mapbox/mapboxsdk/maps/MapSettings.java | 36 ++++++++++++++++++++++ .../java/com/mapbox/mapboxsdk/maps/MapView.java | 11 ++++--- .../mapbox/mapboxsdk/maps/SupportMapFragment.java | 3 +- .../java/com/mapbox/mapboxsdk/maps/UiSettings.java | 34 +++++++++++++++++++- .../mapbox/mapboxsdk/maps/widgets/CompassView.java | 2 -- .../mapboxsdk/maps/widgets/WidgetUpdater.java | 2 -- 7 files changed, 80 insertions(+), 11 deletions(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapFragment.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapFragment.java index 2cd8e9245e..dfaccbe7fc 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapFragment.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapFragment.java @@ -24,7 +24,8 @@ import java.util.List; *

*

* If you are planning on using multiple MapView instances in one lifecycle - * you have to specify a unique ID for each instance with {@link MapboxMapOptions#setMapId(String)} or in xml attributes. + * you have to specify a unique ID for each instance with {@link MapboxMapOptions#setMapId(String)} + * or in the xml attributes. *

*

* To get a reference to the MapView, use {@link #getMapAsync(OnMapReadyCallback)}} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapSettings.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapSettings.java index 8a64937bf4..6b7c055c7d 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapSettings.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapSettings.java @@ -1,7 +1,10 @@ package com.mapbox.mapboxsdk.maps; +import android.arch.lifecycle.LifecycleOwner; +import android.arch.lifecycle.LiveData; import android.arch.lifecycle.MutableLiveData; import android.arch.lifecycle.ViewModel; +import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; @@ -9,6 +12,12 @@ import android.text.TextUtils; import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.constants.Style; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import timber.log.Timber; + /** * Internal class used to manage map settings. */ @@ -119,4 +128,31 @@ public final class MapSettings extends ViewModel { public void setApiBaseUrl(String apiBaseUrl) { this.apiBaseUrl.setValue(apiBaseUrl); } + + void onMapDestroy(@NonNull Context context) { + clearObservers((LifecycleOwner) context); + } + + private void clearObservers(@NonNull LifecycleOwner lifecycleOwner) { + try { + Field[] fields = this.getClass().getDeclaredFields(); + Method method = LiveData.class.getDeclaredMethod("removeObservers", LifecycleOwner.class); + for (Field field : fields) { + Class type = field.getType(); + if (type == MutableLiveData.class) { + Object value = field.get(this); + method.invoke(value, lifecycleOwner); + } + } + } catch (NoSuchMethodException ex) { + Timber.e("Unable to clear %s observers, %s.", this.getClass(), ex.getClass().getSimpleName()); + ex.printStackTrace(); + } catch (IllegalAccessException ex) { + Timber.e("Unable to clear %s observers, %s.", this.getClass(), ex.getClass().getSimpleName()); + ex.printStackTrace(); + } catch (InvocationTargetException ex) { + Timber.e("Unable to clear %s observers, %s.", this.getClass(), ex.getClass().getSimpleName()); + ex.printStackTrace(); + } + } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java index edd93f6ae7..34cc2ed59e 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java @@ -73,7 +73,8 @@ import static com.mapbox.mapboxsdk.maps.widgets.CompassView.TIME_WAIT_IDLE; *

*

* If you are planning on using multiple MapView instances in one lifecycle - * you have to specify a unique ID for each instance with {@link MapboxMapOptions#setMapId(String)} or in xml attributes. + * you have to specify a unique ID for each instance with {@link MapboxMapOptions#setMapId(String)} + * or in the xml attributes. *

*

* Use of {@code MapView} requires a Mapbox API access token. @@ -141,8 +142,8 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { } if (!(context instanceof FragmentActivity)) { - throw new IllegalArgumentException("You need to instantiate MapView from FragmentActivity context. " + - "If your LayoutInflater works with a different context try creating MapView programmatically."); + throw new IllegalArgumentException("You need to instantiate MapView from FragmentActivity context. " + + "If your LayoutInflater works with a different context try creating MapView programmatically."); } mapboxMapOptions = options; @@ -435,7 +436,9 @@ public class MapView extends FrameLayout implements NativeMapView.ViewCallback { destroyed = true; onMapChangedListeners.clear(); mapCallback.clearOnMapReadyCallbacks(); - uiSettings.onMapDestroy(); + + uiSettings.onMapDestroy(getContext()); + mapSettings.onMapDestroy(getContext()); if (nativeMapView != null && hasSurface) { // null when destroying an activity programmatically mapbox-navigation-android/issues/503 diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/SupportMapFragment.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/SupportMapFragment.java index 3b30424c63..5ab977d5d4 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/SupportMapFragment.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/SupportMapFragment.java @@ -24,7 +24,8 @@ import java.util.List; *

*

* If you are planning on using multiple MapView instances in one lifecycle - * you have to specify a unique ID for each instance with {@link MapboxMapOptions#setMapId(String)} or in xml attributes. + * you have to specify a unique ID for each instance with {@link MapboxMapOptions#setMapId(String)} + * or in the xml attributes. *

*

* To get a reference to the MapView, use {@link #getMapAsync(OnMapReadyCallback)}} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/UiSettings.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/UiSettings.java index ea0665d372..96018bb4c0 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/UiSettings.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/UiSettings.java @@ -1,5 +1,7 @@ package com.mapbox.mapboxsdk.maps; +import android.arch.lifecycle.LifecycleOwner; +import android.arch.lifecycle.LiveData; import android.arch.lifecycle.MutableLiveData; import android.arch.lifecycle.ViewModel; import android.content.Context; @@ -17,6 +19,12 @@ import com.mapbox.mapboxsdk.R; import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.utils.ColorUtils; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import timber.log.Timber; + /** * Settings for the user interface of a MapboxMap. To obtain this interface, call getUiSettings(). */ @@ -997,8 +1005,32 @@ public final class UiSettings extends ViewModel { * Method used to cleanup resources that might leak during configuration change, * like deprecated {@link #setAttributionDialogManager(AttributionDialogManager)} or {@link #getWidth()}. */ - void onMapDestroy() { + void onMapDestroy(@NonNull Context context) { setAttributionDialogManager(null); projection = null; + clearObservers((LifecycleOwner) context); + } + + private void clearObservers(@NonNull LifecycleOwner lifecycleOwner) { + try { + Field[] fields = this.getClass().getDeclaredFields(); + Method method = LiveData.class.getDeclaredMethod("removeObservers", LifecycleOwner.class); + for (Field field : fields) { + Class type = field.getType(); + if (type == MutableLiveData.class) { + Object value = field.get(this); + method.invoke(value, lifecycleOwner); + } + } + } catch (NoSuchMethodException ex) { + Timber.e("Unable to clear %s observers, %s.", this.getClass(), ex.getClass().getSimpleName()); + ex.printStackTrace(); + } catch (IllegalAccessException ex) { + Timber.e("Unable to clear %s observers, %s.", this.getClass(), ex.getClass().getSimpleName()); + ex.printStackTrace(); + } catch (InvocationTargetException ex) { + Timber.e("Unable to clear %s observers, %s.", this.getClass(), ex.getClass().getSimpleName()); + ex.printStackTrace(); + } } } diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java index 712e137238..5b5999a4a5 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java @@ -1,12 +1,10 @@ package com.mapbox.mapboxsdk.maps.widgets; import android.annotation.SuppressLint; -import android.arch.lifecycle.ViewModelProviders; import android.content.Context; import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; import android.support.annotation.UiThread; -import android.support.v4.app.FragmentActivity; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPropertyAnimatorCompat; import android.support.v4.view.ViewPropertyAnimatorListenerAdapter; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/WidgetUpdater.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/WidgetUpdater.java index 0f3dbac309..523411b1f5 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/WidgetUpdater.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/WidgetUpdater.java @@ -1,11 +1,9 @@ package com.mapbox.mapboxsdk.maps.widgets; import android.arch.lifecycle.LifecycleOwner; -import android.arch.lifecycle.ViewModelProviders; import android.content.Context; import android.graphics.Color; import android.support.annotation.NonNull; -import android.support.v4.app.FragmentActivity; import android.support.v4.content.ContextCompat; import android.view.View; import android.widget.ImageView; -- cgit v1.2.1