ROS交流群
ROS Group
产品服务
Product Service
开源代码库
Github
官网
Official website
技术交流
Technological exchanges
激光雷达
LIDAR
ROS教程
ROS Tourials
深度学习
Deep Learning
机器视觉
Computer Vision

在Android中使用ROS


  • administrators

    由于ROS提供了Android的对应的开发库,我们可以方便的在Android中开发相应的ROS客户端程序。下面介绍一下在Android中使用ROS库的方法。

    1. 开发环境配置

    Android的开发一般使用Android Studio. 其ROS相关的配置方式可以有两种。一种是在ROS环境中使用,另一种是给普通的Android App添加上ROS的依赖库。第二种方式可以在开发机器没有安装ROS的条件下进行开发。由于我使用Windows系统开发Android,所以这里使用第二种方式。

    1.创建Android App项目

    首先在Android Studio中创建一个普通的Android App

    0_1539307156811_6d22da70-2962-4ef5-888b-726e23b5998f-image.png

    设置好项目名称后点击Next

    0_1539307220238_1f5381be-cd02-4833-afee-b459c48e2f85-image.png

    继续点击Next

    0_1539307249051_27ffcaed-e645-4778-af68-15fa883817b3-image.png

    选择Empty Activity后点击Next

    0_1539307281775_256bf658-2614-45f1-92d1-e880741183d1-image.png

    然后点击Finish

    等待项目Sync完成。

    2.修改build.gradle文件

    项目Sync完成之后,在项目左侧的文件列表内会有两个build.gradle文件。其中一个是Project的,另一个是Module的。

    0_1539307502506_ff9ec153-eba0-4e7b-a3a3-d6d4613f010b-image.png

    首先修改Project的build.gradle文件

    把文件中的

    buildscript {
        repositories {
            jcenter()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:2.2.3'
    
            // NOTE: Do not place your application dependencies here; they belong
            // in the individual module build.gradle files
        }
    }
    

    修改为

    buildscript {
      apply from: "https://github.com/rosjava/android_core/raw/kinetic/buildscript.gradle"
    }
    

    然后在文件中添加

    subprojects {
        apply plugin: 'ros-android'
    
        afterEvaluate { project ->
            android {
                // Exclude a few files that are duplicated across our dependencies and
                // prevent packaging Android applications.
                packagingOptions {
                    exclude "META-INF/LICENSE.txt"
                    exclude "META-INF/NOTICE.txt"
                }
            }
        }
    }
    

    然后修改Module的build.gradle,在dependencies 中添加ros依赖

    ...
    dependencies {
        ...
        // You now now add any rosjava dependencies, like so:
        compile 'org.ros.android_core:android_10:[0.3,0.4)'
    }
    ...
    

    同时把dependencies 中的 全部implementation修改为compile。注意修改时的大小写。

    把文件中的compileSdkVersion版本设置为25
    targetSdkVersion也设置为25
    把 com.android.support:appcompat-v7:27.1.1也修改成25的版本

    最后修改完成的文件如下面所示

    apply plugin: 'com.android.application'
    
    android {
        compileSdkVersion 25
        defaultConfig {
            applicationId "org.bwbot.rostest"
            minSdkVersion 15
            targetSdkVersion 25
            versionCode 1
            versionName "1.0"
            testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile 'com.android.support:appcompat-v7:25.4.0'
        compile 'com.android.support.constraint:constraint-layout:1.1.3'
        testCompile 'junit:junit:4.12'
        androidTestCompile 'com.android.support.test:runner:1.0.2'
        androidTestCompile 'com.android.support.test.espresso:espresso-core:3.0.2'
        compile 'org.ros.android_core:android_10:[0.3,0.4)'
    }
    

    3.修改AndroidManifest.xml文件

    此时如果编译项目会出现下面的错误

    Manifest merger failed : Attribute application@icon value=(@mipmap/ic_launcher) from AndroidManifest.xml:7:9-43
    	is also present at [org.ros.android_core:android_10:0.3.3] AndroidManifest.xml:19:9-36 value=(@mipmap/icon).
    	Suggestion: add 'tools:replace="android:icon"' to <application> element at AndroidManifest.xml:5:5-19:19 to override.
    

    此时需要修改AndroidManifest.xml文件在application项目中做如下修改

    <application xmlns:tools="http://schemas.android.com/tools"
            tools:replace="android:icon"
            ...
    

    为了能够正常使用还需要给app添加网络权限。在AndroidManifest.xml文件中添加

    <uses-permission android:name="android.permission.INTERNET"/>
    

    最后的AndroidManifest.xml文件如下

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="org.bwbot.rostest">
        <uses-permission android:name="android.permission.INTERNET"/>
        <application
            xmlns:tools="http://schemas.android.com/tools"
            tools:replace="android:icon"
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            <activity android:name=".MainActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
    </manifest>
    

    此时项目已经可以成功编译了。

    2. 写一个简单的消息发布程序

    MainActivity.java内容如下

    package org.bwbot.rostest;
    
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    
    import org.ros.android.RosActivity;
    import org.ros.concurrent.CancellableLoop;
    import org.ros.namespace.GraphName;
    import org.ros.node.ConnectedNode;
    import org.ros.node.Node;
    import org.ros.node.NodeConfiguration;
    import org.ros.node.NodeMain;
    import org.ros.node.NodeMainExecutor;
    import org.ros.node.topic.Publisher;
    
    import java.net.URI;
    
    import std_msgs.String;
    
    public class MainActivity extends RosActivity {
    
        protected MainActivity() {
            super("ros_test", "ros_test", URI.create("http://192.168.0.23:11311")); // 这里是ROS_MASTER_URI
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    
        @Override
        protected void init(NodeMainExecutor nodeMainExecutor) {
            NodeConfiguration nodeConfiguration = NodeConfiguration.newPublic(getRosHostname());
            nodeConfiguration.setMasterUri(getMasterUri());
            nodeMainExecutor.execute(new NodeMain() {
                @Override
                public GraphName getDefaultNodeName() {
                    return GraphName.of("ros_test");
                }
    
                @Override
                public void onStart(ConnectedNode connectedNode) {
                    final Publisher<std_msgs.String> pub =  connectedNode.newPublisher("/test", String._TYPE);
                    connectedNode.executeCancellableLoop(new CancellableLoop() {
                        @Override
                        protected void loop() throws InterruptedException {
                            std_msgs.String msg = pub.newMessage();
                            msg.setData("hello world");
                            pub.publish(msg);
                            Thread.sleep(1000);
                        }
                    });
                }
    
                @Override
                public void onShutdown(Node node) {
    
                }
    
                @Override
                public void onShutdownComplete(Node node) {
    
                }
    
                @Override
                public void onError(Node node, Throwable throwable) {
    
                }
            }, nodeConfiguration);
        }
    }
    
    

    编译后,在手机上运行App。然后再运行的ROS的主机上打印/test话题

    0_1539309423828_6d76f7f8-7323-4eb0-af7f-25ecf8e92acd-image.png

    可以看到消息已经成功发送出来了。

    本项目已经发布到 Github

    3. 注意事项

    1. compileSdkVersion 版本问题
      由于在新版本的Android中,com.android.support:appcompat-v7软件包移除了一些组件。而这些组件ROS的库使用到了。所以在Android SDK > 25的版本中无法使用Android ROS. 所以我们要在配置文件中修改SDK版本。

    2. Android模拟器网络
      Android模拟器默认是有NAT转换,所以使用虚拟机是无法访问到局域网内的ROS Master的。开发时建议使用实际的手机。

    4. 如何使用自定义的消息类型

    使用自己定义的消息需要首先生成消息的jar库文件,然后导入项目依赖。

    下面以小强的galileo_serial_server里面的消息为例。

    首先安装rosjava相关的依赖包

    sudo apt-get install ros-kinetic-genjava
    sudo apt-get install ros-kinetic-rosjava*
    

    然后catkin_make具有相关消息的软件包

    catkin_make -DCATKIN_WHITELIST_PACKAGES="galileo_serial_server"
    

    可以看到其输出如下

    WARNING: Package name "NLlinepatrol_planner" does not follow the naming conventions. It should start with a lower case letter and only contain lower case letters, digits, underscores, and dashes.
    [ 73%] Built target galileo_serial_server_generate_messages_java
    Scanning dependencies of target galileo_serial_server_node
    [ 78%] Building CXX object galileo_serial_server/CMakeFiles/galileo_serial_server_node.dir/src/galileo_serial_server_node.cpp.o
    [ 84%] Building CXX object galileo_serial_server/CMakeFiles/galileo_serial_server_node.dir/src/galileo_serial_server.cpp.o
    [ 89%] Building CXX object galileo_serial_server/CMakeFiles/galileo_serial_server_node.dir/src/AsyncSerial.cpp.o
    [ 94%] Compiling Java code for galileo_serial_server
    [100%] Linking CXX executable /home/xiaoqiang/Documents/ros/devel/lib/galileo_serial_server/galileo_serial_server_node
    [100%] Built target galileo_serial_server_node
    warning: [options] bootstrap class path not set in conjunction with -source 1.7
    1 warning
    Uploading: org/ros/rosjava_messages/galileo_serial_server/1.0.0/galileo_serial_server-1.0.0.jar to repository remote at file:/home/xiaoqiang/Documents/ros/devel/share/maven/
    Transferring 2K from remote
    Uploaded 2K
    [100%] Built target galileo_serial_server_generate_messages_java_gradle
    Scanning dependencies of target galileo_serial_server_generate_messages
    [100%] Built target galileo_serial_server_generate_messages
    

    从输出中可以看出,消息的jar包已经生成到了/home/xiaoqiang/Documents/ros/devel/share/maven/文件夹中

    0_1539311040223_4410be69-f46d-4f19-9622-c91dc1da7fa8-image.png

    把此jar文件复制到Android项目中的 app\libs文件夹中。

    右键点击app,在弹出的菜单中点击Open Module Settings

    0_1539311262707_927ba11a-1a93-4994-a662-d4ff2997a67f-image.png

    0_1539311306670_c41eb533-ad59-43f9-b98d-5724913526c0-image.png

    选择dependencies页面,然后点击右侧加号

    选择jar dependencies,然后选择jar文件点击确认就可以了

    0_1539311384074_13a799a9-3aff-448a-a5b4-61fad7960cf3-image.png

    这样就可以在程序中使用自定义的消息了。

    5. ROS Android的程序设计模式

    在ROS Android库中,ROS的相关操作都是异步的(通过回调的方式),比如创建节点,发布和订阅消息。这个在使用中会比较麻烦。比如我们需要实现点击一个按钮就发布一个消息的功能。就需要把这个发布消息的程序封装成一个类,然后继承CancellableLoop。在loop中发布消息。同时提供一个发布消息的方法给别人调用。
    比如下面的方法

    
    public class GalileoCommander extends CancellableLoop {
    
        private Publisher<galileo_serial_server.GalileoNativeCmds> pub;
        private ConnectedNode node;
        private Queue<byte[]> cmdList;
    
        public GalileoCommander(ConnectedNode connectedNode){
            node = connectedNode;
            cmdList = new LinkedBlockingQueue<>(100);
            pub = connectedNode.newPublisher("/test", GalileoNativeCmds._TYPE);
        }
    
        public void sendCmds(byte[] cmds){
            if(!cmdList.offer(cmds)){
                cmdList.poll();
                cmdList.offer(cmds);
            }
        }
    
        @Override
        protected void loop() throws InterruptedException {
            byte[] cmd = cmdList.poll();
            if(cmd == null){
                Thread.sleep(1);
            }else{
                galileo_serial_server.GalileoNativeCmds galileo_cmd =  pub.newMessage();
                galileo_cmd.setLength(cmd.length);
                pub.publish(galileo_cmd);
            }
        }
    }
    

    使用时

    nodeMainExecutor.execute(new NodeMain() {
        @Override
        public GraphName getDefaultNodeName() {
            return GraphName.of("test_node");
        }
    
        @Override
        public void onStart(ConnectedNode connectedNode) {
            galileoCommander = new GalileoCommander(connectedNode);
            connectedNode.executeCancellableLoop(galileoCommander);
        }
    }, nodeConfiguration)
    

    这是我目前想到的比较好的模式,如果有其他更好的方法也欢迎交流。



  • @weijiz 我的程序也跑通了,是一个设置出了问题,修改过程是:
    打开IDE,进入File—>Setting—>Build,Execution,Deployment—>Instant Run
    将将enable instant run to hot swap code/resource changes on deploy勾上!
    0_1539602762564_Screenshot from 2018-10-15 19-25-14.png


  • administrators

    @bitliuyu 你的程序我可以正常运行额

    0_1539598709253_424f26be-ce25-4643-95d6-ed09d0b2b5f8-image.png

    用的是这个配置
    0_1539598792004_e3effb8e-02a4-4b50-9e42-97b5e018da13-image.png



  • @weijiz 这是我上传到github上的代码
    https://github.com/BITLiuYu/ROSTest.git
    多谢您能帮我看一下


  • administrators

    @bitliuyu在Android中使用ROS 中说:

    lass<android.app.AppComponentFactory>

    从错误看不像是gradle的设置问题。你可以把你的代码发github上,我给你看看。



  • @weijiz 可以运行自带的HelloWorld,但是改为您写的MainActivity就会闪退,URI.create我已经改为自己的ip地址了。logcat错误信息为:
    10-15 17:16:56.627 32148-32148/org.bwbot.rostest E/AndroidRuntime: FATAL EXCEPTION: main
    Process: org.bwbot.rostest, PID: 32148
    java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{org.bwbot.rostest/org.bwbot.rostest.MainActivity}: java.lang.IllegalAccessException: void org.bwbot.rostest.MainActivity.() is not accessible from java.lang.Class<android.app.AppComponentFactory>
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2843)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
    at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
    at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loop(Looper.java:193)
    at android.app.ActivityThread.main(ActivityThread.java:6669)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
    Caused by: java.lang.IllegalAccessException: void org.bwbot.rostest.MainActivity.() is not accessible from java.lang.Class<android.app.AppComponentFactory>
    at java.lang.Class.newInstance(Native Method)
    at android.app.AppComponentFactory.instantiateActivity(AppComponentFactory.java:69)
    at android.app.Instrumentation.newActivity(Instrumentation.java:1215)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2831)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048) 
    at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78) 
    at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108) 
    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68) 
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808) 
    at android.os.Handler.dispatchMessage(Handler.java:106) 
    at android.os.Looper.loop(Looper.java:193) 
    at android.app.ActivityThread.main(ActivityThread.java:6669) 
    at java.lang.reflect.Method.invoke(Native Method) 
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) 
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)


  • administrators

    @bitliuyu在Android中使用ROS 中说:

    versionName

    可以运行例子的Android项目吗?
    闪退后Logcat里面的错误是什么?
    是不是没有设置网络权限?



  • 我的系统是Ubuntu,所有的设置也是按照您的教程做的,但是run的时候就立即闪退。
    其中,修改app的build.gradle时出现错误:

    Error:Conflict with dependency 'com.google.code.findbugs:jsr305' in project ':app'. Resolved versions for app (1.3.9) and test app (2.0.1) differ. See http://g.co/androidstudio/app-test-app-conflict for details.
    

    按照网上的方式,进行修改,在Android节点下添加:

    configurations.all {
    resolutionStrategy.force 'com.google.code.findbugs:jsr305:1.3.9'
    }
    

    不知道是否会影响

    我的build.gradle为:

    apply plugin: 'com.android.application'
    
    android {
    compileSdkVersion 25
    //buildToolsVersion '25.0.0'
    defaultConfig {
    applicationId "com.example.rostest"
    minSdkVersion 15
    targetSdkVersion 25
    versionCode 1
    versionName "1.0"
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
    release {
    minifyEnabled false
    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
    }
    productFlavors {
    }
    
    configurations.all {
        resolutionStrategy.force 'com.google.code.findbugs:jsr305:1.3.9'
    }
    
    }
    
    dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
    exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.+'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'
    
    //androidTestCompile 'com.android.support.test:runner:1.0.2'
    //androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
    
    compile 'org.ros.android_core:android_10:[0.3,0.4)'
    
    }