Android天气应用GroundOverlay内存过高引发OOM问题求助
解决Android天气应用动画导致的OOM问题
哥们,你的这个OOM问题我太熟了——虽然PNG文件看着是KB级,但Bitmap解码后是按像素计算内存的(比如ARGB_8888格式下,1080×1920的Bitmap就占约8MB),要是一次性把所有帧的Bitmap都存进HashMap,内存肯定扛不住。给你几个实用的解决方案,组合起来用应该能彻底解决问题:
1. 按需加载+及时回收Bitmap,不要全量缓存
不要一次性加载所有帧并永久存在HashMap里,只保留当前显示帧和前后1-2帧的资源,其他帧用完就立刻回收Bitmap并移除对应的GroundOverlay,释放内存。
你可以先把HashMap的存储对象改成包含Bitmap和GroundOverlay的自定义类,方便后续统一管理:
// 自定义类,封装Bitmap和对应的GroundOverlay static class WeatherOverlayHolder { Bitmap bitmap; GroundOverlay overlay; }
然后在切换动画帧的逻辑里,主动回收上一帧的资源:
// 假设currentTime是当前显示的帧时间,nextTime是要切换的目标帧时间 WeatherOverlayHolder currentHolder = groundOverlaysItemMap.get(currentTime); if (currentHolder != null) { // 回收Bitmap if (currentHolder.bitmap != null && !currentHolder.bitmap.isRecycled()) { currentHolder.bitmap.recycle(); currentHolder.bitmap = null; } // 移除地图上的Overlay currentHolder.overlay.remove(); // 从HashMap中删除,避免内存泄漏 groundOverlaysItemMap.remove(currentTime); } // 加载并显示下一帧...
2. 让Glide加载缩小后的Bitmap,减少单帧内存占用
根据GoogleMap的实际显示区域,计算需要的Bitmap尺寸,让Glide加载时直接缩小图片——毕竟地图上显示的区域有限,没必要用全分辨率的图。
代码示例:
// 获取当前地图可见区域的像素尺寸 Projection projection = mMap.getProjection(); VisibleRegion visibleRegion = projection.getVisibleRegion(); Point northeastPoint = projection.toScreenLocation(visibleRegion.latLngBounds.northeast); Point southwestPoint = projection.toScreenLocation(visibleRegion.latLngBounds.southwest); int targetWidth = northeastPoint.x - southwestPoint.x; int targetHeight = southwestPoint.y - northeastPoint.y; // 注意Android坐标系y轴方向 // 用Glide加载时指定目标尺寸,避免加载过大的Bitmap Glide.with(this) .asBitmap() .load(yourImageUrl) .override(targetWidth, targetHeight) // 可乘以1.5f保证清晰度,比如(targetWidth*1.5f).intValue() .into(new CustomTarget<Bitmap>() { @Override public void onResourceReady(@NonNull Bitmap bitmap, @Nullable Transition<? super Bitmap> transition) { Log.i("Weather", "优化后Bitmap大小: " + bitmap.getByteCount()); // 后续创建GroundOverlay并存入HashMap WeatherOverlayHolder holder = new WeatherOverlayHolder(); holder.bitmap = bitmap; holder.overlay = mMap.addGroundOverlay(overlayOptions); groundOverlaysItemMap.put(mTime, holder); } @Override public void onLoadCleared(@Nullable Drawable placeholder) {} });
3. 使用低内存的Bitmap解码格式
如果你的天气图标不需要全透明通道(或者可以接受半透明损失),可以把Bitmap的解码格式从默认的ARGB_8888改成RGB_565,这样每个像素只占2字节,内存直接减半。
代码示例:
Glide.with(this) .asBitmap() .load(yourImageUrl) .setBitmapDecodeFormat(BitmapDecodeFormat.RGB_565) // 设置低内存格式 .override(targetWidth, targetHeight) .into(new CustomTarget<Bitmap>() { // ... });
4. 用LruCache限制缓存大小
自定义LruCache来管理Bitmap缓存,设置最大缓存容量(比如应用可用内存的1/8),这样超过容量的Bitmap会被自动回收,避免内存溢出。
代码示例:
// 计算最大缓存大小:应用最大可用内存的1/8,单位KB int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); int cacheSize = maxMemory / 8; LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { // 返回Bitmap的大小,单位KB return bitmap.getByteCount() / 1024; } }; // 加载Bitmap时先从缓存取,没有再从网络加载 Bitmap cachedBitmap = bitmapCache.get(yourImageUrl); if (cachedBitmap != null) { // 使用缓存的Bitmap创建Overlay } else { Glide.with(this) .asBitmap() .load(yourImageUrl) .into(new CustomTarget<Bitmap>() { @Override public void onResourceReady(@NonNull Bitmap bitmap, @Nullable Transition<? super Bitmap> transition) { bitmapCache.put(yourImageUrl, bitmap); // 后续处理... } @Override public void onLoadCleared(@Nullable Drawable placeholder) {} }); }
额外注意事项
- 在Activity/Fragment的
onDestroy()方法里,一定要清空groundOverlaysItemMap,并回收所有剩余的Bitmap,避免内存泄漏。 - 可以结合Glide的内存缓存策略,比如禁用Glide的内存缓存(如果自己用LruCache管理的话),避免重复缓存。
内容的提问来源于stack exchange,提问作者Ashwin Khadgi
相关产品推荐
相关产品推荐

