Android - Many OutOfMemoryError exceptions only on

2020-03-31 06:16发布

问题:

I'm getting quite a few OutOfMemoryError reports from my users and every single report is from the same Activity, which contains a MapView. I'm thinking that it's an isolated exception with just this one place in my app, and I can't figure out what the problem is. Can anybody give me some pointers as to why this is happening?

I've removed some unneeded code for this question, so if somebody thinks the issue could potentially be in that, I'll post it.

Stack Traces

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)

Activity

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;
}
}

Crosshair Overlay

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

回答1:

I downloaded the app and play with it while observing the memory via dumpsys.

Things look normal, memory gets reclaimed every time. I can't reproduce the situation, but I do see one spike that is probably related. Whenever you move around in map or zoom (basically refreshing the tiles) in satellite, there will be a brief spike in memory. If you do it fast enough, you don't give a chance for it to get reclaimed and it will go up.

Now, my phone is Android 3.3.4 and have pretty good configuration, so maybe the GC is much more efficient. I wonder though if my older test phones would reclaim the memory slower and thus when I get to the map (say after adding the fishes), I would still have memory from the previous activity that hasn't been reclaimed by GC. Then what I would do, I would go to my location and check things out by zooming in/out. That combined by previous memory from the previous activities might bring the phones to its limit.

This is just a theory though, I am on the road and don't have access to all my test phones. Do you know what version of the phones that are crashing? I'll be back 3-4 days later and I could try the app on my older phones.

UPDATED: I've run more experiments on this app. I'm almost sure that adding the fishes continuously will add more memories. I kept adding and deleting the fishes and checked that the memory keeps going up via dumpsys meminfo. A real users of the Pro Edition or even the lite who keep adding and removing the fishes might eventually hit close to the limit and going to the map afterward will trigger the out memory error since there is a memory jump going into the map. Here is the snapshot after I added and removed the fishes several times

** 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

Your private memory jump to total of 37,948 which I am sure if I continue adding and removing fishes, it will throw the OutOfMemoryException eventually

MORE UPDATE (few minutes later): I manage to crash the app using the theory above. I must have added and removed fishes several times before it occurs. It could be more than 50 fishes before the app crashed.

My guess is the SQL somehow didn't get cleaned properly. Looking at the dumpsys after each set of adding and removing 10 fishes (which is the limit of the lite version), I see that

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

The SQL memory keeps going up even though I deleted the fishes already. If I keep doing this for some time, eventually it will hit the upper limit of the phone and going to the map (which cause the jump in the memory) will trigger the out of memory exception seemingly indicating that the map page is the cause whereas I think that the add/remove fish page is part of the real cause (I say "part of the real cause" as I don't know if similar effect would occur say if I add new location).

I got the OutMemoryException right about when the total memory is about 58MB (this is probably different from phone to phone). For a reference, here is a similar OutOfMemoryException that I got:

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)

Hope it helps



回答2:

i'm not sure if this is related to your issue.

this link disscusses about a memory leak issue and a proposed solution. based on that i'm using this version in my extended MyMapView and call this method in appropriate places.

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