非法参数异常:使用gms.maps.model.Marker.setIcon设置未受管理的描述符。

70

我有一个应用程序,使用android-maps-utilsglide用于标记图标
我收到了Firebase崩溃报告,但在源代码中无法跟踪gms.maps.model.Marker.setIcon是私有的,所以我需要一些帮助解决这个问题。
以下问题分为:

  • 用户正在做什么
  • Firebase崩溃报告了什么信息给我
  • 一些项目配置
  • 我尝试了/发现了什么来理解/修复它

用户正在做什么
他正在地图上缩放(使用com.google.android.gms.maps.SupportMapFragmentFragment

Firebase崩溃报告了什么信息给我

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)

以及:

在此输入图片描述

一些项目配置

  • 我正在使用自定义渲染器(SucursalRender extends DefaultClusterRenderer<Sucursal>
  • 我正在使用 Glide 下载标记图标,就像我之前说的那样:Glide.with(context).load(id).fitCenter().placeholder(R.drawable.ic_no_image).into(simpleTarget);

simpleTarget 是我处理 Glide 下载/缓存的图像的地方。 我将所有与 simpleTarget 相关的代码都发布了,因为崩溃是从那里开始的:

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));
        }
    }
}
代码的最后一行抛出了一个“崩溃”,具体是:markerToChange.setIcon(BitmapDescriptorFactory.fromBitmap(icon));。我尝试在4个真实设备上重现错误,但没有成功。我在网上搜索了关于gms.maps.model.Marker.setIconcom.google.maps.api.android.lib6的类似错误或代码。我试图理解Android Studio中给出的Marker.setIcon的混淆代码。我猜可以将代码包装在try-catch块中,以避免应用程序因崩溃而关闭,但这只是权宜之计。更新: 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);
        }
    }

ImageLoaderManager 只是 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);
}

嗨@YoniGross CustomSimpleTarget 是一个异步进程(当Glide正在下载图像时)。当图像被下载时,您不知道标记的状态,并且您可以传递到构造函数中的标记仍然可能在内存中但未显示在地图中(因为正在与地图中的新标记同步处理,请查看android-maps-utils中缩放时标记如何更新,您就会理解我在说什么)。 - MiguelHincapieC
经过对google-maps-utils及其DefaultClusterManager的一些检查,我改变了我的想法。问题可能恰恰相反:集群管理器不断删除标记并添加新标记。特别是在缩放时(需要创建/分裂集群)。因此,您在滑动目标中提到的标记可能已从地图中删除。因此,实际上不在目标中引用标记应该修复该问题,虽然不是最理想的,而且我将改变整个“渲染”实现。 - Yoni Gross
@YoniGross 让我们看看我是否理解了,删除 if (markerToChange == null) 将有助于解决这个问题(好的,我要试一下)。 你是指更改整个“渲染”,是吗:SucursalRender? 我是根据 google-maps-utils 的演示和一些很好的投票答案与片段来做的。像我之前说的,如果你有更好的方法,我会很乐意使用它;)。我可以在很多关于它的问题(google-maps-utils 和异步下载内容)中引用它,以帮助人们使用它。 - MiguelHincapieC
是的,就我所理解的而言,应该是这样的。ClusterRenderer 角色是根据地图状态(例如缩放级别)不断渲染标记(对于单个项目/聚类)。onBeforeRender 回调与 RecyclerView.Adapter.bindHolder.bindViewHolder() 类似。请查看 DefaultClusterRenderer.RenderTask 代码 - 当动画时它不断删除标记并添加新标记。您的代码与 CustomMarkerClusteringDemoActivity 中的代码的区别在于,那里的 setIcon 操作是同步的,而您的则是异步的。您应该在模型中异步加载,在渲染时进行同步。 - Yoni Gross
@MiguelHincapieC,你试过我的建议了吗?假设它解决了问题,你明白为什么会出现这种情况以及如何正确地重写渲染代码了吗? - Yoni Gross
显示剩余10条评论
11个回答

29

使用地图清除时

    googleMap.clear();

**移除地图上所有标记的任何引用**

我曾经遇到过这个问题,并发现我的代码问题在于我忘记了移除一个标记的引用,试图更改一个已清除的标记的图标。


3
好的提示!虽然不完全适用于我的情况,但指出了我的问题。在设备重置或按下返回按钮后,地图重新加载,所有保存的地图标记都变为无效。我已经很好地重新创建了所有标记,但我忘记了一个我从我的数组列表中单独维护的引用。一旦我从旧标记重新创建了这个“迷路”的标记,问题就解决了。 - ByteSlinger
1
例如,如果您有许多点,并且通过选择您想要更新图标并将先前的点恢复为普通图标,在恢复片段后,您必须知道上一个点是什么。因此,我需要保留对我的最后一个标记的引用。 - Mahdi
你是什么意思说“删除所有标记的引用”?Kotlin没有像Swift那样的引用。 - undefined

22
我发现在移除标记后访问标记时会出现这种情况。在回调中与标记交互正是这种情况。如地图 API 中所述:

当标记被移除后,所有其方法的行为都是未定义的。 https://developers.google.com/android/reference/com/google/android/gms/maps/model/Marker.html#remove()

最好的选择是检查标记是否已从地图上移除。
但我们没有这样的 API。我找到了另一个解决方法,我们可以使用标记的 setTaggetTag。当标记被移除时,标记设置为 null:

Google Maps Android API 既不读取也不写入此属性,除非将标记从地图中移除,否则此属性将设置为 null。 https://developers.google.com/android/reference/com/google/android/gms/maps/model/Marker.html#setTag(java.lang.Object)

创建标记时,请使用某个标记。更新标记时,请检查标记不为 null。

这可能对您的情况有所帮助。

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

而在回调函数中

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

我同意你的分析,尽管我发现在Marker周围添加一个包装器来管理这个“已删除标志”和其他数据是有益的,仅仅因为API说“在标记被删除后,所有方法的行为都是未定义的。”,理论上包括'getTag' - 但显然在实现中'getTag'仍然有效。 - user2711811
继续说,API文档(根据您提供的链接)不再指示在删除时将标签设置为null,而是建议客户端调用setTag(null)(这与“所有其他方法的行为未定义”的声明相矛盾,但仍然似乎有效)。 - user2711811
我的解决方案是:为标记添加标签,在设置标记图标之前检查标签。 - Dan Alboteanu

16

我也遇到了同样的异常,使用try/catch设置静默异常并不是解决办法,因为在我的情况下用户无法看到当前位置:

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)

我正在做的事情:

通过按Home键最小化地图片段屏幕,然后从启动器开始应用程序。

代码在做什么:

检查标记不为空且位置不为空,则设置位置和图标。

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

异常出现在:markerCurrentLocation.setIcon();

我如何解决这个异常:

我删除了以下行:

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

这意味着我再次初始化了marker。如果你遇到这个错误,请尽量不要在旧的marker上使用setIcon(),而是膨胀新的marker,然后再使用setIcon()。

解释:

我推测(不确定)异常的原因是,代码试图在已经设置了setIcon()的标记上再次设置它,例如在我的情况下地图正在恢复,或者在您的情况下标记移出可见部分并重新进入或类似的情况。

确保我们从BitmapDescriptorFactory.fromBitmap()或BitmapDescriptorFactory.fromResource()方法获取的描述符没有问题。正如异常提示的那样,描述符在旧标记上得不到管理,最好使用新的标记。


似乎这是我问题的最接近答案。你遇到了不同原因相同的错误,但它让我对问题可能出在哪里有了一些想法。困难的是,我没有任何设备可以重现错误(这是商店应用程序的崩溃报告)。所以我要尝试你和@JadavLalit说的,发布一个新版本,然后等待是否会继续收到错误报告...或者不会。 - MiguelHincapieC
2
赏金即将结束,正如我之前所说,你的答案是最接近问题的,因此我会给你赏金。但请注意,你的答案(目前)只是给了我一个可以查找错误的路径或方向。 要像被接受的答案一样检查答案,它应该复制代码,显示错误发生的位置和方式以及如何修复它。 - MiguelHincapieC

5
当您的标记被ClusterManager重新聚类时,会出现此异常。 ClusterManager在聚类时会重新创建标记。因此,为避免此问题,您必须从ClusterManeger的渲染器中获取您的标记:
ClusterIconRender render = (ClusterIconRender) mClusterManager.getRenderer();
Marker trueMarker = render.getMarker(clusterMarker);
if (trueMarker != null) {
    trueMarker.setIcon(...);
    ... // do whatever else your want with marker
}

在上述代码中,ClusterMarker 实现了 ClusterItem 接口,而 ClusterIconRender 则继承了 DefaultClusterRenderer 类。

3
对我来说,这是正确的解决方案。我之前一直存储标记(Marker)的引用,但当使用ClusterItem时,应该存储ClusterItem的引用并通过它获取标记。 - zOqvxf
谢谢,这对我有用。当选择另一个标记时,我将当前选定的标记保存为“未选中图像”。 - Zanael
这对我也是答案。只需不要尝试自己跟踪任何“标记”,始终根据您的“ClusterItem”从“ClusterIconRenderer”获取它们。 - EpicPandaForce

3

我有相同的环境(maps-utils + 自定义渲染器 + Glide)和相同的错误 IllegalArgumentException: Unmanaged descriptor

我通过检查标记是否“有效”来解决此错误,使用方法DefaultClusterRenderer.getCluster(Marker)DefaultClusterRenderer.getClusterItem(Marker)。如果两者都返回null,则在onResourceReady(...)方法上不执行任何操作。

对于您的情况,我建议尝试更改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.:我可以在一台慢速设备上轻松重现该问题,并在测试之前清除应用程序缓存(以强制Glide从网络加载)。然后我在地图上打开并执行一些缩放操作,然后等待标记加载。


2

在增加集群后,我遇到了相同的异常。我通过调用以下方法进行修复:

clusterManager.clearItems()

不再使用之前的方法,而是使用集群方式:

googleMap.clear();

谢谢你的回答,我已经卡在这里有一段时间了,代码也改变或改进了,事实是我没有标准来接受一个或另一个答案。无论如何,还是谢谢你帮忙解决这个问题 :)。 - MiguelHincapieC

0

请确保您用于标记的图标不应该是矢量图,而应该是 .png 图像。


2
很遗憾,它们都是 .png 格式的。 - MiguelHincapieC
1
@Harmantj,你可以使用矢量图像作为标记,需要添加一些代码。 - Kaushik

0

标记中心点

当你这样做时:centerPoint.remove();

然后你再次这样做:

marker.setIcon(BitmapDescriptorFactory.fromBitmap(bitmap));

它抛出一个错误:

未受管控的描述符

你应该执行以下操作: centerPoint.remove(); centerPoint=null;


0

精确解决方案

我们应该停止清除googleMap。在添加标记之前,此代码应该工作。这个崩溃将被解决。

        clusterManager.clearItems()
        clusterManager.markerCollection.clear()
    

0

尝试

mClusterManager.markerCollection.clear();
mMap.clear()

3
在这个代码示例中添加一个简短的解释可以帮助改进这个答案。 - shinjw

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接