Getting ROS 2 Running on Android (It’s Easier Than You Think!)
So you want to run ROS 2 on your Android phone? Maybe you want to be able to control your robot from your phone, or maybe you want to integrate a phone on your robot. Well, you can do it with jros2!
What’s jros2?
jros2 is basically ROS 2 but for Java. The cool part? You don’t need to install ROS 2 at all. Just add one dependency to your Android project and you’re publishing and subscribing to topics. It works on Linux, Windows, macOS, and Android. It can even handle custom message types!

The Super Minimal Example
Here’s the absolute barebones example to get you started. This creates a node that publishes a simple string message:
package com.example.ros2app;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import us.ihmc.jros2.ROS2Node;
import us.ihmc.jros2.ROS2Publisher;
import us.ihmc.jros2.ROS2Topic;
public class MainActivity extends AppCompatActivity {
private ROS2Node node;
private Thread publishThread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Create a ROS2 node
node = new ROS2Node("android_node");
// Create a topic and publisher
ROS2Topic<std_msgs.String_> topic =
new ROS2Topic<>("/android/hello", std_msgs.String_.class);
ROS2Publisher<std_msgs.String_> publisher = node.createPublisher(topic);
// Publish in a background thread
publishThread = new Thread(() -> {
std_msgs.String_ msg = new std_msgs.String_();
while (!node.isClosed()) {
msg.setData("Hello from Android at " + System.currentTimeMillis());
publisher.publish(msg);
try {
Thread.sleep(1000); // 1 Hz
} catch (InterruptedException e) {
break;
}
}
});
publishThread.start();
}
@Override
protected void onDestroy() {
if (node != null) {
node.close();
}
super.onDestroy();
}
}
That’s it! About 40 lines and you’ve got a working ROS2 publisher on Android.
Want to Subscribe Instead?
Receiving messages is even easier:
ROS2Node node = new ROS2Node("subscriber_node");
ROS2Topic<std_msgs.String> topic =
new ROS2Topic<>("/chatter", std_msgs.String_.class);
node.createSubscriptionSampler(topic, msg -> {
// This runs when a message arrives
runOnUiThread(() -> {
textView.setText(msg.getData());
});
});
The runOnUiThread() part is important – ROS callbacks happen on background threads, so if you want to update your UI, you need to hop back to the main thread.
Real-World Example: Publishing Phone Sensors
Here’s something actually useful – publishing your phone’s accelerometer data to ROS2. This is from a test app I made:
public class MainActivity extends AppCompatActivity implements SensorEventListener {
private ROS2Node ros2Node;
private ROS2Publisher<geometry_msgs.PoseStamped> publisher;
private SensorManager sensorManager;
private Sensor rotationSensor;
private float[] rotationVector = new float[4];
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Initialize ROS2
ros2Node = new ROS2Node("phone_imu");
ROS2Topic<geometry_msgs.PoseStamped> topic =
new ROS2Topic<>("/phone/pose", geometry_msgs.PoseStamped.class);
publisher = ros2Node.createPublisher(topic);
// Set up sensors
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
rotationSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
sensorManager.registerListener(this, rotationSensor,
SensorManager.SENSOR_DELAY_GAME);
}
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) {
// Copy rotation vector data
System.arraycopy(event.values, 0, rotationVector, 0,
Math.min(event.values.length, 4));
// Create and publish ROS message
geometry_msgs.PoseStamped pose = new geometry_msgs.PoseStamped();
pose.getHeader().setFrameId("phone");
// Set timestamp
long nanos = System.currentTimeMillis() * 1_000_000;
pose.getHeader().getStamp().setSec((int)(nanos / 1_000_000_000));
pose.getHeader().getStamp().setNanosec((int)(nanos % 1_000_000_000));
// Set orientation from sensor
pose.getPose().getOrientation().setX(rotationVector[0]);
pose.getPose().getOrientation().setY(rotationVector[1]);
pose.getPose().getOrientation().setZ(rotationVector[2]);
pose.getPose().getOrientation().setW(rotationVector[3]);
publisher.publish(pose);
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {}
@Override
protected void onDestroy() {
sensorManager.unregisterListener(this);
if (ros2Node != null) {
ros2Node.close();
}
super.onDestroy();
}
}
Setup
First, add the IHMC repository to your settings.gradle.kts:
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven {
url = uri("https://robotlabfiles.ihmc.us/repository/")
}
}
}
Then in your app’s build.gradle.kts:
android {
compileSdk = 35
defaultConfig {
minSdk = 26
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
isCoreLibraryDesugaringEnabled = true
}
packaging {
resources {
excludes += "/META-INF/LICENSE.md"
excludes += "/META-INF/NOTICE.md"
}
}
}
dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
implementation("us.ihmc:jros2-android:1.2.1")
}
And don’t forget permissions in AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
Things to Watch Out For
-
Threading – ROS callbacks run on background threads. Always use
runOnUiThread()to update UI. -
Lifecycle Management – Create your node in
onCreate()and always close it inonDestroy(). -
Discovery Issues – Make sure your phone and ROS 2 system are on the same WiFi network (or use USB tethering for a wired connection).
Testing It Out
Fire up your app, then on your ROS 2 machine:
ros2 topic list
# You should see /android/hello or whatever you named your topic
ros2 topic echo /android/hello
# You should see the messages being published
Potential Use Cases
You can now use your phone as a cheap mobile sensor platform for your robot. IMU data, camera images, GPS, microphone audio are all accessible through Android’s sensor APIs and publishable to ROS 2. Plus, your phone probably has better sensors than whatever’s on your robot anyway.
Have fun, and also check out the wiki!