IllegalArgumentException: Unmanaged descriptor using gms.maps.model.Marker.setIcon

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
  • What firebase crash reported to me
  • Some project configs
  • What I tried/found trying to understand/fix it

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

  • I'm using a Custom Render (SucursalRender extends DefaultClusterRenderer<Sucursal>)
  • I'm downloading the Marker icon with Glide like I said before: 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

  • Tried to reproduce the error in 4 real devices without success.
  • Searched in web for similar errors or code about gms.maps.model.Marker.setIcon or com.google.maps.api.android.lib6
  • Tried to understand the obfuscated code given in Android Studio for 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);
}

Answers


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 was too getting same exception and setting silent exception with try/catch would not have been solution as user is not able to see current location in my case:

java.lang.IllegalArgumentException: Unmanaged descriptor at com.google.maps.api.android.lib6.common.k.b(:com.google.android.gms.DynamiteModulesB:162) at com.google.maps.api.android.lib6.impl.o.c(:com.google.android.gms.DynamiteModulesB:75) at com.google.maps.api.android.lib6.impl.db.a(:com.google.android.gms.DynamiteModulesB:334) at com.google.android.gms.maps.model.internal.q.onTransact(:com.google.android.gms.DynamiteModulesB:204) at android.os.Binder.transact(Binder.java:361) at com.google.android.gms.maps.model.internal.zzf$zza$zza.zzL(Unknown Source) at com.google.android.gms.maps.model.Marker.setIcon(Unknown Source)

What I was doing :

Minimize the map fragment screen by pressing home button and then starting app from launcher.

What code was doing:

Checking is marker is not null and location is not null set location and icon.

     if (markerCurrentLocation == null && googleMap != null) {
            markerCurrentLocation = googleMap.addMarker(new MarkerOptions()
                    .position(new LatLng(0.0, 0.0))
                    .icon(null));
            markerCurrentLocation.setTag(-101);
       }

         if (markerCurrentLocation != null && location != null) {
                markerCurrentLocation.setPosition(new LatLng(location.getLatitude(), location.getLongitude()));
                if (ORDER_STARTED) {
                   markerCurrentLocation.setIcon(CURRENT_MARKER_ORANGE);
                } else {
                    markerCurrentLocation.setIcon(CURRENT_MARKER_GRAY);
                }       
         }

Exception was at : markerCurrentLocation.setIcon();

How I got rid of this exception:

I removed the following line

 if (markerCurrentLocation == null && googleMap != null) 

Which means I am initializing marker again. If you encounter this error, try not to setIcon() on old marker, instead inflate new marker and then use setIcon().

Explanation:

I ASSUME (not sure) exception reason was if code is trying to setIcon() again on marker on which it is already set , at particular instance like in my case Map is resuming or may be in your case marker goes out of visible part of map and comes in or something similar.

For sure there is no problem with descriptor we get from method BitmapDescriptorFactory.fromBitmap() or BitmapDescriptorFactory.fromResource(). As the exception hints, descriptor got unmanaged on a old marker, better use new one.


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));
            }
        }
    }
}

This exception happens when your marker was reclustered by ClusterManager. ClusterManager recreates marker on clustering. So, to avoid it you must get your marker from render of ClusterManeger:

ClusterIconRender render = (ClusterIconRender) mClusterManager.getRenderer();
Marker trueMarker = render.getMarker(clusterMarker);
if (trueMarker != null) {
    trueMarker.setIcon(...);
    ... // do whatever else your want with marker
}

In code above ClusterMarker implements ClusterItem and ClusterIconRender extends DefaultClusterRenderer.


I have the same environment (maps-utils + custom renderer + Glide) and the same error IllegalArgumentException: Unmanaged descriptor.

I solved the error by checking if the marker is "valid" before setting the icon, using the methods DefaultClusterRenderer.getCluster(Marker) and DefaultClusterRenderer.getClusterItem(Marker). If both return null, I don't do anything on the onResourceReady(...) method.

In your case I would try the following change to CustomSimpleTarget:

private class CustomSimpleTarget extends SimpleTarget<GlideDrawable> {
    Sucursal sucursal;
    Marker markerToChange = null;

    @Override
    public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) {

        if (getCluster(markerToChange) != null || getClusterItem(markerToChange) != null) {

            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();

            //GlideShortcutDrawable is a WeakReference<>(drawable)
            sucursal.setGlideShortCutDrawable(resource);
            markerToChange.setIcon(BitmapDescriptorFactory.fromBitmap(icon));
        }
    }
}

PS.: I can reproduce the problem easily on a slow device and clearing the app cache before testing (to force Glide to load from network). Then I open the map and perform some zoom in/out before any markers load.


Make sure the icon that you are using for marker should not be vector,it should be .png image.


I fixed it by calling the following method.

clusterManager.clearItems()

After that, you can set bitmap to the markers.


Need Your Help

Why is my custom MySQL function so much slower than inlining same in query?

mysql performance user-defined-functions

I repeatedly use this SELECT query to read unsigned integers representing IPv4 addresses and present them as human readable dotted quad strings.

Signing and verifying signatures with RSA C#

c# encoding rsa signing

I recently posted about issues with encrypting large data with RSA, I am finally done with that and now I am moving on to implementing signing with a user's private key and verifying with the