Google Maps Android API v2的SupportMapFragment存在内存泄漏问题

18

使用2个简单的活动。第一个活动仅包含一个按钮,用于启动第二个活动,该活动包含地图:

主要活动:

public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
}

public void goToMap(View view){ //This is just the onClick method for the button
    Intent intent=new Intent( this, BigMapTest.class);
    startActivity(intent);
}
地图活动:
public class BigMapTest extends FragmentActivity {
SupportMapFragment mapFragment;
GoogleMap map;

@Override
protected void onCreate(Bundle arg0) {
    // TODO Auto-generated method stub
    super.onCreate(arg0);

    setContentView(R.layout.travel_diary_big_map);

    mapFragment=(SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.big_map);
    map=mapFragment.getMap();

}

地图活动的XML布局:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >

<fragment
        android:id="@+id/big_map"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        class="com.google.android.gms.maps.SupportMapFragment"  
        />

现在,当我运行这段代码时,按下按钮移到带有地图的Activity,然后按返回键回到第一个Activity...然后重复这个过程,我可以看到堆大小每次都在增加,直到达到极限,然后它开始卡顿。如果继续使用地图(例如缩放),此时可能会出现OOM异常。

01-25 16:10:13.931: D/dalvikvm(21578): GC_FOR_ALLOC freed 1898K, 7% free 45859K/49187K, paused 204ms
01-25 16:10:14.671: I/dalvikvm-heap(21578): Clamp target GC heap from 52.724MB to 48.000MB
01-25 16:10:14.671: D/dalvikvm(21578): GC_CONCURRENT freed 2534K, 6% free 46554K/49187K, paused 3ms+14ms
01-25 16:10:15.372: I/dalvikvm-heap(21578): Clamp target GC heap from 52.979MB to 48.000MB
01-25 16:10:15.382: D/dalvikvm(21578): GC_CONCURRENT freed 2273K, 5% free 46815K/49187K, paused 3ms+15ms
01-25 16:10:15.622: I/dalvikvm-heap(21578): Clamp target GC heap from 52.604MB to 48.000MB
01-25 16:10:15.622: D/dalvikvm(21578): GC_FOR_ALLOC freed 657K, 6% free 46431K/49187K, paused 202ms
01-25 16:10:16.203: I/dalvikvm-heap(21578): Clamp target GC heap from 52.959MB to 48.000MB
01-25 16:10:16.203: D/dalvikvm(21578): GC_FOR_ALLOC freed 1469K, 5% free 46796K/49187K, paused 217ms
01-25 16:10:16.203: I/dalvikvm-heap(21578): Forcing collection of SoftReferences for 278744-byte allocation
01-25 16:10:16.423: I/dalvikvm-heap(21578): Clamp target GC heap from 52.952MB to 48.000MB
01-25 16:10:16.423: D/dalvikvm(21578): GC_BEFORE_OOM freed 9K, 5% free 46786K/49187K, paused 219ms
01-25 16:10:16.423: E/dalvikvm-heap(21578): Out of memory on a 278744-byte allocation.

欢迎任何建议/帮助。


1
使用MAT,然后搜索具有最大保留大小的对象(该大小在每个周期中都会增长),然后使用路径到GC根排除弱引用,我已经得出了以下结果:class maps.by.a @ 0x414f7fa8 System Class。- 但我不知道接下来该怎么做。 - Nims
1
似乎在Hashmap中积累了一些东西:类“maps.by.a”由“dalvik.system.PathClassLoader @ 0x413de740”加载,占用12,923,112(57.78%)字节。内存积累在一个由“<system class loader>”加载的“java.util.HashMap $ HashMapEntry []”实例中。 - Nims
嗯,听起来不太乐观。你可不可以上传整个测试项目到某个地方让我看看? - CommonsWare
http://dl.dropbox.com/u/83195107/MapMemoryLeakTest.zip - Nims
2
好的,我会在周末查看这个,并在结果出来后回报。 - CommonsWare
显示剩余5条评论
4个回答

9
据我初步的MAT分析,你所看到的是Maps V2维护的已下载地图数据的缓存。如果你频繁平移和缩放地图,缓存似乎会更大。如果离开地图并稍后返回新地图,则缓存会缩小。但是,我无法通过多次启动示例应用程序中的地图活动来获取N个缓存,并且缓存大小取决于用户的操作情况。
不幸的是,就我所知,该缓存无法进行配置,例如缓存大小、何时清除缓存以及是否溢出到磁盘等方面。
因此,默认情况下,你只能为Maps V2保留足够大的堆空间,并采取措施保持在较小的堆子集内。
如果想要进行实验,你可以尝试调用GoogleMap上的clear()SupportMapFragment上的onLowMemory(),看看它们是否有助于减少缓存大小。

尽管观察到的行为可能部分归因于缓存的流动,但地图片段明显泄漏了托管它的上下文。这可以通过旋转带有地图的活动几次,然后查看堆中仍有多少个活动副本来轻松测试。即使是地图演示应用程序也会像错误报告中描述的那样做。 - blahdiblah

2
我们正在推出Google Play服务v3.0,其中引入了Google+登录和Google Maps Android API的改进。截至2013年2月26日,在gmaps-api-issues页面上描述的此问题现已由当前的Google API更新解决。

1
我有完全相同的问题。每次托管V2地图的活动启动时,内存都会增加。即使活动完成后也不会释放。
因此,解决方法是重用该活动。在清单中将活动设置为singleTask,并且不要使用finish(),而是使用moveTaskToBack(true);

如果您只有一个地图或使用非常少的地图,则这是一个不错的解决方案,但如果您在多个活动中使用地图,我不认为这会起作用。 - Nims

-1

在你的布局中使用这个:

<LinearLayout
        android:id="@+id/map_container2"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_weight="35.22"
        android:orientation="horizontal" >

        <fragment
            android:id="@+id/map1"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1"
            class="com.google.android.gms.maps.SupportMapFragment"
            map:cameraTargetLat="40.72"
            map:cameraTargetLng="-74.00"
            map:cameraZoom="8" />
    </LinearLayout>

而这段代码:

onCreate{
   setUpMapIfNeeded();
}

private void setUpMapIfNeeded() {
        // TODO Auto-generated method stub
        // Do a null check to confirm that we have not already instantiated the map.
        if (mMap == null) {
            // Try to obtain the map from the SupportMapFragment.
            mMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map1))
                    .getMap();
            // Check if we were successful in obtaining the map.
            if (mMap != null) {
                setUpMap();
            }
        }
    }

private void setUpMap() {
        // TODO Auto-generated method stub
         // Hide the zoom controls as the button panel will cover it.
        mUiSettings = mMap.getUiSettings();
 // Enables/disables zoom gestures (i.e., double tap, pinch & stretch).
        mMap.getUiSettings().setZoomGesturesEnabled(false);
// Enables/disables scroll gestures (i.e. panning the map).
        mMap.getUiSettings().setScrollGesturesEnabled(false);
 // Enables/disables the compass (icon in the top left that indicates the orientation of the
        // map).
        mMap.getUiSettings().setCompassEnabled(false);
        // Add lots of markers to the map.
        addMarkersToMap();

        // Pan to see all markers in view.
        // Cannot zoom to bounds until the map has a size.
        final View mapView = getSupportFragmentManager().findFragmentById(R.id.map1).getView();
        if (mapView.getViewTreeObserver().isAlive()) {
            mapView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
                @SuppressLint("NewApi") // We check which build version we are using.
                @Override
                public void onGlobalLayout() {
                    LatLngBounds bounds = new LatLngBounds.Builder()
                            .include(WOODS)
                            .build();
                    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                      mapView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                    } else {
                      mapView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                    }
                    mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 50));
                }
            });
        }
    }

private void addMarkersToMap() {
        // TODO Auto-generated method stub
         // Uses a colored icon.
        mWoods = mMap.addMarker(new MarkerOptions()
                .position(WOODS)
                .title("Woods")
                .snippet("R. Quatá, 1016, Vila Olimpia - (11) 3849-6868")
                .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE)));
    }

这里的WOODS是什么?LatLngBounds bounds = new LatLngBounds.Builder() .include(WOODS)。mUiSettings的变量名称是什么? - Singh Arjun
WOODS 是私有的静态最终 LatLng WOODS = new LatLng(-23.596089,-46.682393); 用于保存 Lat 和 Long 值的变量,private UiSettings mUiSettings; - Marckaraujo
你能解释一下为什么这样可以消除内存泄漏吗? - Daniel Gomez Rico

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接