I have an app that use android-maps-utils and glide for marker icons.
I got an error report using Firebase crash reporting which I can't track in source code because gms.maps.model.Marker.setIcon
is private, so I'm asking for some help with this problem.
The follow part of the question is divided into:
What the user was doing
He was zooming in and out in a map (Fragment
that uses com.google.android.gms.maps.SupportMapFragment
)
What firebase crash reported to me
Exception java.lang.IllegalArgumentException: Unmanaged descriptor
com.google.maps.api.android.lib6.common.k.b (:com.google.android.gms.DynamiteModulesB:162)
com.google.maps.api.android.lib6.impl.o.c (:com.google.android.gms.DynamiteModulesB:75)
com.google.maps.api.android.lib6.impl.db.a (:com.google.android.gms.DynamiteModulesB:334)
com.google.android.gms.maps.model.internal.q.onTransact (:com.google.android.gms.DynamiteModulesB:204)
android.os.Binder.transact (Binder.java:387)
com.google.android.gms.maps.model.internal.zzf$zza$zza.zzL () com.google.android.gms.maps.model.Marker.setIcon ()
co.com.spyspot.ui.content.sucursal.SucursalRender$CustomSimpleTarget.onResourceReady (SucursalRender.java:156)
co.com.spyspot.ui.content.sucursal.SucursalRender$CustomSimpleTarget.onResourceReady (SucursalRender.java:130)
com.bumptech.glide.request.GenericRequest.onResourceReady (GenericRequest.java:525)
com.bumptech.glide.request.GenericRequest.onResourceReady (GenericRequest.java:507)
com.bumptech.glide.load.engine.EngineJob.handleResultOnMainThread (EngineJob.java:158)
com.bumptech.glide.load.engine.EngineJob.access$100 (EngineJob.java:22)
com.bumptech.glide.load.engine.EngineJob$MainThreadCallback.handleMessage (EngineJob.java:202)
android.os.Handler.dispatchMessage (Handler.java:98)
android.os.Looper.loop (Looper.java:148)
android.app.ActivityThread.main (ActivityThread.java:5443)
java.lang.reflect.Method.invoke (Method.java)
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run (ZygoteInit.java:728)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:618)
And:
Some project configs
SucursalRender extends DefaultClusterRenderer<Sucursal>
)Glide.with(context).load(id).fitCenter().placeholder(R.drawable.ic_no_image).into(simpleTarget);
The simpleTarget
is where I handle the images downloaded/cached for Glide. I'm posting all code about simpleTarget
because the crash is starting there:
private class CustomSimpleTarget extends SimpleTarget<GlideDrawable> { Sucursal sucursal; Marker markerToChange = null; @Override public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) { mImageView.setImageDrawable(resource); //currentSelectedItem is the current element selected in the map (Sucursal type) //mIconGenerator is a: CustomIconGenerator extends IconGenerator if (currentSelectedItem != null && sucursal.idalmacen.contentEquals(currentSelectedItem.idalmacen)) mIconGenerator.customIconBackground.useSelectionColor(true, ContextCompat.getColor(mContext, R.color.colorAccent)); else mIconGenerator.customIconBackground.useSelectionColor(false, 0); Bitmap icon = mIconGenerator.makeIcon(); if (markerToChange == null) { for (Marker marker : mClusterManager.getMarkerCollection().getMarkers()) { if (marker.getPosition().equals(sucursal.getPosition())) { markerToChange = marker; } } } // if found - change icon if (markerToChange != null) { //GlideShortcutDrawable is a WeakReference<>(drawable) sucursal.setGlideShortCutDrawable(resource); markerToChange.setIcon(BitmapDescriptorFactory.fromBitmap(icon)); } } }
The crash is being thrown in last line of code: markerToChange.setIcon(BitmapDescriptorFactory.fromBitmap(icon));
What I tried/found trying to understand/fix it
gms.maps.model.Marker.setIcon
or com.google.maps.api.android.lib6
Marker.setIcon
I guess I can wrap the code in a try-catch block
for that IllegalArgumentException: Unmanaged descriptor to avoid application get closed because the crash but it's just a work around it.
update 2
The code of DefaultClusterRenderer
:
public class SucursalRender extends DefaultClusterRenderer<Sucursal> { /** * Create a customized icon for markers with two background colors. Used with {@link com.google.maps.android.clustering.ClusterItem}. */ private final CustomIconGenerator mIconGenerator; /** * Marker image. */ private final ImageView mImageView; /** * Create a customized icon for {@link Cluster<Sucursal>} with a single background. */ private final IconGenerator mClusterIconGenerator; /** * Cluster image. */ private final ImageView mClusterImageView; private final Context mContext; /** * Keep a reference to the current item highlighted in UI (the one with different background). */ public Sucursal currentSelectedItem; /** * The {@link ClusterManager<Sucursal>} instance. */ private ClusterManager<Sucursal> mClusterManager; public SucursalRender(Context context, GoogleMap map, ClusterManager<Sucursal> clusterManager) { super(context, map, clusterManager); mContext = context; mClusterManager = clusterManager; mIconGenerator = new CustomIconGenerator(mContext.getApplicationContext()); mClusterIconGenerator = new IconGenerator(mContext.getApplicationContext()); int padding = (int) mContext.getResources().getDimension(R.dimen.custom_profile_padding); int dimension = (int) mContext.getResources().getDimension(R.dimen.custom_profile_image); //R.layout.map_cluster_layout is a simple XML with the visual elements to use in markers and cluster View view = ((AppCompatActivity)mContext).getLayoutInflater().inflate(R.layout.map_cluster_layout, null); mClusterIconGenerator.setContentView(view); mClusterImageView = (ImageView) view.findViewById(R.id.image); mClusterImageView.setPadding(padding, padding, padding, padding); mImageView = new ImageView(mContext.getApplicationContext()); mImageView.setLayoutParams(new ViewGroup.LayoutParams(dimension, dimension)); mImageView.setPadding(padding, padding, padding, padding); mIconGenerator.setContentView(mImageView); CustomIconBackground customIconBackground = new CustomIconBackground(false); mIconGenerator.setBackground(customIconBackground); mIconGenerator.customIconBackground = customIconBackground; mClusterIconGenerator.setBackground(new CustomIconBackground(true)); } ... @Override protected void onBeforeClusterItemRendered(final Sucursal sucursal, MarkerOptions markerOptions) { mImageView.setImageDrawable(ContextCompat.getDrawable(mContext, R.drawable.ic_no_image)); Bitmap icon = mIconGenerator.makeIcon(); markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon)); } @Override protected void onClusterItemRendered(Sucursal clusterItem, Marker marker) { CustomSimpleTarget simpleTarget = new CustomSimpleTarget(); simpleTarget.sucursal = clusterItem; simpleTarget.markerToChange = marker; ImageLoaderManager.setImageFromId(simpleTarget, clusterItem.logo, mContext); } @Override protected void onBeforeClusterRendered(Cluster<Sucursal> cluster, MarkerOptions markerOptions) { mClusterImageView.setImageDrawable(ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_sucursales, null)); Bitmap icon = mClusterIconGenerator.makeIcon(String.valueOf(cluster.getSize())); markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon)); } @Override protected boolean shouldRenderAsCluster(Cluster cluster) { // Always render clusters. return cluster.getSize() > 1; } /** * Just extends {@link IconGenerator} and give the ability to change background. * Used to know highlight the current selected item in UI. */ private class CustomIconGenerator extends IconGenerator { private CustomIconBackground customIconBackground; private CustomIconGenerator(Context context) { super(context); } } /** * Create a custom icon to use with {@link Marker} or {@link Cluster<Sucursal>} */ private class CustomIconBackground extends Drawable { private final Drawable mShadow; private final Drawable mMask; private int mColor = Color.WHITE; private boolean useSelectionColor; private int mColorSelection; private CustomIconBackground(boolean isCluster) { useSelectionColor = false; if (isCluster) { mMask = ContextCompat.getDrawable(mContext, R.drawable.map_pin_negro_cluster); mShadow = ContextCompat.getDrawable(mContext, R.drawable.map_pin_transparente_cluster); } else { mMask = ContextCompat.getDrawable(mContext, R.drawable.map_pin_negro); mShadow = ContextCompat.getDrawable(mContext, R.drawable.map_pin_transparente); } } public void setColor(int color) { mColor = color; } private void useSelectionColor(boolean value, int color) { useSelectionColor = value; mColorSelection = color; } @Override public void draw(@NonNull Canvas canvas) { mMask.draw(canvas); canvas.drawColor(mColor, PorterDuff.Mode.SRC_IN); mShadow.draw(canvas); if (useSelectionColor) { canvas.drawColor(mColorSelection, PorterDuff.Mode.SRC_IN); useSelectionColor = false; } } @Override public void setAlpha(int alpha) { throw new UnsupportedOperationException(); } @Override public void setColorFilter(ColorFilter cf) { throw new UnsupportedOperationException(); } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } @Override public void setBounds(int left, int top, int right, int bottom) { mMask.setBounds(left, top, right, bottom); mShadow.setBounds(left, top, right, bottom); } @Override public void setBounds(@NonNull Rect bounds) { mMask.setBounds(bounds); mShadow.setBounds(bounds); } @Override public boolean getPadding(@NonNull Rect padding) { return mMask.getPadding(padding); } }
The ImageLoaderManager
is just a Facade for Glide.
public static void setImageFromId(SimpleTarget<GlideDrawable> simpleTarget, String id, Context context) { if (context instanceof AppCompatActivity) { AppCompatActivity activity = (AppCompatActivity)context; if (activity.isDestroyed()) return; } Glide.with(context) .load(id) .fitCenter() .placeholder(R.drawable.ic_no_image) .into(simpleTarget); }
When clearing the map with
googleMap.clear();
**remove any reference to all the markers**
on the map. I had the problem and figured out that the problem was with my code which I forgot to remove reference to a marker and tried to change icon of a cleared Marker
I found this happening when accessing marker after it was removed. Interacting with marker in callback is exactly that case. As mentioned in Map's API:
After a marker has been removed, the behavior of all its methods is undefined. https://developers.google.com/android/reference/com/google/android/gms/maps/model/Marker.html#remove()
Best option would be checking is marker removed from map or not.
But we don't have such API. And I found another workaround, we can use Marker's setTag
and getTag
. Tag is set to null, when marker is removed:
Google Maps Android API neither reads nor writes this property, except that when a marker is removed from the map, this property is set to null. https://developers.google.com/android/reference/com/google/android/gms/maps/model/Marker.html#setTag(java.lang.Object)
When creating marker use some tag for it.
When updating marker check tag is not null.
This could help in your case.
@Override protected void onClusterItemRendered(Sucursal clusterItem, Marker marker) { // we don't care about tag's type so don't reset original one if (marker.getTag() == null) { marker.setTag("anything"); } CustomSimpleTarget simpleTarget = new CustomSimpleTarget(); simpleTarget.sucursal = clusterItem; simpleTarget.markerToChange = marker; ImageLoaderManager.setImageFromId(simpleTarget, clusterItem.logo, mContext); }
And in callback
private class CustomSimpleTarget extends SimpleTarget<GlideDrawable> { ... @Override public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) { ... // if found - change icon if (markerToChange != null) { //GlideShortcutDrawable is a WeakReference<>(drawable) sucursal.setGlideShortCutDrawable(resource); if (markerToChange.getTag != null) { markerToChange.setIcon(BitmapDescriptorFactory.fromBitmap(icon)); } } } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With