Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nativescript Mapbox on iOS retain memory after navigation away #67

Closed
ahalls opened this issue Dec 23, 2016 · 27 comments
Closed

Nativescript Mapbox on iOS retain memory after navigation away #67

ahalls opened this issue Dec 23, 2016 · 27 comments
Milestone

Comments

@ahalls
Copy link
Collaborator

ahalls commented Dec 23, 2016

I'm using Angular 2, with router-outlet navigation. My app is killed by the OS for using too much memory after I navigate away from the page with the MapBox map and return. It's clear that the memory associated with the Mapbox instance is not being released.

I put a log statement in ngOnDestroy() and it is being called when I expect it to be. Is there anything I can do to force the SDK to release memory?

I'm instantiating the map via XML


    <GridLayout  #mapcontentview  rows="*" columns="*" >
        <Mapbox
            accessToken=MYTOKEN
            latitude="39.9780"
            longitude="-82.4182"
            zoomLevel="12"
            showUserLocation="true"
            (mapReady)="onMapReady($event)">
        </Mapbox>
     </GridLayout>

This component is instantiated in the page via Angular2 router-outlet navigation.

How can I further debug this issue?

@ahalls
Copy link
Collaborator Author

ahalls commented Dec 27, 2016

Created a sample project with this problem.
https://github.com/ahalls/mapboxtest

@EddyVerbruggen
Copy link
Collaborator

Thanks for the sample!

When attaching a profiler and going back and forth between those 2 pages I can see that memory builds up. Looks like JS is keeping a reference (possibly Angular even).

Can you try changing app/tap.component.ts to:

import { Component } from "@angular/core";
import {RouterExtensions} from "nativescript-angular/router";;
import * as utils from "utils/utils";

@Component({
    selector: 'tap',
    templateUrl: "tap.component.html",
})
export class TapComponent {
    public counter: number = 16;

   constructor(
        private _routerExtensions: RouterExtensions
    ) { }

    public get message(): string {
        if (this.counter > 0) {
            return this.counter + " taps left";
        } else {
            return "Hoorraaay! \nYou are ready to start building!";
        }
    }

    public onTap() {
        this.counter--;
        console.log("--- doing GC");
        utils.GC();
    }

    public onMapTap() {
        this._routerExtensions.navigate(["/map"]);
//            <any>  { clearHistory: true });
    }
}

And tap the 'tap' button after each visit to the map? Curious to learn if that fixes your crash as utils.GC() should clear any lingering JS garbage, in turn releasing the native references.

@Stavanger75
Copy link

@ahalls do you have the same problem if you define maps in code instead of XML ? I have 2 projects running mapbox plugin in classic JS and Typescript. The one with map defined in XML is eating a lot of memory and gets killed. The other project has map defined in code and is running fine. (eating memory but at much lower pace). I have not checked this further since maps defined in code is working for me at the moment.

@ahalls
Copy link
Collaborator Author

ahalls commented Dec 30, 2016

@Stavanger75 I have not tried creating the map from code. I need to place the map in the context of other components. I didn't see a way to find a container and place the map in the container from code. Is there a way?

@ahalls
Copy link
Collaborator Author

ahalls commented Dec 31, 2016

@EddyVerbruggen directly calling a GC() a little bit after the navigating from the Map page with the significantly reduces the growth of the memory footprint of the app. There is still leaked memory ~20MB instead of ~300 MB as I had before. It was even worse until I started using navigate() from the RouterExtensions instead of the the Angular2 Router. It might make the app acceptable to my client. But still there seems to be some dangling references holding onto memory. It seems to be related to the mapbox plugin, because if I remove instantiation of the map, memory loss, when I force the GS the growth if any is only a few KB.

@EddyVerbruggen
Copy link
Collaborator

Thanks for confirming that. I think for now you'll need this silly workaround since getting to the rootcause will be very hard. The leak may be caused by many things and thus will take quite some time to figure out.

@Stavanger75
Copy link

@ahalls I have not used Angular 2 i am afraid only 1.x so i can unfortunately not help you with creating angular component. I use classic JS with Typescript only. Good calling GC() helped on some of the memory leak. Hope you get it sorted out.

@ahalls
Copy link
Collaborator Author

ahalls commented Jan 2, 2017

I've found a similar issue with Android as well.

@tskweres
Copy link

tskweres commented Feb 4, 2017

This issue solved? Or does anyone know of another plugin without the memory leak?

My entire app is the map so it's critical :(

@EddyVerbruggen
Copy link
Collaborator

@tskweres Did you try the GC trick?

@tskweres
Copy link

@EddyVerbruggen client put the project on hold :/ Been watching this thread though to see any updates, I love Mapbox and use it heavily on the web

@ahalls
Copy link
Collaborator Author

ahalls commented Mar 31, 2017

@EddyVerbruggen Still having problems with this issue. Calling GC() all over the place.

@ahalls
Copy link
Collaborator Author

ahalls commented Apr 1, 2017

@EddyVerbruggen I'm going to try to call mapbox.destroy() but having trouble finding the handle to the mapbox object when I create it via XML/Angular2

    @ViewChild("mapboxview") mapboxViewRef: ElementRef;

   ngOnDestroy() {
       console.log("MapComponent::ngOnDestroy");
       console.dump(this.mapboxViewRef.nativeElement);
       this.mapboxViewRef.nativeElement.destroy();

       // setTimeout(() => this.cleanupAfterDelay(), 500)
        
    }
    cleanupAfterDelay() {
        console.log("MapComponent::cleanupAfterDelay:GC");
        GC();
    }

the destroy() call return undefined method on object

@EddyVerbruggen
Copy link
Collaborator

@ahalls Can you please try that again with 2.6.1?

@ahalls
Copy link
Collaborator Author

ahalls commented Apr 1, 2017

With the above referenced test app.

I do a GC on ngInit ...
I'm loosing only 5MB on a round trip. This is a big improvement over ~350MB I was loosing.

It is probably good enough for our application. But not for a full scale (web scale) production app.

I don't think you should close this issue.

@simonettoa
Copy link

hey @ahalls, did you find any solutions or workaround? I trying different things to solve this memory issue. The problem in our app is that it is in a RadSideDrawer, so the map could be called several times and so memory increase continuosly; the gc command is helping but not a lot. any ideas? thanks in advance

@EddyVerbruggen
Copy link
Collaborator

Why do you have multiple instances of the sidedrawer?

@simonettoa
Copy link

hi @EddyVerbruggen and thanks for the quick reply.
This app has a vehicle page and trailers page (and others), at the beginning it appears as list but clicking on a right button bar the map appears with the position of the vehicles as markers.
in the details page of a vehicle I have a button to see the POIs of the selected vehicle, so here another instance of the map.

But the problem is not the multiple instance of the map, in another app there is just the map page in the menù and others pages. Still entering in the map page and in another and then back in the map page the memory increases, so there I'm using the utils.gc() and I'm destroying the map.

I was thinking on creating the map by code in the app.component and showing and hiding when I need it and not calling every time in a different html file.
Do you suggest any other way to call the map? I was thinking a modal page or as I wrote above calling it not in html but in app.component and showing it when I need,
not sure if it'll work

@EddyVerbruggen
Copy link
Collaborator

I'm not sure what exactly causes the map to be retained. I mean, when the page goes off the stack and is destroyed and gc'd, the map should be removed from memory as well. If that's not the case then I'm not sure why exactly.

@simonettoa
Copy link

simonettoa commented Apr 7, 2018

@EddyVerbruggen I've created a repository here to test what I wrote above.

https://github.com/simonettoa/my-drawer-ng

In the readMe file there are the instructions and also some tests data I've made. Now I will try a different approach to figured it out this memory retain issue. If you have any suggestions it will be very appreciated


UPDATE: I've updated the solution posted above and calling the map box through a modal page is the best solution to clear the memory(it's still increasing but not as before) ; at least in ios; didn't try in android but I assume it is the correct way to proceed! if anybody has a better solution post it, thanks

@simonettoa
Copy link

I've tested the android part and is not working, the map doesn't appear in the modal page, it appears in the browse page (iOS works), any clue why?

@EddyVerbruggen
Copy link
Collaborator

@simonettoa Did you update the demo to run in a modal? Looks like the current version on Github sits inside a sidedrawer.

@simonettoa
Copy link

@EddyVerbruggen I've just updated the project.

the modal page solution doesn't work in android. Tested to add the map programatically and use the hide and unhide property. It works good in ios, it doesn't add any memory because the map is created once. In android it does not work when you change the page.

To test it go from the home page to search page, click on the actionBar map's icon, first time you'll see the map, click on the menu on the top left the hide property is called, go to settings page and back to search, click again you'll see the map (in this page is just using hide and unhide). In android the same behaviour is not replied, you'll see the first time the map and the click on the map's icon is playing with hide/unhide (of the local page mapbox) - click on the menu will set the unhide to the app.component map and the current one; the click again will fires the unhide on the app.component map but it won't appear.

In featured page it works little different, when you click on the menu icon the destroy fuction is called; it will work logically but the every time the map is called the show function is also and the memory will increase as usual.

Still looking to a good solution...

@simonettoa
Copy link

Looking inside the library I can see that in ios the functions hide/unhide call removeFromSuperview/addSubview. In android it uses to change the setVisibility of the view (INVISIBLE/VISIBLE). Probably nativescript is putting the current page on top. I'm trying to see if I can do a similar thing adding and removing the map in android as it's done in ios .


Strange behaviour that I've noticed and maybe could help to find out a solution; now the demo I've done uses to hide and unhide the map through the current page component based on the shared map's app.component. If I try to use directly the hide function on the 'shared' map object it won't re-appear.

@simonettoa
Copy link

SOLUTION FOUND!!!

Basically to make it work also in android the solution was to change in app.component.html from page-outer-outlet to outer-outlet. Connected with this change is also necessary to fix some stuff in order to have the graphic to appear properly.

Right now from the home page and going to browse or search page you'll see a map icon on right-top, clicking on it and the map will appear with two markers. Going to the another page will hide the map and going to the search page and clicking on the map's icon the map will appear and different markers.

The project shared in older post is updated.

@EddyVerbruggen
Copy link
Collaborator

Thanks for sharing, @simonettoa!

I'm about to release plugin version 4.0.0 which includes Mapbox iOS SDK version 4.0.0 which (as far as I can see from their changelog) contains a bunch of memory leaks. Perhaps that will help as well. Closing for now.

@NarenderKhod
Copy link

Hey,

Have you released the plugin?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants