Android creating BitmapDescriptor exception

2019-02-18 22:58发布

问题:

I'm writing an application that works alot with google map and markers on it. My task is to create and display some amount of markers on google map. Markers have custom image and text in it. Data is loading from server and i need to display new amount of data every time user moves google map camera. So i'm using android-maps-utils:0.4.3 library for creating custom Bitmap (using IconGenerator) and then create BitmapDescriptor from it. Here is part of the code:

  googleMap.clear()

        LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        IconGenerator clusterIconGenerator = new IconGenerator(getActivity());
        View clusterView = layoutInflater.inflate(R.layout.marker_cluster_view, null, false);
        clusterIconGenerator.setContentView(clusterView);
        clusterIconGenerator.setBackground(ContextCompat.getDrawable(getActivity(), R.drawable.mark_normal_grey));

        List<Cluster> clusters = result.getResult();  // server data

        for (Cluster cluster : clusters) {
            Bitmap bitmap = clusterIconGenerator.makeIcon(String.valueOf(cluster.getOffersCount()));

            Marker marker = googleMap.addMarker(new MarkerOptions()
                    .position(new LatLng(cluster.getLocation().getLatitude(), cluster.getLocation().getLongitude()))
                    .icon(BitmapDescriptorFactory.fromBitmap(bitmap))  // crash here
                    .anchor(0.5f, 0.5f));

            markerClusterMap.put(marker, cluster);
        }

Everything is ok except application is crashes sometimes (not very often) with 2 different exceptions:

  java.lang.RuntimeException: Could not copy bitmap to parcel blob.
                                                                     at android.graphics.Bitmap.nativeWriteToParcel(Native Method)
                                                                     at android.graphics.Bitmap.writeToParcel(Bitmap.java:1541)
                                                                     at com.google.android.gms.maps.model.a.c.a(:com.google.android.gms:237)
                                                                     at com.google.android.gms.maps.internal.h.a(:com.google.android.gms:227)
                                                                     at com.google.android.gms.maps.internal.j.a(:com.google.android.gms:183)
                                                                     at com.google.android.gms.maps.internal.CreatorImpl.a(:com.google.android.gms:32)
                                                                     at com.google.android.gms.maps.internal.b.a(:com.google.android.gms:227)
                                                                     at com.google.android.gms.maps.model.a.b.onTransact(:com.google.android.gms:106)
                                                                     at android.os.Binder.transact(Binder.java:387)
                                                                     at com.google.android.gms.maps.model.internal.zza$zza$zza.zzc(Unknown Source)
                                                                     at com.google.android.gms.maps.model.BitmapDescriptorFactory.fromBitmap(Unknown Source)
                                                                     at com.cheapsta.cheapsta.fragments.GoogleMapFragment.clustersLoadingFinished(GoogleMapFragment.java:187)

and sometimes

java.lang.RuntimeException: Could not allocate dup blob fd.
                                                                       at android.graphics.Bitmap.nativeCreateFromParcel(Native Method)
                                                                       at android.graphics.Bitmap.-wrap0(Bitmap.java)
                                                                       at android.graphics.Bitmap$1.createFromParcel(Bitmap.java:1516)
                                                                       at android.graphics.Bitmap$1.createFromParcel(Bitmap.java:1515)
                                                                       at maps.bx.a$a.onTransact(:com.google.android.gms.alldynamite:101)
                                                                       at android.os.Binder.transact(Binder.java:387)
                                                                       at com.google.android.gms.maps.model.a.c.a(:com.google.android.gms:242)
                                                                       at com.google.android.gms.maps.internal.h.a(:com.google.android.gms:227)
                                                                       at com.google.android.gms.maps.internal.j.a(:com.google.android.gms:183)
                                                                       at com.google.android.gms.maps.internal.CreatorImpl.a(:com.google.android.gms:32)
                                                                       at com.google.android.gms.maps.internal.b.a(:com.google.android.gms:227)
                                                                       at com.google.android.gms.maps.model.a.b.onTransact(:com.google.android.gms:106)
                                                                       at android.os.Binder.transact(Binder.java:387)
                                                                       at com.google.android.gms.maps.model.internal.zza$zza$zza.zzc(Unknown Source)
                                                                       at com.google.android.gms.maps.model.BitmapDescriptorFactory.fromBitmap(Unknown Source)
                                                                       at com.cheapsta.cheapsta.fragments.GoogleMapFragment.clustersLoadingFinished(GoogleMapFragment.java:187)

What can i do with this? I guess i'm using my memory to much to create BitmapDescriptors. It's nearly 20 BitmapDescriptos every 3 seconds if user moving camera too much. Should i cache it somehow? Thx a lot for your answers and time!

回答1:

Well here is what i got. Looks like BitmapFactory can't create Bitmap if it don't have enought memory. So if GC didn't do job and u don't have enough memory u'll get this exception. In my case that was pretty often because i need to generate about 10-20 markers every time user moves google map camera.

First of all don't be stupid like me and don't use android-maps-utils just for IconGenerator :) I wrote my own class that generate's BitmapDescriptor from Bitmap and caches it in LruCache. Here's good tutorial for caching Bitmaps. You can do almost the same for BitmapDescriptor. Pay attention to LruCache size. You can't get BitmapDescriptor size in bytes, so you need to think about amount of these objects in LruCache. Just look at your bitmap size and do some calculations.

If you need text in your image do something like this:

Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.mark_active_grey).copy(Bitmap.Config.ARGB_8888, true);

    Canvas canvas = new Canvas(bitmap);
    canvas.drawText(offersCount,
            canvas.getWidth()/2,
            canvas.getHeight()/2 - ((clustersPaint.getFontMetrics().ascent + clustersPaint.getFontMetrics().descent) / 2) ,
            clustersPaint);

Sorry for bad english and i hope this information will be useful to some one.



回答2:

I had same problem. With Kuva's answer I make a new class like this:

public class MapBmpContainter
{
    private int mBmpSize;
    public BitmapDescriptor mBmpDescriptor;

    public MapBmpContainter(Bitmap bmp)
    {
        mBmpSize=bmp.getByteCount()/1014;
        mBmpDescriptor= BitmapDescriptorFactory.fromBitmap(bmp);
    }

    public int getSize()
    {
        return mBmpSize;
    }
}

I cache new class object in LruCache instead of Bitmap. Same with Kuva I think Bitmap and BitmapDescriptor almost same size. And It worked



回答3:

For me the problem was related to the size of the icon. I just change dynamically the size of the Bitmap and works fine.

Bitmap image2 = Bitmap.createScaledBitmap(iconBitmap, 120, 120, false);



回答4:

Use Picasso , Glide or Fresco Literary to cache bitmaps efficiently.

 Picasso.with(getContext())
   .load(R.drawable.marker)
   .resize(width, width)
    .into(new Target() {
   @Override
  public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
   markerOptionsHome = new MarkerOptions();
   markerOptionsHome.title("Home location");
   markerOptionsHome.snippet("");
   markerOptionsHome.position(latlng);
   markerOptionsHome.icon(BitmapDescriptorFactory.fromBitmap(bitmap));
   homeLocationMarker = map.addMarker(markerOptionsHome);

            }

    @Override
   public void onBitmapFailed(Drawable errorDrawable) { }
     @Override
    public void onPrepareLoad(Drawable placeHolderDrawable) {  }
          });