mapsplugin / cordova-plugin-googlemaps

Google Maps plugin for Cordova
Apache License 2.0
1.66k stars 916 forks source link

Performance problems when adding many markers. #835

Closed atannus closed 8 years ago

atannus commented 8 years ago

I'm proposing a patch to the PluginMarker.java file that will greatly improve the performance when adding hundreds of markers to the map.

I propose the creation of a createMarkers method (note the plural), which will take the same marker definition as does the currently available createMarker, except for the fact they'll be in an array, therefore allowing the creation of multiple markers with a single over-the-bridge request. Naturally, the callback has to return an array of markers, instead of a single marker.

A private createMarker_ method is to be called by createMarkers. This does not break the current API, and the current createMarker method can be refactored to simply call the private version with its argument in an array.

I have already implemented marker caching and marker pre-loading which on top of that will drastically reduce memory consumption and prevent leaks.

This is where my understanding of this project ends. I don't know what my constraints as a developer are:

I'm seeing fantastic performance with these improvements, I'd really like to contribute them.

Thanks a lot for the help.

wf9a5m75 commented 7 years ago

@blckshrk Specify the same icon as much as possible. The v2 creates image cache internally when the icon properties (url and size) are the same.

guillenotfound commented 7 years ago

I also asked you about this when you released v2 @wf9a5m75 , there's something causing slow drawing of markers on the map in v2.

Point 5: https://github.com/mapsplugin/v2.0-demo/issues/6#issuecomment-253334483

But your answer about that point didn't help so much... https://github.com/mapsplugin/v2.0-demo/issues/6#issuecomment-253339084

I'd love to use v2 since I believe that it will work much better than v1, but adding lots of markers it's a common task, that HUGE time impact will be bad for UX...

alexbonhomme commented 7 years ago

Hey guys!

@wf9a5m75 I used the default icon for my tests (so no icons properties) and on the initial app each marker have the same icon object (with same url/size properties)

@ZiFFeL1992 I agree, adding more than 100 marker have a really huge impact on performances, but it seems limited to the Android version (to be confirmed)

alexbonhomme commented 7 years ago

Hi,

I've taken a day to dig the problem and I found some issues.

Observations

capture d ecran 2017-02-07 a 09 18 49 capture d ecran 2017-02-07 a 09 24 10

Interpretation

I suspect a memory leak in the ResizeTask (and maybe somewhere else), which lefts no memory for the WebView. So the WebView needs to wait for the CG to dequeue the waiting callbacks. I have a Nexus 5, so that could explain why I'm facing this problem and not you (less RAM). That also makes sense with the exponential evolution of the creation time according to the markers number.

Conclusion

I think we need to reduce the memory usage and fix the memory leaks to solve this problem. My advice is to avoid new as possible and maybe use iterators.

What do you think about ?

wf9a5m75 commented 7 years ago

@blckshrk If you have find any problem, please fix it, and send it as a pull request with test code.

bmcbride commented 7 years ago

I'm also experiencing poor performance on Android with many (~400) markers in v2. Switching from v2 to v1 cuts loading time from 19 seconds to under 2 seconds. Has there been any progress on resolving this? I'm happy to help test, but I'm pretty sure resolving it is beyond my skill. Thanks!

alexbonhomme commented 7 years ago

@bmcbride As described above, I observed the same problem, but can't found a solution...

sam2x commented 7 years ago

Hey i bring my own experience in this thread. I also have some issue with v2 when i use custom marker.
Here the contexts & tests:

Environment:

Cordova: 6.5.0
Cordova-plugin-googlemaps: 2.0.0-beta 
Android platform SDK : 6.1.2
Google Map SDK Google.android.gms:play-services-maps: 10.2.1
Phone test: Galaxy A5 

Application Context

Angular 1.6.4 using cordova, with 2views. The home view, and the Map view. Apk size is around 10Mo.

Test performed

I just switch between Home view and Map view. At each switch i check memory.

Test1: not custom marker

Test1 has 27 visibles and 39 invisibles markers during the whole test.

  1. Home: ~ 110 Mo (cordova + angular is known to have an important memory footprint, so no surprise here)
  2. Map: vary 175-185 Mo : not moving the map
  3. Home: ~ 160Mo (waited 30sec in case GC need some time)
  4. Map: vary 175-190 Mo : not moving the map (almost same as step2)
  5. Home: ~ 178Mo (waited 30sec in case GC need some time) ~ 162Mo (after 1min, almost same as step 3)

-> Looping between these two view seems to give almost the same results, which seems "legits" to me.

Test2: all markers are custom, size option is set to 32x32 (~10 differents png images, dimensions 36x36, between 1.5Ko and 2Ko each, and stored locally)

Test2 has the same marker env as test1 => 27 visibles and 39 invisibles markers. Differents markers may set the same png.

  1. Home: ~110 Mo
  2. Map: vary 175-185Mo (same result as previous test! seems good)
  3. Home: ~175-180Mo (hmmm not decreasing...)
  4. Map: vary 205-220 Mo (should be around the range value of step 2!? we got more than 20Mo here)
  5. Home: ~ 205 Mo (something is wrong)
  6. Map: vary 225-240 Mo (something is definitely wrong)
  7. Home: ~ 225 Mo (again ~20Mo memory seems to be retained somewhere).
  8. etc...

-> Looping between these two view increase memory, leading sooner or later the app to be killed by the system. So memory leaks likely occurs somewhere.

How i leave the Map view ?

I use destroy event in the Map view, in this function i do the following actions :

Statements/Questions

Imho there is two distincts "statements":

  1. No matter the tests, there is ~60Mo that are persistents when we load the map, and even after destroying the map they still remains. (see steps 1, 2, 3)
  2. Memory allocation seems to not be released when creating new custom markers and destroying them (see tests 2).

So :

  1. Is this the GoogleMapService module that get loaded/mapped in memory (due to lazy loading ?) ? It would be normal behaviour so (isnt this kind of library shared accross all app in the phone and shouldnt appear as "consumed memory" in my app memory profil ? Not sure...). 1'. Or some kind of cached data or others stuff not released/freed ? Any ideas ? Do you have the same behaviour ? Well not really my priority, but if you know, i'll appreciate.
  2. Markers: I think there is memory allocation that is not freed when using custom marker image. So the ressources is not cleaned despite my destroy step. I'm not sure where/how, have to look in the this plugin code (i'm not java reader, but i'll give a try).

Anyway, any pointers/ideas from your point of view ?

Regards

alexbonhomme commented 7 years ago

@sam2x Imho, this is because the memory leaks are in the native code. So, destroy JS listeners won't clear native memory (I may said something wrong, I haven't looked at the remove() method)

Additionally, it seems that during the markers creation step some leaks appears (may some references aren't freed somewhere en lost after, waiting for a garbage collection for example).

Best, Alex

sam2x commented 7 years ago

@blckshrk thanks you for this notice. Yes, i'm aware this could be the native code guilty. However I'm trying first to take a deep look at the plugin (even if i'm not Java expert). Also It looks odd to me that so much memory is leaked from native code, in such simple "test-case", someone would have noticed if it was from native code (also i'm using last google-map sdk) ?

I'm investigating the way this plugin is dealing with BitMap (since it looks like a good culprit to me):

According the Android guideline Bitmap can "very easily exhaust an app's memory budget". They recommend to use the Glide library to "fetch, decode, and display bitmaps".

Looking at the code, i dont understand something. When creating a marker, with icon option specified, the setIcon method is called. Since we specified a size, this method call an util method resizeBitmap. However in this resizeBitmap function we dont call "bitmap.recycle()" like scaleBitmapForDevice is doing.

@wf9a5m75 Isnt "bitmap.recycle()" missing here ? According my understanding from http://stackoverflow.com/questions/3823799/android-bitmap-recycle-how-does-it-work when you assign a new image, if you dont explicitely call "recycle()" the old one is still in memory.

I tried to rebuild my app without specifying the size (and let the plugin computes it via scaleBitmapForDevice which call recycle()) but result is still same as test2, so it seems to not solve the prob :/

So:

  1. i'm still investigating to see if there isnt any forgotten reference to Bitmap that need to be cleaned.
  2. Any reason why Glide is not used ? Will there be any benefit using "Glide" in this plugin ?
  3. The Bitmap cache system is only used for remote ressource (see above the description https://github.com/mapsplugin/cordova-plugin-googlemaps/issues/835#issuecomment-251792249). Why not also use it for local ressource ? It should load faster marker that use the same bitmap ?
wf9a5m75 commented 7 years ago

@sam2x Before asking easily, please try to debug (such as insert bitmap.recycle()) at first.

wf9a5m75 commented 7 years ago

@sam2x ok, could you make another issue at once? This thread is too long. And please make an example project that reproduce your memory issue 100%, then share it on github or bitbucket.

sam2x commented 7 years ago

Is there a way to dicuss in PM about this ? i'm willing to financially contribute for your free time with this issue and make the effort/result available for the community. I know we all have priorities, and can't afford time easily.

wf9a5m75 commented 7 years ago

Please send email to me (see my github profile page).

wf9a5m75 commented 7 years ago

Since this thread is too long, I lock this thread. If you have any problem with v2, please make another issue at the issue list page.