【问题标题】:Android - Many OutOfMemoryError exceptions only on single Activity with MapViewAndroid - 许多 OutOfMemoryError 异常仅在使用 MapView 的单个 Activity 上
【发布时间】:2011-08-21 03:47:47
【问题描述】:

我从我的用户那里收到了很多 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

【问题讨论】:

  • 您是否尝试过通过转储 hprof 来分析内存使用情况并使用 Eclipse Memory Analyzer 等内存分析器对其进行分析?这应该让您了解最大的消费在哪里,并让您开始寻找什么。
  • 尝试移行:Bitmap bmp = BitmapFactory.decodeResource(mContext.getResources(), resourceId);退出 draw() 方法,以便每次调用 draw() 时都不会重新创建位图(经常)。
  • 我会尝试这两个建议并报告。
  • @momo - 我运行了内存分析器,认为我没有发现任何异常。最大的消耗来自 android_maps_conflict_avoidance.com.google.googlenav.map.MapService。这和它有什么关系吗?我还不能最终重现这个问题。
  • @hooked82 我下载了应用程序。评论里的空间不够放我的观察,所以我把它放在答案部分。这只是我的预感,所以这不是真正的官方答案。我必须用更多的手机进行测试才能确定。

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


【解决方案1】:

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

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

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

不过,这只是一个理论,我在路上,无法使用我所有的测试手机。你知道哪个版本的手机会崩溃吗?我将在 3-4 天后回来,我可以在旧手机上试用该应用。

更新: 我在这个应用程序上进行了更多实验。我几乎可以肯定,不断添加鱼会增加更多的记忆。我不断添加和删除鱼,并通过 dumpsys meminfo 检查内存是否不断增加。专业版的真实用户,甚至是不断添加和删除鱼的精简版用户最终可能会接近极限,然后进入地图会触发内存溢出错误,因为有内存跳转进入地图。这是我多次添加和删除鱼后的快照

** pid 11572 中的 MEMINFO [com.hookedroid.fishingcompanion.lite] ** native dalvik 其他总限制位图 nativeBmp 尺寸:19728 18251 N/A 37979 32768 N/A N/A 已分配: 17174 14674 不适用 31848 不适用 3144 0 免费: 405 3577 N/A 3982 N/A N/A N/A (附): 12750 1771 25944 40465 N/A N/A N/A (共享脏话):908 1544 5800 8252 N/A N/A N/A (私人脏):12732 1008 24208 37948 N/A N/A N/A

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

更多更新(几分钟后): 我设法使用上述理论使应用程序崩溃。在它发生之前,我必须多次添加和删除鱼。应用程序崩溃之前可能超过 50 条鱼。

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

SQL 堆:6581 MEMORY_USED:6581 PAGECACHE_OVERFLOW:173 MALLOC_SIZE:50 数据库 pgsz dbsz Lookaside(b) 数据库名 1 16 62 钓鱼伴侣 1 16 62 钓鱼伴侣 1 16 62 钓鱼伴侣 1 16 62 钓鱼伴侣 1 16 62 钓鱼伴侣 1 16 62 钓鱼伴侣 1 16 62 钓鱼伴侣 1 16 62 钓鱼伴侣 1 16 62 钓鱼伴侣 1 16 62 钓鱼伴侣 1 16 33 钓鱼伴侣 1 16 62 钓鱼伴侣 1 16 62 钓鱼伴侣 1 16 62 钓鱼伴侣 1 16 62 钓鱼伴侣 1 16 62 钓鱼伴侣 1 16 62 钓鱼伴侣

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

当总内存约为 58MB 时,我得到了 OutMemoryException(这可能因手机而异)。作为参考,我得到了一个类似的 OutOfMemoryException:

D/dalvikvm(11572):GC_FOR_MALLOC 释放 125K,11% 释放 25734K/28743K,外部 4047K/4695K,暂停 188ms D/AndroidRuntime(11572):关闭虚拟机 W/dalvikvm(11572): threadid=1: 线程以未捕获的异常退出 (group=0x4001d648) E/AndroidRuntime(11572):致命异常:主要 E/AndroidRuntime(11572): java.lang.OutOfMemoryError: 位图大小超出 VM 预算(堆大小=28743KB,分配=25734KB,位图大小=4047KB) E/AndroidRuntime(11572):在 android.graphics.Bitmap.nativeCreate(Native Method) E/AndroidRuntime(11572): 在 android.graphics.Bitmap.createBitmap(Bitmap.java:695) E/AndroidRuntime(11572):在 com.google.android.maps.ZoomHelper.createSnapshot(ZoomHelper.java:444) E/AndroidRuntime(11572):在 com.google.android.maps.ZoomHelper.beginZoom(ZoomHelper.java:194) E/AndroidRuntime(11572):在 com.google.android.maps.MapView$2.onScaleBegin(MapView.java:380) E/AndroidRuntime(11572):在 android.view.ScaleGestureDetector.onTouchEvent(ScaleGestureDetector.java:216) E/AndroidRuntime(11572):在 com.google.android.maps.MapView.onTouchEvent(MapView.java:682) E/AndroidRuntime(11572):在 android.view.View.dispatchTouchEvent(View.java:3932) E/AndroidRuntime(11572):在 android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:955) E/AndroidRuntime(11572):在 android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1015) E/AndroidRuntime(11572):在 android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1015) E/AndroidRuntime(11572):在 android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1015)

希望对你有帮助

【讨论】:

  • 哇,感谢您对此进行如此详细的研究。我没有想到那种情景。我也会尝试使用一些较旧的测试手机。至于遇到问题的模型,谷歌给出了一些很好的信息:“Droid”和“Other”。 sigh 期待您的发现,谢谢!
  • +1 用于下载应用程序并进行测试!!!哇!!没有多少人会竭尽全力帮助他人。
  • @hooked82 似乎地图只是触发器,但添加和删除鱼确实会增加内存。似乎即使在 Lite 版本中,添加和删除鱼也会不断增加内存,而在 Pro 版本中,您甚至可以更快地获得它。让我继续在这里添加和删除鱼几次,看看它是否会触发 OutOfMemoryException
  • @hooked82 我已经用重现步骤更新了答案。恐怕这是我在没有源代码的情况下所能帮助你的。我什至无法调查 hprof,因为 DDMS 不允许我这样做。我希望调查结果能帮助您解决问题的根本原因。另外,虽然我还没有尝试过,但您可能想检查添加位置和齿轮是否有相同的症状。我只是认为添加鱼可能是真实用户最常使用的功能之一。祝你好运
  • 很棒的工作妈妈!这看起来是确切的问题。所以回顾一下,你一直在添加和删除鱼,一段时间后你会去地图并开始平移/缩放/等?在上面的 SQL 中,有很多“FishingCompanion”的实例。原因是我错过了在 onDestroy() 中关闭数据库的几个地方。我已经添加了正确的数据库代码来在下一个版本中关闭它们,所以根据你的发现,这个问题应该会消失,对吗?再次感谢您写得这么棒!
【解决方案2】:

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

这个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();
    }
}

【讨论】:

  • 这可能是导致问题的原因。一旦我可以确认/拒绝修复,我会调查并报告。感谢您的链接!
  • @Samuel,你把什么叫做“合适的地方”?
  • 在海报的讨论链接中,根据框架 [onDestroy] (code.google.com/p/android/issues/detail?id=2181#c11) 的开发人员将在其中。但我猜你的里程可能会有所不同。
猜你喜欢
  • 2014-11-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多