outofmemory_我如何修复由于我的应用程序中的Google地图上的自定义图块导致的OutOfMemory异常...

outofmemory

Hello, welcome to my first writeup. This is my experience with debugging an OOM error. I hope you learn something from it. Enjoy :)

您好,欢迎来到我的第一篇文章。 这是我调试OOM错误的经验。 希望您能从中学到一些东西。 请享用 :)

I’ve been working on a map app for cycling from some time. A few months back I’d added a feature to see custom map tiles from Open Street Maps inside GoogleMaps. A little background on map tiles from OSM. So OSM is basically a large data set of coordinates and metadata. You’d think OSM is maps, which it is, but its essentially just a ton of data. The latest Planet OSM XML file is over 91GB in size. The maps that you see are statically pre-generated png files. Thats why you’ll notice that when you rotate OSM maps, the labels and everything rotate as well, unlike on Google Maps which are vector maps. These pngs are called tiles and are generated on a daily or weekly basis on tile servers. It is ideally possible to create these tiles on-the-go but that ends up being a costly operation and is not done for the most often seen tiles.

我一直在开发地图应用程序,从一段时间开始骑自行车。 几个月前,我添加了一项功能,可以在GoogleMaps中的Open Street Maps中查看自定义地图图块。 OSM的地图图块上的一些背景。 因此,OSM基本上是由坐标和元数据组成的大型数据集。 您可能会认为OSM是地图,确实如此,但是本质上只是大量数据。 最新的Planet OSM XML文件大小超过91GB。 您看到的地图是静态预先生成的png文件。 这就是为什么您会注意到旋转OSM地图时标签和所有内容也会旋转的原因,这与矢量图谷歌地图不同。 这些png称为图块,并且每天或每周在图块服务器上生成。 理想情况下,可以随时随地创建这些图块,但这最终会导致成本高昂的操作,而对于大多数常见图块而言,这是不可行的。

OSM needs to pre-render these tiles for every zoom level on a map. If OSM pre-renders all tiles for all locations for all zoom levels, it would take up a whopping 54TB of space! So they do some interesting tricks to avoid that.

OSM需要针对地图上的每个缩放级别预渲染这些图块。 如果OSM为所有位置的所有缩放级别预渲染所有图块,则将占用高达54TB的空间! 因此,他们采取了一些有趣的技巧来避免这种情况。

To fetch these tiles, we access a url like this- https://tile.openstreetmap.org/8/187/108.pngThis represents baseurl + zoom/x/y. The x and y are not latitude longitudes but are identifiers for a tile for an area on the map using Mercator Projection. You can read about the related complex math here. Due to the massive number of tiles with increasing zoom level, OSM does not render tiles for zoom levels 20 and 21. One can do it themselves though if the space and computation power is available.

要获取这些图块,我们访问如下网址:https: //tile.openstreetmap.org/8/187/108.png这表示baseurl + zoom / x / y 。 x和y不是纬度经度,而是使用墨卡托投影的地图上某个区域的图块的标识符。 您可以在此处阅读有关的复杂数学。 由于缩放级别不断提高的大量图块,OSM不会渲染缩放级别20和21的图块。即使有足够的空间和计算能力,也可以自己完成。

Each of these tiles are 256x256 by default. This is very low res for mobile apps and ends up looking pretty awful. So HD tiles are used which are 512x512 pngs. Here’s a comparison between the two —

默认情况下,这些图块中的每个图块均为256x256。 对于移动应用而言,这是非常低的分辨率,最终看起来非常糟糕。 因此使用的高清图块是512x512 png。 这是两者之间的比较-

Image for post
Comparison between using 256x256 vs 512x512 map tiles
使用256x256与512x512地图图块的比较

Now back to Android. To render these tiles, we use a map tile overlay in Google Maps. As the term says, these are just overlays on top of Google Maps itself. To show OSM tiles, I set the google map to not be shown (map becomes blank) and then set the map to show any custom map tile overlay.

现在回到Android。 要渲染这些图块,我们在Google地图中使用了地图图块叠加层。 顾名思义,这些只是Google Maps本身的叠加层。 为了显示OSM磁贴,我将Google地图设置为不显示(地图变为空白),然后将地图设置为显示任何自定义地图磁贴叠加层。

googleMap.mapType = GoogleMap.MAP_TYPE_NONEgoogleMap.addTileOverlay(TileOverlayOptions().tileProvider(MyUrlTileProvider(this@MapsActivity, url)))

Since these are just overlays, you can use it on top of GoogleMaps itself to show anything. For eg, here I am showing bicycle routes on top of Google Maps and in the second picture, Strava heat map tiles with dark Google Map(a custom map style). Traffic layer works the same way. It’s just another overlay on top of the base Google Maps.

由于这些只是叠加层,因此您可以在GoogleMaps本身上使用它来显示任何内容。 例如,在这里,我在Google Maps上显示自行车路线,在第二张图片中,显示了具有深色Google Map( 自定义地图样式 )的Strava热地图图块。 流量层的工作方式相同。 这只是基础Google Maps的另一个叠加层。

Image for post
Image for post
Waymarked cycling trails map tiles on normal Google Map, Strava heat map tiles on Dark Google Map
普通Google地图上带有标记的自行车道地图图块,黑暗Google地图上的Strava热图图块

So the possibilities are endless. You can render hill shading with normal maps or show train tracks on dark maps etc etc. Rendering and showing all these kinds of maps and overlays is not the problem. The problem comes with showing many tiles (pngs) at once.

因此,可能性是无限的。 您可以使用法线贴图渲染山体阴影,也可以在深色地图等上显示火车轨迹。渲染和显示所有这些类型的贴图和覆盖图不是问题。 问题在于同时显示许多图块(png)。

Now about the bug. A few weeks back I started noticing these OutOfMemory crashes happening in random places in the app.

现在介绍该错误。 几周前,我开始注意到这些OutOfMemory崩溃是在应用程序中的随机位置发生的。

Image for post

The weirdest part was that they were happening at different places in the app completely unrelated to the map tiles. So pinpointing the issue became difficult. However on looking at the same event logs before the crash it was clear what was the issue.

最奇怪的是,它们发生在应用程序中与地图图块完全无关的不同位置。 因此,查明问题变得困难。 但是,在崩溃之前查看同一事件日志时,很清楚问题出在哪里。

Image for post

If you’re aware of how pngs use up space on the heap, you’ll know that the size of the png is irrelevant. The space a bitmap uses in the memory is number-of-pixels*bytes-per-pixel.

如果您知道png如何用完堆上的空间,您会知道png的大小无关紧要。 位图在内存中使用的空间是像素数*每像素字节数。

About the size of the tile pngs, the png size depends on 1. The resolution of the tile. 2. Amount of detail in the png.

关于图块png的大小,png大小取决于1。图块的分辨率。 2. png中的细节量。

So for eg, if you look at this HD tile from somewhere in the city, https://maps.wikimedia.org/osm-intl/11/1101/671@2x.png (61KB) is about 2 times bigger than a tile from the countryside https://maps.wikimedia.org/osm-intl/13/4407/2677@2x.png (23 KB). Another tile with hardly any detail is only (13 KB) https://maps.wikimedia.org/osm-intl/15/17636/10702@2x.png. So size of the tile png can highly vary.

因此,例如,如果您从城市某处观看此高清图块,则https://maps.wikimedia.org/osm-intl/11/1101/671@2x.png(61KB )大约是城市的2倍乡村地砖https://maps.wikimedia.org/osm-intl/13/4407/2677@2x.png(23 KB)。 几乎没有任何细节的另一个磁贴仅为(13 KB) https://maps.wikimedia.org/osm-intl/15/17636/10702@2x.png 。 因此,图块png的大小可能会有很大差异。

Image for post

When it comes to render these tiles on GoogleMaps using a TileProvider, the size of the bitmaps on the heap also vary.

当使用TileProvider在GoogleMaps上呈现这些图块时,堆上位图的大小也有所不同。

A few tiles wont matter, but when building a route on this app (thats what my app does- a cycling route builder/planner), the user tends to look at many areas and on various zoom levels. Hundreds of tiles being loaded on the map quickly adds up in memory.

几个图块无关紧要,但是在此应用程序上构建路线时(这就是我的应用程序所做的-自行车路线构建器/计划程序),用户倾向于查看多个区域并处于各种缩放级别。 数以百计的图块正在地图上快速加载到内存中。

Looking at just maps around Ahmadabad, India and loading only 200 tiles added about 30MB to the heap! The longer a user uses the app and looks at more maps, it keeps adding up pretty quickly.

仅查看印度Ahmadabad周围的地图,仅加载200个图块,就会增加大约30MB的空间! 用户使用该应用程序并查看更多地图的时间越长,它的添加速度就越快。

You must be wondering, that 200 images is nothing and that you’ve loaded many more images in your app. If you use a library like Glide, it manages your memory very well and thats why you never see an OutOfMemory exception unless you load a really high res image. However, when loading tiles on the map, its the GoogleMaps object and the TileProvider which do the memory management. I use Glide to load and cache the png but thats where its responsibility ends. GoogleMaps on the other hand, does not manage the memory well for overlay tiles. Ideally it should clear tiles which are not visible anymore, but it does not.

您一定想知道,那200张图像是什么,而您已在应用程序中加载了更多图像。 如果您使用像Glide这样的库,它可以很好地管理您的内存,这就是为什么除非加载真正的高分辨率图像,否则您永远不会看到OutOfMemory异常的原因。 但是,在地图上加载图块时,其GoogleMaps对象和TileProvider会执行内存管理。 我使用Glide加载和缓存png,但这就是它的职责结束的地方。 另一方面,GoogleMaps不能很好地管理叠加图块的内存。 理想情况下,它应该清除不再可见的磁贴,但现在不再可见。

The OOM crash was happening on random but few devices overall. So the one thing I’m unsure of is if this crash were happening on those devices specifically because of its low RAM and aggressive memory caps for apps customised by the OEM or were they because the users on those devices loaded too many tiles.

OOM崩溃是随机发生的,但总体而言很少。 因此,我不确定的是,这些崩溃是否发生在这些设备上,特别是由于其内存不足以及OEM定制的应用程序的内存容量过大,还是因为这些设备上的用户加载了太多的图块。

One thing was for clear though, that google maps did not clear any tiles from the memory as you can see in this gif from the profiler.

不过有一点很明确,那就是Google地图并没有从内存中清除任何切片,正如您在探查器的gif中看到的那样。

Image for post
Increasing memory occupied by Graphics as more tiles are loaded
随着加载更多图块,增加了Graphics占用的内存

Now onto how to clear all these tiles from memory. My first approach was to call System.gc(). I placed this call at various strategic places. However, this did not make any difference. As you can see in the gif, there are many points at which garbage collection happens on its own trigged by the system. This reduces the Native memory and Java memory, but makes no difference on the Graphics memory which continues to increase with more tiles loaded.

现在介绍如何从内存中清除所有这些磁贴。 我的第一种方法是调用System.gc() 。 我将此电话放在各个战略要地。 但是,这没有任何区别。 正如您在gif中看到的那样,在很多地方,垃圾收集都是由系统触发的。 这减少了本内存和Java内存,但是在图形内存上没有任何区别,随着更多的切片加载, 图形内存继续增加。

The second idea I had to solve this problem was use android:largeHeap="true" in the manifest. This wouldn’t be a solution really as much as mitigating the problem. And I didn’t want to add this fearing additional issues that could happen on devices in the wild.

我必须解决的第二个想法是在清单中使用android:largeHeap="true" 。 这实际上不是减轻问题的解决方案。 而且我不想再加上担心在野外设备上可能发生的其他问题的信息。

I found the solution in this comment. googleMap.clear() was the solution. This call basically clears all overlays, all markers and polylines displayed on the map. This worked like a charm.

我在此评论中找到了解决方案。 googleMap.clear()是解决方案。 此调用基本上清除了地图上显示的所有叠加层,所有标记和折线。 这就像一个魅力。

Image for post
Graphics memory dropping after map.clear()
map.clear()之后图形内存下降

googleMap.clear() makes a massive difference. Before- 161.1MB and after call- 36.1 MB. Only one piece to the puzzle remains- at what point do I trigger the clear() call. The answer was onTrimMemory.

googleMap.clear()有很大的不同。 通话前161.1MB和通话后36.1 MB。 剩下的只有一小部分-我在什么时候触发clear()调用。 答案是onTrimMemory

Android provides a callback to let you know when the app is running low on memory so that one can take actions to mitigate that. It also provides info on what level of memory cleanup needs to happen. Its important to use this. Simply calling your code in onTrimMemory will wreak havoc as this callback is invoked at various points in the app lifecycle. It is even called after onStop. You can read all about managing memory here.

Android提供了一个回调函数,可让您知道应用程序的内存不足情况,以便人们可以采取措施缓解这种情况。 它还提供有关需要进行什么级别的内存清理的信息。 使用它很重要。 在onTrimMemory简单地调用您的代码将会造成严重破坏,因为此回调在应用程序生命周期的各个点被调用。 它甚至在onStop之后被onStop 。 您可以在此处阅读有关管理内存的所有信息。

override fun onTrimMemory(level: Int) {
when (level) {
ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL -> {
//Clearing the map
googleMap.clear()

//This resets the previously shown tile overlay
setMapTileOverlay()

/*
Here I re-add the polylines and markers which were
removed from the map
*/
presenter.reshowCurrentRoute()
}
else -> {
super.onTrimMemory(level)
}
}
}

I decided to handle only CRITICAL memory scenarios which happens when the app is about to crash due to OOM. Handling MODERATE and LOW memory situation was a bad UX wherein the map and all the markers and polylines and map tiles were being re-added often and creating a janky experience.

我决定只处理在应用程序因OOM而崩溃时发生的关键内存方案。 处理MODERATE和LOW内存情况是一个糟糕的UX,其中经常重新添加地图以及所有标记,折线和地图图块,从而产生了混乱的体验。

Thanks for reading :)Thanks to Sudhir Khanger for the idea to write a blog about this!

感谢您的阅读:)感谢Sudhir Khanger撰写有关此主题的博客的想法!

You can check out this app which I’m working on here- https://play.google.com/store/apps/details?id=abhiank.maplocs

您可以在此处查看我正在使用的此应用-https ://play.google.com/store/apps/details? id = abhiank.maplocs

翻译自: https://medium.com/swlh/how-i-fixed-an-outofmemory-exception-using-custom-tiles-on-google-maps-in-my-app-5b019f6b652e

outofmemory

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值