Android地图自定义Marker转Bitmap时偶现OutOfMemoryError求助
解决地图Marker自定义Bitmap导致的OutOfMemoryError问题
首先得说清楚,你遇到的OOM问题,根源其实不是最后缩放的100100 Bitmap,而是你在加载网络图片时直接用BitmapFactory.decodeStream加载了原图——哪怕你后面把它缩成100100,原始的大Bitmap已经在内存里占了大量空间,这才是导致OOM的关键。另外,你的Bitmap回收和缓存逻辑也有优化空间,咱们一步步来解决:
1. 先对网络图片进行采样压缩,从根源减少内存占用
不要直接加载原图,先获取图片的宽高,计算合适的采样率,再加载缩小后的Bitmap,这样一开始占用的内存就小很多。示例代码:
private Bitmap getSampledBitmapFromUrl(String imageUrl, int targetWidth, int targetHeight) throws IOException { URL url = new URL(imageUrl); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.connect(); // 先获取图片的宽高,不加载到内存 BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeStream(connection.getInputStream(), null, options); // 计算采样率 options.inSampleSize = calculateInSampleSize(options, targetWidth, targetHeight); // 重新打开流,加载采样后的Bitmap connection.disconnect(); connection = (HttpURLConnection) url.openConnection(); options.inJustDecodeBounds = false; options.inPreferredConfig = Bitmap.Config.RGB_565; // 用RGB_565比ARGB_8888节省一半内存 Bitmap bitmap = BitmapFactory.decodeStream(connection.getInputStream(), null, options); // 最后缩到目标尺寸(如果采样后的尺寸还是比目标大的话) return Bitmap.createScaledBitmap(bitmap, targetWidth, targetHeight, true); } private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { inSampleSize *= 2; } } return inSampleSize; }
用这个方法替代你原来的BitmapFactory.decodeStream,就能避免加载超大的原始Bitmap到内存。
2. 优化Bitmap的回收与缓存
- 用完Bitmap及时回收:当Marker被移除(比如地图视野变化、页面销毁)时,调用
bitmap.recycle(),并置为null,帮助GC回收内存。 - 用LruCache缓存Bitmap:避免重复下载和创建相同的Marker Bitmap,减少内存波动。示例:
private LruCache<String, Bitmap> mMarkerBitmapCache; // 在初始化时设置缓存(比如Activity的onCreate) int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); int cacheSize = maxMemory / 8; // 用1/8的内存作为缓存 mMarkerBitmapCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { return bitmap.getByteCount() / 1024; } }; // 缓存Bitmap public void addBitmapToCache(String key, Bitmap bitmap) { if (getBitmapFromCache(key) == null) { mMarkerBitmapCache.put(key, bitmap); } } // 获取缓存的Bitmap public Bitmap getBitmapFromCache(String key) { return mMarkerBitmapCache.get(key); }
3. 优化drawableToIcon方法,避免不必要的内存开销
你的方法里用了buildDrawingCache,记得用完要清理,不然会残留内存:
public Bitmap drawableToIcon(@NonNull Context context, Bitmap image, String status) { View marker = LayoutInflater.from(context).inflate(R.layout.custom_marker_layout, null); ImageView imageView = marker.findViewById(R.id.imageView1); imageView.setImageBitmap(image); // 测量布局 marker.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); marker.layout(0, 0, marker.getMeasuredWidth(), marker.getMeasuredHeight()); // 创建Bitmap并绘制 Bitmap returnedBitmap = Bitmap.createBitmap(marker.getMeasuredWidth(), marker.getMeasuredHeight(), Bitmap.Config.RGB_565); Canvas canvas = new Canvas(returnedBitmap); marker.draw(canvas); // 清理DrawingCache,避免内存泄漏 marker.destroyDrawingCache(); return returnedBitmap; }
另外,把Bitmap.Config.ARGB_8888改成RGB_565,能节省一半内存(如果你的Marker不需要透明通道的话)。
4. 推荐用图片加载库替代自己实现(最省心的方案)
比如Glide,它自带内存缓存、自动采样、生命周期管理,能完美解决你的问题,还不用自己处理复杂的Bitmap逻辑。示例代码:
Glide.with(context) .asBitmap() .load("http://your-image-url.com/image.jpg") .override(100, 100) // 直接指定目标尺寸 .into(new CustomTarget<Bitmap>() { @Override public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) { // 用这个Bitmap创建Marker Bitmap markerBitmap = drawableToIcon(context, resource, status); MarkerOptions markerOptions = new MarkerOptions() .position(latLng) .icon(BitmapDescriptorFactory.fromBitmap(markerBitmap)); mMap.addMarker(markerOptions); // 回收临时Bitmap if (!resource.isRecycled()) { resource.recycle(); } // 注意:如果Marker还在地图上显示,不要回收markerBitmap!可以在Marker移除时再回收 // if (!markerBitmap.isRecycled()) { markerBitmap.recycle(); } } @Override public void onLoadCleared(@Nullable Drawable placeholder) { // 清理资源 } });
Glide会自动帮你处理图片的下载、缓存、采样,还会根据页面生命周期自动释放资源,比自己写的代码稳定得多。
最后提醒一下:如果你的地图上有大量Marker,尽量复用Bitmap,避免每个Marker都创建新的Bitmap,这也是减少内存占用的关键。
内容的提问来源于stack exchange,提问作者Kalaimeow meow
相关产品推荐
相关产品推荐

