5
0
Эх сурвалжийг харах

Fixes issue 73. RosActivity now starts up and shutsdown reliably.
Better handling of OutOfMemory errors. Still have to track down the leak.
Cleanups, javadoc, copyright headers.

Damon Kohler 13 жил өмнө
parent
commit
f252ef1171

+ 3 - 0
android_gingerbread/src/org/ros/android/InitRunnable.java

@@ -19,6 +19,9 @@ package org.ros.android;
 import org.ros.node.NodeRunner;
 import org.ros.node.NodeRunner;
 
 
 /**
 /**
+ * Initializes instances of classes derived from {@link RosActivity} with the
+ * parent {@link RosActivity} and {@link NodeRunner}.
+ * 
  * @author damonkohler@google.com (Damon Kohler)
  * @author damonkohler@google.com (Damon Kohler)
  */
  */
 class InitRunnable implements Runnable {
 class InitRunnable implements Runnable {

+ 43 - 7
android_gingerbread/src/org/ros/android/NodeRunnerService.java

@@ -29,6 +29,8 @@ import android.os.IBinder;
 import android.os.PowerManager;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.os.PowerManager.WakeLock;
 import android.util.Log;
 import android.util.Log;
+import org.ros.concurrent.ListenerCollection;
+import org.ros.concurrent.ListenerCollection.SignalRunnable;
 import org.ros.node.DefaultNodeRunner;
 import org.ros.node.DefaultNodeRunner;
 import org.ros.node.NodeConfiguration;
 import org.ros.node.NodeConfiguration;
 import org.ros.node.NodeListener;
 import org.ros.node.NodeListener;
@@ -36,12 +38,16 @@ import org.ros.node.NodeMain;
 import org.ros.node.NodeRunner;
 import org.ros.node.NodeRunner;
 
 
 import java.util.Collection;
 import java.util.Collection;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 
 /**
 /**
  * @author damonkohler@google.com (Damon Kohler)
  * @author damonkohler@google.com (Damon Kohler)
  */
  */
 public class NodeRunnerService extends Service implements NodeRunner {
 public class NodeRunnerService extends Service implements NodeRunner {
 
 
+  private static final String TAG = "NodeRunnerService";
+
   // NOTE(damonkohler): If this is 0, the notification does not show up.
   // NOTE(damonkohler): If this is 0, the notification does not show up.
   private static final int ONGOING_NOTIFICATION = 1;
   private static final int ONGOING_NOTIFICATION = 1;
 
 
@@ -52,6 +58,7 @@ public class NodeRunnerService extends Service implements NodeRunner {
 
 
   private final NodeRunner nodeRunner;
   private final NodeRunner nodeRunner;
   private final IBinder binder;
   private final IBinder binder;
+  private final ListenerCollection<NodeRunnerServiceListener> listeners;
 
 
   private WakeLock wakeLock;
   private WakeLock wakeLock;
   private WifiLock wifiLock;
   private WifiLock wifiLock;
@@ -68,24 +75,26 @@ public class NodeRunnerService extends Service implements NodeRunner {
 
 
   public NodeRunnerService() {
   public NodeRunnerService() {
     super();
     super();
-    nodeRunner = DefaultNodeRunner.newDefault();
+    ExecutorService executorService = Executors.newCachedThreadPool();
+    nodeRunner = DefaultNodeRunner.newDefault(executorService);
     binder = new LocalBinder();
     binder = new LocalBinder();
+    listeners = new ListenerCollection<NodeRunnerServiceListener>(executorService);
   }
   }
 
 
   @Override
   @Override
   public void onCreate() {
   public void onCreate() {
     PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
     PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
-    wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "NodeRunnerService");
+    wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
     wakeLock.acquire();
     wakeLock.acquire();
     int wifiLockType = WifiManager.WIFI_MODE_FULL;
     int wifiLockType = WifiManager.WIFI_MODE_FULL;
     try {
     try {
       wifiLockType = WifiManager.class.getField("WIFI_MODE_FULL_HIGH_PERF").getInt(null);
       wifiLockType = WifiManager.class.getField("WIFI_MODE_FULL_HIGH_PERF").getInt(null);
     } catch (Exception e) {
     } catch (Exception e) {
       // We must be running on a pre-Honeycomb device.
       // We must be running on a pre-Honeycomb device.
-      Log.w("NodeRunnerService", "Unable to acquire high performance wifi lock.");
+      Log.w(TAG, "Unable to acquire high performance wifi lock.");
     }
     }
     WifiManager wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
     WifiManager wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
-    wifiLock = wifiManager.createWifiLock(wifiLockType, "NodeRunnerService");
+    wifiLock = wifiManager.createWifiLock(wifiLockType, TAG);
     wifiLock.acquire();
     wifiLock.acquire();
   }
   }
 
 
@@ -100,6 +109,11 @@ public class NodeRunnerService extends Service implements NodeRunner {
     run(nodeMain, nodeConfiguration, null);
     run(nodeMain, nodeConfiguration, null);
   }
   }
 
 
+  @Override
+  public void execute(Runnable runnable) {
+    nodeRunner.execute(runnable);
+  }
+
   @Override
   @Override
   public void shutdownNodeMain(NodeMain nodeMain) {
   public void shutdownNodeMain(NodeMain nodeMain) {
     nodeRunner.shutdownNodeMain(nodeMain);
     nodeRunner.shutdownNodeMain(nodeMain);
@@ -107,15 +121,37 @@ public class NodeRunnerService extends Service implements NodeRunner {
 
 
   @Override
   @Override
   public void shutdown() {
   public void shutdown() {
+    signalOnShutdown();
+    // NOTE(damonkohler): This may be called multiple times. Shutting down a
+    // NodeRunner multiple times is safe. It simply calls shutdown on all
+    // NodeMains.
+    nodeRunner.shutdown();
+    if (wakeLock.isHeld()) {
+      wakeLock.release();
+    }
+    if (wifiLock.isHeld()) {
+      wifiLock.release();
+    }
     stopForeground(true);
     stopForeground(true);
     stopSelf();
     stopSelf();
   }
   }
+  
+  public void addListener(NodeRunnerServiceListener listener) {
+    listeners.add(listener);
+  }
+
+  private void signalOnShutdown() {
+    listeners.signal(new SignalRunnable<NodeRunnerServiceListener>() {
+      @Override
+      public void run(NodeRunnerServiceListener nodeRunnerServiceListener) {
+        nodeRunnerServiceListener.onShutdown(NodeRunnerService.this);
+      }
+    });
+  }
 
 
   @Override
   @Override
   public void onDestroy() {
   public void onDestroy() {
-    nodeRunner.shutdown();
-    wakeLock.release();
-    wifiLock.release();
+    shutdown();
     super.onDestroy();
     super.onDestroy();
   }
   }
 
 

+ 29 - 0
android_gingerbread/src/org/ros/android/NodeRunnerServiceListener.java

@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.ros.android;
+
+
+/**
+ * @author damonkohler@google.com (Damon Kohler)
+ */
+public interface NodeRunnerServiceListener {
+
+  /**
+   * @param nodeRunnerService the {@link NodeRunnerService} that was shut down
+   */
+  void onShutdown(NodeRunnerService nodeRunnerService);
+}

+ 27 - 23
android_gingerbread/src/org/ros/android/RosActivity.java

@@ -23,6 +23,7 @@ import android.content.ComponentName;
 import android.content.Intent;
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.content.ServiceConnection;
 import android.os.IBinder;
 import android.os.IBinder;
+import android.widget.Toast;
 import org.ros.node.NodeMain;
 import org.ros.node.NodeMain;
 import org.ros.node.NodeRunner;
 import org.ros.node.NodeRunner;
 
 
@@ -36,26 +37,27 @@ public abstract class RosActivity extends Activity {
 
 
   private static final int MASTER_CHOOSER_REQUEST_CODE = 0;
   private static final int MASTER_CHOOSER_REQUEST_CODE = 0;
 
 
-  private URI masterUri;
-  private NodeRunner nodeRunner;
-
   private final ServiceConnection nodeRunnerServiceConnection;
   private final ServiceConnection nodeRunnerServiceConnection;
   private final String notificationTicker;
   private final String notificationTicker;
   private final String notificationTitle;
   private final String notificationTitle;
 
 
+  private URI masterUri;
+  private NodeRunnerService nodeRunnerService;
+
   private class NodeRunnerServiceConnection implements ServiceConnection {
   private class NodeRunnerServiceConnection implements ServiceConnection {
     @Override
     @Override
     public void onServiceConnected(ComponentName name, IBinder binder) {
     public void onServiceConnected(ComponentName name, IBinder binder) {
-      // NOTE(damonkohler): This must be synchronized in case the activity is
-      // paused while we are connecting to the service. Pausing the activity
-      // causes the nodeRunner field to be nulled.
-      synchronized (RosActivity.this) {
-        nodeRunner = ((NodeRunnerService.LocalBinder) binder).getService();
-        // Run init() in a new thread as a convenience since it often requires
-        // network access. Also, this allows us to keep a reference to the
-        // NodeRunner separate from this class.
-        new Thread(new InitRunnable(RosActivity.this, nodeRunner)).start();
-      }
+      nodeRunnerService = ((NodeRunnerService.LocalBinder) binder).getService();
+      nodeRunnerService.addListener(new NodeRunnerServiceListener() {
+        @Override
+        public void onShutdown(NodeRunnerService nodeRunnerService) {
+          RosActivity.this.finish();
+        }
+      });
+      // Run init() in a new thread as a convenience since it often requires
+      // network access. Also, this allows us to keep a reference to the
+      // NodeRunner separate from this class.
+      nodeRunnerService.execute(new InitRunnable(RosActivity.this, nodeRunnerService));
     }
     }
 
 
     @Override
     @Override
@@ -72,16 +74,16 @@ public abstract class RosActivity extends Activity {
 
 
   @Override
   @Override
   protected void onResume() {
   protected void onResume() {
-    super.onResume();
     if (getMasterUri() == null) {
     if (getMasterUri() == null) {
       // Call this method on super to avoid triggering our precondition in the
       // Call this method on super to avoid triggering our precondition in the
       // overridden startActivityForResult().
       // overridden startActivityForResult().
       super.startActivityForResult(new Intent(this, MasterChooser.class), 0);
       super.startActivityForResult(new Intent(this, MasterChooser.class), 0);
-    } else if (nodeRunner == null) {
+    } else if (nodeRunnerService == null) {
       // TODO(damonkohler): The NodeRunnerService should maintain its own copy
       // TODO(damonkohler): The NodeRunnerService should maintain its own copy
       // of master URI that we can query if we're restarting this activity.
       // of master URI that we can query if we're restarting this activity.
       startNodeRunnerService();
       startNodeRunnerService();
     }
     }
+    super.onResume();
   }
   }
 
 
   private void startNodeRunnerService() {
   private void startNodeRunnerService() {
@@ -95,21 +97,23 @@ public abstract class RosActivity extends Activity {
   }
   }
 
 
   @Override
   @Override
-  protected void onPause() {
-    super.onPause();
-    synchronized (this) {
-      if (nodeRunner != null) {
-        unbindService(nodeRunnerServiceConnection);
-        nodeRunner = null;
-      }
+  protected void onDestroy() {
+    if (nodeRunnerService != null) {
+      nodeRunnerService.shutdown();
+      unbindService(nodeRunnerServiceConnection);
+      // NOTE(damonkohler): The activity could still be restarted. In that case,
+      // nodeRunner needs to be null for everything to be started up again.
+      nodeRunnerService = null;
     }
     }
+    Toast.makeText(this, notificationTitle + " shut down.", Toast.LENGTH_SHORT).show();
+    super.onDestroy();
   }
   }
 
 
   /**
   /**
    * This method is called in a background thread once this {@link Activity} has
    * This method is called in a background thread once this {@link Activity} has
    * been initialized with a master {@link URI} via the {@link MasterChooser}
    * been initialized with a master {@link URI} via the {@link MasterChooser}
    * and a {@link NodeRunnerService} has started. Your {@link NodeMain}s should
    * and a {@link NodeRunnerService} has started. Your {@link NodeMain}s should
-   * be started here.
+   * be started here using the provided {@link NodeRunner}.
    * 
    * 
    * @param nodeRunner
    * @param nodeRunner
    *          the {@link NodeRunner} created for this {@link Activity}
    *          the {@link NodeRunner} created for this {@link Activity}

+ 1 - 0
android_honeycomb_mr2/src/org/ros/android/views/DistancePoints.java

@@ -31,6 +31,7 @@ import javax.microedition.khronos.opengles.GL10;
  * @author munjaldesai@google.com (Munjal Desai)
  * @author munjaldesai@google.com (Munjal Desai)
  */
  */
 class DistancePoints {
 class DistancePoints {
+
   // Members for displaying the range vertices and polygons.
   // Members for displaying the range vertices and polygons.
   private FloatBuffer rangeVertexBuffer;
   private FloatBuffer rangeVertexBuffer;
   private ByteBuffer rangeVertexByteBuffer;
   private ByteBuffer rangeVertexByteBuffer;

+ 20 - 1
android_honeycomb_mr2/src/org/ros/android/views/visualization/Texture.java

@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package org.ros.android.views.visualization;
 package org.ros.android.views.visualization;
 
 
 import com.google.common.base.Preconditions;
 import com.google.common.base.Preconditions;
@@ -7,7 +23,11 @@ import android.opengl.GLUtils;
 
 
 import javax.microedition.khronos.opengles.GL10;
 import javax.microedition.khronos.opengles.GL10;
 
 
+/**
+ * @author damonkohler@google.com (Damon Kohler)
+ */
 public class Texture {
 public class Texture {
+  
   private boolean needReload;
   private boolean needReload;
   private Bitmap textureBitmap;
   private Bitmap textureBitmap;
   private int[] textureHandle;
   private int[] textureHandle;
@@ -60,5 +80,4 @@ public class Texture {
     textureBitmap = null;
     textureBitmap = null;
     needReload = false;
     needReload = false;
   }
   }
-
 }
 }

+ 19 - 0
android_honeycomb_mr2/src/org/ros/android/views/visualization/TextureBitmapUtilities.java

@@ -1,7 +1,26 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
 package org.ros.android.views.visualization;
 package org.ros.android.views.visualization;
 
 
 import android.graphics.Bitmap;
 import android.graphics.Bitmap;
 
 
+/**
+ * @author damonkohler@google.com (Damon Kohler)
+ */
 public class TextureBitmapUtilities {
 public class TextureBitmapUtilities {
 
 
   public static Bitmap createSquareBitmap(int[] pixels, int width, int height, int fillColor) {
   public static Bitmap createSquareBitmap(int[] pixels, int width, int height, int fillColor) {

+ 4 - 3
android_honeycomb_mr2/src/org/ros/android/views/visualization/TextureDrawable.java

@@ -36,9 +36,10 @@ import javax.microedition.khronos.opengles.GL10;
  */
  */
 public class TextureDrawable implements OpenGlDrawable {
 public class TextureDrawable implements OpenGlDrawable {
 
 
-  private Texture texture;
-  private FloatBuffer vertexBuffer;
-  private FloatBuffer textureBuffer;
+  private final Texture texture;
+  private final FloatBuffer vertexBuffer;
+  private final FloatBuffer textureBuffer;
+  
   private Transform origin;
   private Transform origin;
   private double resolution;
   private double resolution;
   private double width;
   private double width;

+ 3 - 3
android_honeycomb_mr2/src/org/ros/android/views/visualization/VisualizationView.java

@@ -35,7 +35,7 @@ public class VisualizationView extends GLSurfaceView implements NodeMain {
   private final RenderRequestListener renderRequestListener;
   private final RenderRequestListener renderRequestListener;
   private final TransformListener transformListener;
   private final TransformListener transformListener;
   private final Camera camera;
   private final Camera camera;
-  private final XYOrthoraphicRenderer renderer;
+  private final XyOrthoraphicRenderer renderer;
   private final List<Layer> layers;
   private final List<Layer> layers;
 
 
   private Node node;
   private Node node;
@@ -50,7 +50,7 @@ public class VisualizationView extends GLSurfaceView implements NodeMain {
     };
     };
     transformListener = new TransformListener();
     transformListener = new TransformListener();
     camera = new Camera(transformListener.getTransformer());
     camera = new Camera(transformListener.getTransformer());
-    renderer = new XYOrthoraphicRenderer(transformListener.getTransformer(), camera);
+    renderer = new XyOrthoraphicRenderer(transformListener.getTransformer(), camera);
     layers = Lists.newArrayList();
     layers = Lists.newArrayList();
     setEGLConfigChooser(8, 8, 8, 8, 0, 0);
     setEGLConfigChooser(8, 8, 8, 8, 0, 0);
     getHolder().setFormat(PixelFormat.TRANSLUCENT);
     getHolder().setFormat(PixelFormat.TRANSLUCENT);
@@ -68,7 +68,7 @@ public class VisualizationView extends GLSurfaceView implements NodeMain {
     return false;
     return false;
   }
   }
 
 
-  public XYOrthoraphicRenderer getRenderer() {
+  public XyOrthoraphicRenderer getRenderer() {
     return renderer;
     return renderer;
   }
   }
   
   

+ 4 - 7
android_honeycomb_mr2/src/org/ros/android/views/visualization/XYOrthoraphicRenderer.java → android_honeycomb_mr2/src/org/ros/android/views/visualization/XyOrthoraphicRenderer.java

@@ -16,10 +16,9 @@
 
 
 package org.ros.android.views.visualization;
 package org.ros.android.views.visualization;
 
 
-import org.ros.android.views.visualization.layer.TfLayer;
-import org.ros.android.views.visualization.layer.Layer;
-
 import android.opengl.GLSurfaceView;
 import android.opengl.GLSurfaceView;
+import org.ros.android.views.visualization.layer.Layer;
+import org.ros.android.views.visualization.layer.TfLayer;
 
 
 import java.util.List;
 import java.util.List;
 
 
@@ -30,9 +29,8 @@ import javax.microedition.khronos.opengles.GL10;
  * Renders all layers of a navigation view.
  * Renders all layers of a navigation view.
  * 
  * 
  * @author moesenle@google.com (Lorenz Moesenlechner)
  * @author moesenle@google.com (Lorenz Moesenlechner)
- * 
  */
  */
-public class XYOrthoraphicRenderer implements GLSurfaceView.Renderer {
+public class XyOrthoraphicRenderer implements GLSurfaceView.Renderer {
   /**
   /**
    * List of layers to draw. Layers are drawn in-order, i.e. the layer with
    * List of layers to draw. Layers are drawn in-order, i.e. the layer with
    * index 0 is the bottom layer and is drawn first.
    * index 0 is the bottom layer and is drawn first.
@@ -43,7 +41,7 @@ public class XYOrthoraphicRenderer implements GLSurfaceView.Renderer {
 
 
   private Camera camera;
   private Camera camera;
 
 
-  public XYOrthoraphicRenderer(Transformer transformer, Camera camera) {
+  public XyOrthoraphicRenderer(Transformer transformer, Camera camera) {
     this.setLayers(layers);
     this.setLayers(layers);
     this.transformer = transformer;
     this.transformer = transformer;
     this.camera = camera;
     this.camera = camera;
@@ -106,5 +104,4 @@ public class XYOrthoraphicRenderer implements GLSurfaceView.Renderer {
   public void setLayers(List<Layer> layers) {
   public void setLayers(List<Layer> layers) {
     this.layers = layers;
     this.layers = layers;
   }
   }
-
 }
 }

+ 48 - 26
android_honeycomb_mr2/src/org/ros/android/views/visualization/layer/CompressedBitmapLayer.java

@@ -19,6 +19,7 @@ package org.ros.android.views.visualization.layer;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.BitmapFactory;
 import android.os.Handler;
 import android.os.Handler;
+import android.util.Log;
 import org.ros.android.views.visualization.Camera;
 import org.ros.android.views.visualization.Camera;
 import org.ros.android.views.visualization.TextureBitmapUtilities;
 import org.ros.android.views.visualization.TextureBitmapUtilities;
 import org.ros.android.views.visualization.TextureDrawable;
 import org.ros.android.views.visualization.TextureDrawable;
@@ -27,6 +28,7 @@ import org.ros.message.MessageListener;
 import org.ros.message.compressed_visualization_transport_msgs.CompressedBitmap;
 import org.ros.message.compressed_visualization_transport_msgs.CompressedBitmap;
 import org.ros.namespace.GraphName;
 import org.ros.namespace.GraphName;
 import org.ros.node.Node;
 import org.ros.node.Node;
+import org.ros.node.topic.Subscriber;
 
 
 import java.nio.IntBuffer;
 import java.nio.IntBuffer;
 
 
@@ -39,7 +41,9 @@ public class CompressedBitmapLayer extends
     SubscriberLayer<org.ros.message.compressed_visualization_transport_msgs.CompressedBitmap>
     SubscriberLayer<org.ros.message.compressed_visualization_transport_msgs.CompressedBitmap>
     implements TfLayer {
     implements TfLayer {
 
 
-  private final TextureDrawable occupancyGrid;
+  private static final String TAG = "CompressedBitmapLayer";
+
+  private final TextureDrawable textureDrawable;
 
 
   private boolean ready;
   private boolean ready;
   private String frame;
   private String frame;
@@ -50,43 +54,61 @@ public class CompressedBitmapLayer extends
 
 
   public CompressedBitmapLayer(GraphName topic) {
   public CompressedBitmapLayer(GraphName topic) {
     super(topic, "compressed_visualization_transport_msgs/CompressedBitmap");
     super(topic, "compressed_visualization_transport_msgs/CompressedBitmap");
-    occupancyGrid = new TextureDrawable();
+    textureDrawable = new TextureDrawable();
     ready = false;
     ready = false;
   }
   }
 
 
   @Override
   @Override
   public void draw(GL10 gl) {
   public void draw(GL10 gl) {
     if (ready) {
     if (ready) {
-      occupancyGrid.draw(gl);
+      textureDrawable.draw(gl);
     }
     }
   }
   }
 
 
   @Override
   @Override
   public void onStart(Node node, Handler handler, Camera camera, Transformer transformer) {
   public void onStart(Node node, Handler handler, Camera camera, Transformer transformer) {
     super.onStart(node, handler, camera, transformer);
     super.onStart(node, handler, camera, transformer);
-    getSubscriber()
-        .addMessageListener(
-            new MessageListener<org.ros.message.compressed_visualization_transport_msgs.CompressedBitmap>() {
-              @Override
-              public void onNewMessage(CompressedBitmap compressedBitmap) {
-                BitmapFactory.Options options = new BitmapFactory.Options();
-                options.inPreferredConfig = Bitmap.Config.ARGB_8888;
-                Bitmap bitmap =
-                    BitmapFactory.decodeByteArray(compressedBitmap.data, 0,
-                        compressedBitmap.data.length, options);
-                IntBuffer pixels = IntBuffer.allocate(bitmap.getWidth() * bitmap.getHeight());
-                bitmap.copyPixelsToBuffer(pixels);
-                bitmap.recycle();
-                Bitmap occupancyGridBitmap =
-                    TextureBitmapUtilities.createSquareBitmap(pixels.array(), bitmap.getWidth(),
-                        bitmap.getHeight(), 0xff000000);
-                occupancyGrid.update(compressedBitmap.origin, compressedBitmap.resolution_x,
-                    occupancyGridBitmap);
-                frame = compressedBitmap.header.frame_id;
-                ready = true;
-                requestRender();
-              }
-            });
+    Subscriber<CompressedBitmap> subscriber = getSubscriber();
+    subscriber.setQueueLimit(1);
+    subscriber
+        .addMessageListener(new MessageListener<org.ros.message.compressed_visualization_transport_msgs.CompressedBitmap>() {
+          @Override
+          public void onNewMessage(CompressedBitmap compressedBitmap) {
+            update(compressedBitmap);
+          }
+        });
+  }
+
+  void update(CompressedBitmap compressedBitmap) {
+    Bitmap bitmap;
+    IntBuffer pixels;
+    try {
+      BitmapFactory.Options options = new BitmapFactory.Options();
+      options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+      bitmap =
+          BitmapFactory.decodeByteArray(compressedBitmap.data, 0, compressedBitmap.data.length,
+              options);
+      pixels = IntBuffer.allocate(bitmap.getWidth() * bitmap.getHeight());
+      bitmap.copyPixelsToBuffer(pixels);
+      bitmap.recycle();
+    } catch (OutOfMemoryError e) {
+      Log.e(TAG, "Not enough memory to decode incoming compressed bitmap.", e);
+      return;
+    }
+    Bitmap squareBitmap;
+    try {
+      squareBitmap =
+          TextureBitmapUtilities.createSquareBitmap(pixels.array(), bitmap.getWidth(),
+              bitmap.getHeight(), 0xff000000);
+    } catch (OutOfMemoryError e) {
+      Log.e(TAG, String.format("Not enough memory to render %d x %d pixel bitmap.",
+          bitmap.getWidth(), bitmap.getHeight()), e);
+      return;
+    }
+    textureDrawable.update(compressedBitmap.origin, compressedBitmap.resolution_x, squareBitmap);
+    frame = compressedBitmap.header.frame_id;
+    ready = true;
+    requestRender();
   }
   }
 
 
   @Override
   @Override

+ 4 - 1
android_honeycomb_mr2/src/org/ros/android/views/visualization/layer/SubscriberLayer.java

@@ -16,6 +16,8 @@
 
 
 package org.ros.android.views.visualization.layer;
 package org.ros.android.views.visualization.layer;
 
 
+import com.google.common.base.Preconditions;
+
 import android.os.Handler;
 import android.os.Handler;
 import org.ros.android.views.visualization.Camera;
 import org.ros.android.views.visualization.Camera;
 import org.ros.android.views.visualization.Transformer;
 import org.ros.android.views.visualization.Transformer;
@@ -47,11 +49,12 @@ public class SubscriberLayer<T> extends DefaultLayer {
   
   
   @Override
   @Override
   public void onShutdown(VisualizationView view, Node node) {
   public void onShutdown(VisualizationView view, Node node) {
-    super.onShutdown(view, node);
     subscriber.shutdown();
     subscriber.shutdown();
+    super.onShutdown(view, node);
   }
   }
 
 
   public Subscriber<T> getSubscriber() {
   public Subscriber<T> getSubscriber() {
+    Preconditions.checkNotNull(subscriber);
     return subscriber;
     return subscriber;
   }
   }
 }
 }