【问题标题】:Titanium MapView Not Releasing MemoryTitanium MapView 不释放内存
【发布时间】:2011-06-30 17:15:13
【问题描述】:

我有一个使用 Appcelerator Titanium 构建的应用程序(适用于 iOS,Ti 和 Apple 的最新 SDK),其中一部分严重依赖于地图。我遇到的问题是,当我关闭包含 MapView 的窗口时,内存似乎没有释放。因此,在菜单屏幕和地图之间来回切换会降低 iPhone 的速度,直到它最终完全停止响应(加载 3-5 次地图)。

我使用Titanium 的Ti.Platform.availableMemory 调用来查看进入带有地图的窗口以及地图关闭后的内存。结果是随着每次连续进入/退出而呈现稳定的下降趋势,大致如下:

25(map.js 的初始加载)
20(注释后)
20(win.close()之后)
19(map.js 的第二次加载)
18(注释)
19(离开)
18(进入)
16(注释)
15(离开)

在模拟器中,当窗口关闭时,它可能会上升一点,但即使它显示出稳定的下降趋势。

这是地图的代码,位于它自己的“map.js”文件中。我已将其精简为使用的功能代码(因此这里只有 button_index 的事件侦听器)。

button_index.addEventListener('click', function()
{
    Ti.App.xhr.abort();
    if (mapview) {
        mapview.removeAllAnnotations();
        mapview = null;
    }
    if(policeJson){
        policeJson = null;
        fireJson = null;
    }
    Ti.App.police = false;
    Ti.App.types = null;
    win.close(); //This should clean up everything, according to the docs
    Ti.API.info('Memory: ' + Ti.Platform.availableMemory);
});

var mapview;// = Ti.App.mapview;

Titanium.Geolocation.purpose = "Recieve User Location";
Titanium.Geolocation.accuracy = Titanium.Geolocation.ACCURACY_BEST;
Ti.API.info('Memory: ' + Ti.Platform.availableMemory);

function getMarkers(e){
    var miles = Ti.App.miles;
    Ti.API.info("Getting markers");
    //Google's API radius is in meters, so we need to convert
    var radius = miles * 1610; // 1mi = 1609.344 meters, so we just round up to 1610.
    // http connection setup
    Ti.App.xhr.setTimeout(10000);

    googleLatLng = e.coords.latitude + "," + e.coords.longitude;

    Ti.App.xhr.onload = function()
    {
        var data = Ti.XML.parseString(this.responseText);

        var ref = data.documentElement.getElementsByTagName("reference");
        if(ref != null && Ti.App.xhr.readyState == 4){
            for(var i =0; i < ref.length; i++){
                var marker = new Object();
                marker.lat = data.documentElement.getElementsByTagName("lat").item(i).text;
                marker.lng = data.documentElement.getElementsByTagName("lng").item(i).text;
                marker.name = data.documentElement.getElementsByTagName("name").item(i).text;
                marker.ref = ref.item(i).text;
                addMarker(marker);
                marker = null;
            }
        }
    };
    Ti.App.xhr.open("GET","https://maps.googleapis.com/maps/api/place/search/xml?location=" + googleLatLng + "&radius=" + radius + "&types=" + Ti.App.types + "&sensor=true&key=" + Ti.App.apiKey,false);

    Ti.App.xhr.send();
    Ti.API.info('Memory: ' + Ti.Platform.availableMemory);
}

// find the user's location and mark it on the map
function waitForLocation(e)
{
    var region = null;
    if ( e.error ) {
        region = regionDefault; // User didn't let us get their location
        var alertDialog = Titanium.UI.createAlertDialog({
            title: 'Geolocation',
            message: 'We were unable to center the map over your location.',
            buttonNames: ['OK']
        });
        alertDialog.show();
    } else {
        region = {
            latitude: e.coords.latitude,
            longitude: e.coords.longitude,
            animate:true,
            latitudeDelta:0.05,
            longitudeDelta:0.05
        };
    }
    Ti.App.lat = region.latitude;
    Ti.App.lng = region.longitude;
    mapview.setLocation(region);

    mapview.removeAllAnnotations();
    currentLoc = Titanium.Map.createAnnotation({
        latitude: region.latitude,
        longitude: region.longitude,
        title: e.error ? "Columbus" : "You are here!",
        pincolor: Titanium.Map.ANNOTATION_RED,
        animate:true
    });
    mapview.addAnnotation(currentLoc);
    mapview.selectAnnotation(currentLoc);
    mapview.addEventListener('click', function(e){
        if (e.clicksource == 'rightButton') {
            if (e.annotation.spotUrl != '') {
                alert('Website!');
            }
            else {
                alert('No website available');
            }
        }
    });

    if(Ti.App.police == true) {
        var fire_img = "../../images/iNeighborhood/fire.png";
        var police_img = "../../images/iNeighborhood/police.png";
        serviceMarkers(fire_addr, fire_title, fire_lat_1, fire_long_1,fire_img);
        serviceMarkers(police_addr, police_title, police_lat_1, police_long_1,police_img);
    }

    getMarkers(e);
}


function addMarker(marker){
    var ann = Titanium.Map.createAnnotation({
        animate:true,
        latitude:marker.lat,
        longitude:marker.lng,
        title:marker.name,
        pincolor: Titanium.Map.ANNOTATION_GREEN
    });

    mapview.addAnnotation(ann);
}

// Automatically refresh current location.
/*
 * IN PROGRESS
 */
function getLocation(){
    // create the mapView and center it on Columbus
    if (!mapview) {
        mapview = Titanium.Map.createView({
            mapType: Titanium.Map.STANDARD_TYPE,
            animate: true,
            region: {
                latitude: 39.961176,
                longitude: -82.998794,
                latitudeDelta: 0.1,
                longitudeDelta: 0.1
            },
            regionFit: true,
            userLocation: true,
            visible: true,
            top:29
        });

        //Ti.App.mapview = mapview;

        win.add(mapview);
    }
    refresh();

    //Get the current position and set it to the mapview
    Titanium.Geolocation.getCurrentPosition(waitForLocation);
}
getLocation();

// pretty self explanatory...
function cleanMap(){
    if (mapview) {
        mapview.removeAllAnnotations();
    }
    if(xhr){
        xhr.abort();
    }
}

Ti.App.addEventListener('map:mapIt',function(){
    cleanMap();
    getLocation();
});

这是加载地图的索引页面中的一些代码:

var winMap = Titanium.UI.createWindow({
    url:'map.js',
    tabBarHidden:false
});

btnEducation.addEventListener('click',function(){
    Ti.App.types = Ti.App.schools;
    Ti.UI.currentTab.open(winMap);
    Ti.App.police = false;
});

我创建了一个全局 HTTPClient 并重用它,就像其他一些问答答案(在 SO 和 Appcelerator 的网站上)所建议的那样,这似乎有所帮助(每次加载地图时不会消耗太多内存),我还尝试手动将变量(尤其是较大的变量)设置为 null(这可能有效也可能无效),但仍然存在一些问题。我还尝试在事件侦听器中为打开窗口的按钮创建地图窗口,但这似乎根本没有任何效果。

我还运行 Instruments 看看它能找到什么,但没有发现任何值得注意的东西(我什至把它展示给我的同事,他全职做移动开发,他说他没有任何不寻常的地方可以看到)。

我已经查看这段代码几个小时了,这不是我的全部代码,所以我完全有可能遗漏了一些明显的东西,但是我的代码中是否有原因导致内存不存在按原样释放?我还能做些什么来释放更多内存吗?我现在只针对 iOS 进行开发,因此可以接受特定于 iOS 的解决方案。

编辑 - 我现在也尝试将地图部分包含到调用它的文件中(使用Ti.include('map.js'))。我做了一个快速而肮脏的设置,看看它是否可以工作:

Ti.include('map.js');
var button_index = Ti.UI.createButton({
   text:'Back',
   height:20,
   width:50,
   top:0,
   left:0,
   color:'#000'
});
button_index.addEventListener('click', function()
{
    Ti.App.xhr.abort();
    if (mapview) {
        mapview.removeAllAnnotations();
//        mapview = null;
    }
    if(policeJson){
        policeJson = null;
        fireJson = null;
    }
    Ti.App.police = false;
    Ti.App.types = null;
    Ti.App.title = null;
    mapview.hide();
    Ti.API.info('Memory: ' + Ti.Platform.availableMemory);
});
mapview.add(button_index);
mapview.hide();

btnArts.addEventListener('click',function(){
    Ti.App.types = Ti.App.arts;
//    Ti.UI.currentTab.open(winMap);
mapview.show();
Ti.App.fireEvent('map:mapIt'); //Triggers the chain of events to clear the map and add the necessary annotations to it
    Ti.App.police = false;
    Ti.App.title = 'arts';
});

它似乎工作得更好,但是当我进出 mapview 时,可用内存量仍然在稳步减少,并且初始内存负载使其在设备上与其他方法一样无法使用(drops内存减少到大约 3MB)。

【问题讨论】:

  • developer.appcelerator.com/question/116867/… 刚刚遇到这个。也许它可以提供帮助。
  • @tgriesser - 我很久以前就发现了。它实际上是影响上述代码以及我如何处理地图的主要解决方案之一。
  • 您是否尝试过将 map.js 中的所有内容封装在一个自调用匿名函数中?
  • @tgriesser - 我没有尝试过,但我目前不再参与该项目。

标签: ios memory-management mobile titanium


【解决方案1】:

来自有关选项卡/选项卡组的文档...“一个 TabGroup 选项卡实例。每个选项卡实例维护一组选项卡窗口。一次只能看到选项卡中的一个窗口。当窗口关闭时,通过用户或代码,窗口从堆栈中移除,使前一个窗口可见。"

一种猜测是close() 在应用于选项卡时可能不会像您想象的那样运行,因为当您在选项卡之间循环时,它似乎会保持选项卡之间的状态。另外,上面的代码示例中可能缺少一些东西,但我实际上并没有看到在哪里 “win”被定义为一个变量(我假设您在某处有var win = Ti.UI.currentWindow();,但您可能需要仔细检查它是否在调用该函数时实际上正在关闭。

您还可以考虑为您的应用程序创建一个对象,并将函数链接到该对象,以免污染全局范围。见:http://wiki.appcelerator.org/display/guides/JavaScript+Best+Practices

【讨论】:

  • 当用户或代码关闭窗口时,窗口从堆栈中移除,使前一个窗口可见。除了实际清除之外,如何将其解释为任何其他方式?此外,window.close() 方法是 Appcelerator Titans 自己建议的用于解决内存问题的方法。是的,var win = Ti.UI.currentWindow(); 声明在顶部。如果不是这样,我的 IDE 会对我大喊大叫,而我不希望有红色或黄色波浪线的冲动会解决这个问题。
  • 至于全局作用域,大部分代码已经在函数中了。绝大多数不是 UI 元素及其事件处理程序。虽然与他们一起做其他事情可能被认为是最佳实践,但我继承了这个项目,并且已经在一个难以置信的紧迫期限内,所以虽然它在重构列表中,但当我问这个问题时(说截止日期已经过去,我们'现在只是在处理它)同时仍然确保它不会损坏并不是一个真正可行的解决方案(尽管被授予,它可能是唯一的方法,但没有人解释为什么)。
【解决方案2】:

是否每次返回地图时都会调用具有“重窗口”(即指向 URL 并创建另一个 js 上下文,而不是包含在同一上下文中)的 winMap?我没有看到它是从哪里调用的。

【讨论】:

  • 查看我的第二个代码块。它在事件侦听器中被调用。 Ti.UI.currentTab.open(winMap);
【解决方案3】:

您确定正在释放 mapView 的内存吗?我从查看代码的预感是它可能是罪魁祸首。

我可能会建议使用一个全局 mapView 对象,而不是继续在其中创建 map.js

【讨论】:

  • 这是我的问题,它并没有从我能说的情况中被释放(尽管由于某种原因 Instruments 无法确认),但我不知道什么没有被释放或为什么。根据我发现的所有内容,调用win.close() 应该会释放所有内容,而使用 TabGroups 应该可以让您不必担心。您如何建议我使用“一个全局 mapView”?我已经在独立于事件侦听器创建它,这应该意味着它不会为每次按下按钮创建一个新的。还是我猜错了?
【解决方案4】:

我只是想补充一下,如果您将来有更多使用 Titanium 创建的项目,有一种推荐的设置应用程序的方法可以最大限度地减少内存问题。

首先,我不建议您使用 Ti.include() 函数。有一个更好的替代方法叫做 require()。

我遇到了一些对象没有被正确地垃圾收集的问题,但是这些链接帮助我编写了内存效率高的应用程序:

这是来自 Appcelerator: http://search.vimeo.com/29804284#

这解释了 require 函数和 CommonJS 模块: https://wiki.appcelerator.org/display/guides/CommonJS+Modules+in+Titanium

如何使用 CommonJS 的示例: https://github.com/appcelerator/Documentation-Examples/blob/master/commonjsExample/Resources/modules/pages/userlist.js

我希望这会有所帮助!

【讨论】:

    猜你喜欢
    • 2021-09-17
    • 1970-01-01
    • 2017-06-29
    • 1970-01-01
    • 2011-06-30
    • 2016-09-14
    • 2015-08-19
    • 2012-05-15
    • 2018-01-26
    相关资源
    最近更新 更多