非法参数异常:使用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个回答

0

我通过调用以下方法解决了这个问题。

clusterManager.clearItems()

之后,您可以将位图设置为标记。


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