Android - 使用MapView的单个Activity上的许多OutOfMemoryError异常

时间:2011-08-21 03:47:47

标签: java android overlay android-mapview out-of-memory

我从用户那里收到了很多OutOfMemoryError报告,每个报告来自同一个Activity,其中包含一个MapView。我认为这是一个孤立的例外,我的应用程序只有这一个地方,我无法弄清楚问题是什么。谁能给我一些关于为什么会发生这种情况的指示?

我已经为这个问题删除了一些不需要的代码,所以如果有人认为问题可能存在,我会发布它。

堆栈跟踪

Stack Trace #1

java.lang.OutOfMemoryError: bitmap size exceeds VM budget
at android.graphics.Bitmap.nativeCreate(Native Method)
at android.graphics.Bitmap.createBitmap(Bitmap.java:677)
at com.google.android.maps.ZoomHelper.createSnapshot(ZoomHelper.java:444)
at com.google.android.maps.ZoomHelper.beginZoom(ZoomHelper.java:194)
at com.google.android.maps.MapView$2.onScaleBegin(MapView.java:371)
at android.view.ScaleGestureDetector.onTouchEvent(ScaleGestureDetector.java:216)
at com.google.android.maps.MapView.onTouchEvent(MapView.java:646)
at android.view.View.dispatchTouchEvent(View.java:3778)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:920)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:959)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:959)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:959)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:1716)
at com.android.internal.policy.impl.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1124)
at android.app.Activity.dispatchTouchEvent(Activity.java:2125)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:1700)
at android.view.ViewRoot.handleMessage(ViewRoot.java:1822)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:143)
at android.app.ActivityThread.main(ActivityThread.java:5068)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:521)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:858)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
at dalvik.system.NativeStart.main(Native Method)


Stack Trace #2

java.lang.OutOfMemoryError: bitmap size exceeds VM budget
at android.graphics.Bitmap.nativeCreate(Native Method)
at android.graphics.Bitmap.createBitmap(Bitmap.java:468)
at com.google.android.maps.ZoomHelper.createSnapshot(ZoomHelper.java:444)
at com.google.android.maps.ZoomHelper.doZoom(ZoomHelper.java:151)
at com.google.android.maps.ZoomHelper.doZoom(ZoomHelper.java:140)
at com.google.android.maps.MapView.doZoom(MapView.java:1478)
at com.google.android.maps.MapView.doZoom(MapView.java:1487)
at com.google.android.maps.MapController.zoomOut(MapController.java:439)
at com.hookedroid.fishingcompanion.GoogleMaps$3.onClick(GoogleMaps.java:133)
at android.view.View.performClick(View.java:2405)
at android.view.View$PerformClick.run(View.java:8813)
at android.os.Handler.handleCallback(Handler.java:587)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:123)
at android.app.ActivityThread.main(ActivityThread.java:4627)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:521)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
at dalvik.system.NativeStart.main(Native Method)


Stack Trace #3

java.lang.OutOfMemoryError: bitmap size exceeds VM budget
at android.graphics.Bitmap.nativeCreate(Native Method)
at android.graphics.Bitmap.createBitmap(Bitmap.java:468)
at android.graphics.Bitmap.createBitmap(Bitmap.java:435)
at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:340)
at android.graphics.BitmapFactory.finishDecode(BitmapFactory.java:488)
at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:462)
at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:323)
at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:346)
at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:372)
at com.hookedroid.fishingcompanion.maps.CrosshairOverlay.draw(CrosshairOverlay.java:32)
at com.google.android.maps.OverlayBundle.draw(OverlayBundle.java:45)
at com.google.android.maps.MapView.onDraw(MapView.java:494)
at android.view.View.draw(View.java:6742)
at android.view.ViewGroup.drawChild(ViewGroup.java:1640)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367)
at android.view.ViewGroup.drawChild(ViewGroup.java:1638)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367)
at android.view.View.draw(View.java:6745)
at android.widget.FrameLayout.draw(FrameLayout.java:352)
at android.view.ViewGroup.drawChild(ViewGroup.java:1640)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367)
at android.view.View.draw(View.java:6745)
at android.widget.FrameLayout.draw(FrameLayout.java:352)
at com.android.internal.policy.impl.PhoneWindow$DecorView.draw(PhoneWindow.java:1913)
at android.view.ViewRoot.draw(ViewRoot.java:1407)
at android.view.ViewRoot.performTraversals(ViewRoot.java:1163)
at android.view.ViewRoot.handleMessage(ViewRoot.java:1727)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:123)
at android.app.ActivityThread.main(ActivityThread.java:4646)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:521)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:858)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
at dalvik.system.NativeStart.main(Native Method)

活动

public class GoogleMaps extends MapActivity {

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.googlemaps_layout);

    intent = getIntent();
    currentMode = intent.getIntExtra("MAP_MODE", 0);

    initControls();
    initMembers();

    currentOverlayMode = prefs.getInt("map_viewmode", 0);

    populateMap();
}

private void initMembers() {
    mDbHelper = new FishingCompanionDB(this);
    mDbHelper.open();
    catchList = new ArrayList<FishEntry>();

    prefs = PreferenceManager.getDefaultSharedPreferences(this);
    prefsEditor = prefs.edit();
}

private void initControls() {
    mMaps = (MapView)findViewById(R.id.google_maps);
    mMaps.setClickable(true);
    mMaps.setLongClickable(true);
    mapController = mMaps.getController();

    mOverlayModeBtn = (Button)findViewById(R.id.googlemaps_overlay_btn);
    mOverlayModeBtn.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
            if (currentOverlayMode < 1)
                currentOverlayMode++;
            else
                currentOverlayMode = 0;
            switch (currentOverlayMode) {
                case OVERLAY_STREET:
                    mMaps.setSatellite(false);
                    mMaps.setStreetView(true);
                    prefsEditor.putInt("map_viewmode", OVERLAY_STREET);
                    break;
                case OVERLAY_SAT:
                    mMaps.setStreetView(false);
                    mMaps.setSatellite(true);
                    prefsEditor.putInt("map_viewmode", OVERLAY_SAT);
                    break;
            }
            prefsEditor.commit();
            mMaps.invalidate();
        }
    });
    mZoomInBtn = (Button)findViewById(R.id.googlemaps_btn_zoomin);
    mZoomInBtn.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
            mapController.zoomIn();
        }
    });
    mZoomOutBtn = (Button)findViewById(R.id.googlemaps_btn_zoomout);
    mZoomOutBtn.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
            mapController.zoomOut();
        }
    });
}

private void populateMap() {
    overlays = mMaps.getOverlays();
    overlays.clear();

    overlays.add(new CrosshairOverlay(this, R.drawable.mapcenter));
    mSelectPos = (Button)findViewById(R.id.googlemaps_select_location);
    mSelectPos.setVisibility(View.VISIBLE);
    mSelectPos.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
            GeoPoint centerGp = mMaps.getMapCenter();
            double lat = centerGp.getLatitudeE6()/1E6;
            double lng = centerGp.getLongitudeE6()/1E6;

            Intent i;
            if (currentMode == SELECT_POS_WEATHER) {
                i = new Intent(GoogleMaps.this, WeatherLookup.class);
                i.putExtra("WEATHER_LAT", lat);
                i.putExtra("WEATHER_LNG", lng);
            }
            else {
                i = new Intent(GoogleMaps.this, AddLocation.class);
                i.putExtra("LOCATION_LAT", lat);
                i.putExtra("LOCATION_LNG", lng);
            }
            i.putExtra("MODE", 1);

            startActivity(i);
            finish();
        }
    });

    mMaps.invalidate();
}

@Override
protected boolean isRouteDisplayed() {
    return false;
}
}

十字线叠加

public class CrosshairOverlay extends Overlay {

    private Context mContext;
    private int resourceId;

    public CrosshairOverlay(Context context, int resId) {
        this.mContext = context;
        this.resourceId = resId;
    }

    public boolean draw(Canvas canvas, MapView mapView, boolean shadow, long when) {
         super.draw(canvas, mapView, shadow);
         GeoPoint centerGp = mapView.getMapCenter();
         Projection projection = mapView.getProjection();
         Point centerPoint = projection.toPixels(centerGp, null);
         Paint p = new Paint();
         Bitmap bmp = BitmapFactory.decodeResource(mContext.getResources(), resourceId);

         canvas.drawBitmap(bmp, (centerPoint.x - (bmp.getWidth()/2)), (centerPoint.y - (bmp.getHeight()/2)), p);

         return true;
    }
}

DUMPSYS MEMINFO

** MEMINFO in pid 25493 [com.hookedroid.fishingcompanion] **
native   dalvik    other    total
size:    10036     7495      N/A    17531
allocated:     9955     3965      N/A    13920
free:       80     3530      N/A     3610
(Pss):     3717     1480     6703    11900
(shared dirty):      668     1512     8056    10236
(priv dirty):     3696      804     5024     9524

Objects
Views:        0        ViewRoots:        0
AppContexts:        0       Activities:        0
Assets:        3    AssetManagers:        3
Local Binders:       19    Proxy Binders:       21
Death Recipients:        1
OpenSSL Sockets:        0

SQL
heap:      527         MEMORY_USED:      527
PAGECACHE_OVERFLOW:       62         MALLOC_SIZE:       50

DATABASES
pgsz     dbsz   Lookaside(b)  Dbname
1       16            260  FishingCompanion
1       18             63  google_analytics.db

2 个答案:

答案 0 :(得分:7)

我下载了应用程序并使用它来玩,同时通过dumpsys观察内存。

事情看起来很正常,每次都会收回记忆。我无法重现这种情况,但我确实看到一个可能与之相关的尖峰。无论何时在地图中移动或缩放(基本上刷新瓷砖)卫星,内存中都会出现短暂的峰值。如果你做得足够快,你就没有机会让它得到回收,它就会上升。

现在,我的手机是Android 3.3.4并且具有相当不错的配置,因此GC可能效率更高。我想知道如果我的旧测试手机能够更慢地回收内存,那么当我到达地图时(比如添加鱼类之后),我仍然可以从之前未被GC回收的活动中获取内存。然后我会做什么,我会去我的位置并通过放大/缩小来检查。结合之前活动的先前记忆可能会使手机达到极限。

这只是一个理论,我在路上,无法访问我所有的测试手机。你知道什么版本的手机崩溃了吗?我将在3-4天后回来,我可以在我的旧手机上试用该应用程序。

<强>更新: 我在这个应用程序上运行了更多实验。我几乎可以肯定,不断添加鱼类会增加更多回忆。我一直在添加和删除鱼,并检查内存是否通过dumpsys meminfo继续上升。 Pro Edition的真实用户甚至是不断添加和删除鱼类的精简版可能最终会达到接近极限并且之后进入地图将触发输出内存错误,因为内存跳转进入地图。这是我多次添加和删除鱼类后的快照

** MEMINFO in pid 11572 [com.hookedroid.fishingcompanion.lite] **
                    native   dalvik    other    total    limit   bitmap nativeBmp
            size:    19728    18251      N/A    37979    32768      N/A      N/A
       allocated:    17174    14674      N/A    31848      N/A     3144        0
            free:      405     3577      N/A     3982      N/A      N/A      N/A
           (Pss):    12750     1771    25944    40465      N/A      N/A      N/A
  (shared dirty):      908     1544     5800     8252      N/A      N/A      N/A
    (priv dirty):    12732     1008    24208    37948      N/A      N/A      N/A

您的私人内存总数跃升至37,948,我相信如果我继续添加和删除鱼类,它最终会抛出OutOfMemoryException

更新更新(几分钟后): 我设法使用上面的理论使应用程序崩溃。我必须在它出现之前多次添加和移除鱼类。应用程序崩溃之前可能超过50条鱼。

我的猜测是SQL以某种方式没有得到正确的清理。在每组添加和删除10条鱼(这是精简版的限制)之后查看dumpsys,我看到了

SQL
               heap:     6581         MEMORY_USED:     6581
 PAGECACHE_OVERFLOW:      173         MALLOC_SIZE:       50

 DATABASES
      pgsz     dbsz   Lookaside(b)  Dbname
         1       16             62  FishingCompanion
         1       16             62  FishingCompanion
         1       16             62  FishingCompanion
         1       16             62  FishingCompanion
         1       16             62  FishingCompanion
         1       16             62  FishingCompanion
         1       16             62  FishingCompanion
         1       16             62  FishingCompanion
         1       16             62  FishingCompanion
         1       16             62  FishingCompanion
         1       16             33  FishingCompanion
         1       16             62  FishingCompanion
         1       16             62  FishingCompanion
         1       16             62  FishingCompanion
         1       16             62  FishingCompanion
         1       16             62  FishingCompanion
         1       16             62  FishingCompanion

即使我已经删除了鱼,SQL内存仍在继续上升。如果我继续这样做一段时间,最终它将达到手机的上限并转到地图(导致内存中的跳转)将触发内存不足异常,似乎表明地图页面是原因而我认为添加/删除鱼页是真正原因的一部分(我说“真正原因的一部分”,因为我不知道是否会发生类似的效果,如果我添加新位置)。

我得到OutMemoryException关于何时总内存大约是58MB(这可能与手机不同)。作为参考,这里有一个类似的OutOfMemoryException:

D/dalvikvm(11572): GC_FOR_MALLOC freed 125K, 11% free 25734K/28743K, external 4047K/4695K, paused 188ms
D/AndroidRuntime(11572): Shutting down VM
W/dalvikvm(11572): threadid=1: thread exiting with uncaught exception (group=0x4001d648)
E/AndroidRuntime(11572): FATAL EXCEPTION: main
E/AndroidRuntime(11572): java.lang.OutOfMemoryError: bitmap size exceeds VM budget(Heap Size=28743KB, Allocated=25734KB, Bitmap Size=4047KB)
E/AndroidRuntime(11572):    at android.graphics.Bitmap.nativeCreate(Native Method)
E/AndroidRuntime(11572):    at android.graphics.Bitmap.createBitmap(Bitmap.java:695)
E/AndroidRuntime(11572):    at com.google.android.maps.ZoomHelper.createSnapshot(ZoomHelper.java:444)
E/AndroidRuntime(11572):    at com.google.android.maps.ZoomHelper.beginZoom(ZoomHelper.java:194)
E/AndroidRuntime(11572):    at com.google.android.maps.MapView$2.onScaleBegin(MapView.java:380)
E/AndroidRuntime(11572):    at android.view.ScaleGestureDetector.onTouchEvent(ScaleGestureDetector.java:216)
E/AndroidRuntime(11572):    at com.google.android.maps.MapView.onTouchEvent(MapView.java:682)
E/AndroidRuntime(11572):    at android.view.View.dispatchTouchEvent(View.java:3932)
E/AndroidRuntime(11572):    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:955)
E/AndroidRuntime(11572):    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1015)
E/AndroidRuntime(11572):    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1015)
E/AndroidRuntime(11572):    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1015)

希望有所帮助

答案 1 :(得分:1)

我不确定这是否与您的问题有关。

link讨论memory leak issue和建议的解决方案。基于我在我的扩展MyMapView 中使用此版本并在适当的位置调用此方法。

public void cleanUpMemory(){
    try {
        Field fMapInView = MapView.class.getDeclaredField("mMap");
        AccessibleObject.setAccessible(new AccessibleObject[]{fMapInView}, true);
        fMapInView.set(this, null);
    } catch (Exception e) {
        e.printStackTrace();
    }

    try {
        Field fConverterInView = MapView.class.getDeclaredField("mConverter");
        AccessibleObject.setAccessible(new AccessibleObject[]{fConverterInView}, true);
        fConverterInView.set(this, null);
    } catch (Exception e) {
        e.printStackTrace();
    }

    try {
        Field fControllerInView = MapView.class.getDeclaredField("mController");
        AccessibleObject.setAccessible(new AccessibleObject[]{fControllerInView}, true);
        fControllerInView.set(this, null);
    } catch (Exception e) {
        e.printStackTrace();
    }

    try {
        Field fZoomHelperInView = MapView.class.getDeclaredField("mZoomHelper");
        AccessibleObject.setAccessible(new AccessibleObject[]{fZoomHelperInView}, true);
        fZoomHelperInView.set(this, null);
    } catch (Exception e) {
        e.printStackTrace();
    }
}