ROS Group 产品服务
Product Service 开源代码库
Github 官网
Official website 技术交流
Technological exchanges 激光雷达
LIDAR ROS教程
ROS Tourials 深度学习
Deep Learning 机器视觉
Computer Vision
在Android中使用ROS
-
由于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
设置好项目名称后点击Next
继续点击Next
选择Empty Activity后点击Next
然后点击Finish
等待项目Sync完成。
2.修改build.gradle文件
项目Sync完成之后,在项目左侧的文件列表内会有两个build.gradle文件。其中一个是Project的,另一个是Module的。
首先修改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
话题可以看到消息已经成功发送出来了。
本项目已经发布到 Github
3. 注意事项
-
compileSdkVersion 版本问题
由于在新版本的Android中,com.android.support:appcompat-v7
软件包移除了一些组件。而这些组件ROS的库使用到了。所以在Android SDK > 25的版本中无法使用Android ROS. 所以我们要在配置文件中修改SDK版本。 -
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/
文件夹中把此jar文件复制到Android项目中的
app\libs
文件夹中。右键点击app,在弹出的菜单中点击Open Module Settings
选择dependencies页面,然后点击右侧加号
选择jar dependencies,然后选择jar文件点击确认就可以了
这样就可以在程序中使用自定义的消息了。
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)
这是我目前想到的比较好的模式,如果有其他更好的方法也欢迎交流。
-
-
我的系统是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)' }
-
-
@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) -
@bitliuyu 在 在Android中使用ROS 中说:
lass<android.app.AppComponentFactory>
从错误看不像是gradle的设置问题。你可以把你的代码发github上,我给你看看。
-
@weijiz 这是我上传到github上的代码
https://github.com/BITLiuYu/ROSTest.git
多谢您能帮我看一下 -
@bitliuyu 你的程序我可以正常运行额
用的是这个配置
-
@weijiz 我的程序也跑通了,是一个设置出了问题,修改过程是:
打开IDE,进入File—>Setting—>Build,Execution,Deployment—>Instant Run
将将enable instant run to hot swap code/resource changes on deploy勾上!
-
请问导入的自建消息,如何在jni中调用呢?
-
@gongyue666 好像是给java用的,如果是给c++用的自建消息应该如何导入呢?
-
为什么编译总是这个错误?是依赖错了吗?
Caused by: org.gradle.api.resources.ResourceException: Could not get resource 'https://github.com/rosjava/android_core/raw/kinetic/buildscript.gradle'. at org.gradle.internal.resource.ResourceExceptions.failure(ResourceExceptions.java:74) at org.gradle.internal.resource.ResourceExceptions.getFailed(ResourceExceptions.java:57) at org.gradle.api.internal.artifacts.repositories.resolver.DefaultExternalResourceAccessor.resolve(DefaultExternalResourceAccessor.java:67) at org.gradle.api.internal.artifacts.repositories.resolver.DefaultExternalResourceAccessor.resolveUri(DefaultExternalResourceAccessor.java:47) at org.gradle.internal.resource.transfer.DefaultUriTextResourceLoader.loadUri(DefaultUriTextResourceLoader.java:43) at org.gradle.api.internal.plugins.DefaultObjectConfigurationAction.applyScript(DefaultObjectConfigurationAction.java:103) at org.gradle.api.internal.plugins.DefaultObjectConfigurationAction.access$000(DefaultObjectConfigurationAction.java:38) at org.gradle.api.internal.plugins.DefaultObjectConfigurationAction$1.run(DefaultObjectConfigurationAction.java:68) at org.gradle.api.internal.plugins.DefaultObjectConfigurationAction.execute(DefaultObjectConfigurationAction.java:143) at org.gradle.api.internal.project.AbstractPluginAware.apply(AbstractPluginAware.java:46) at org.gradle.api.internal.project.ProjectScript.apply(ProjectScript.java:34) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93) at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325) at org.gradle.internal.metaobject.BeanDynamicObject$MetaClassAdapter.invokeMethod(BeanDynamicObject.java:479) at org.gradle.internal.metaobject.BeanDynamicObject.tryInvokeMethod(BeanDynamicObject.java:191) at org.gradle.groovy.scripts.BasicScript$ScriptDynamicObject.tryInvokeMethod(BasicScript.java:130) at org.gradle.internal.metaobject.ConfigureDelegate.invokeMethod(ConfigureDelegate.java:78) at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeOnDelegationObjects(ClosureMetaClass.java:430) at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:369) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1022) at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:69)
Caused by: org.gradle.internal.resource.transport.http.HttpRequestException: Could not HEAD 'https://github.com/rosjava/android_core/raw/kinetic/buildscript.gradle'. at org.gradle.internal.resource.transport.http.HttpClientHelper.performRequest(HttpClientHelper.java:96) at org.gradle.internal.resource.transport.http.HttpClientHelper.performRawHead(HttpClientHelper.java:72) at org.gradle.internal.resource.transport.http.HttpClientHelper.performHead(HttpClientHelper.java:76) at org.gradle.internal.resource.transport.http.HttpResourceAccessor.getMetaData(HttpResourceAccessor.java:65) at org.gradle.internal.resource.transfer.DefaultExternalResourceConnector.getMetaData(DefaultExternalResourceConnector.java:63) at org.gradle.internal.resource.transfer.AccessorBackedExternalResource.getMetaData(AccessorBackedExternalResource.java:201) at org.gradle.internal.resource.BuildOperationFiringExternalResourceDecorator$1.call(BuildOperationFiringExternalResourceDecorator.java:61) at org.gradle.internal.resource.BuildOperationFiringExternalResourceDecorator$1.call(BuildOperationFiringExternalResourceDecorator.java:58) at org.gradle.internal.progress.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:350) at org.gradle.internal.progress.DefaultBuildOperationExecutor$CallableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:340) at org.gradle.internal.progress.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:199) at org.gradle.internal.progress.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:120) at org.gradle.internal.resource.BuildOperationFiringExternalResourceDecorator.getMetaData(BuildOperationFiringExternalResourceDecorator.java:58) at org.gradle.internal.resource.transfer.DefaultCacheAwareExternalResourceAccessor$1.create(DefaultCacheAwareExternalResourceAccessor.java:102) at org.gradle.internal.resource.transfer.DefaultCacheAwareExternalResourceAccessor$1.create(DefaultCacheAwareExternalResourceAccessor.java:82) at org.gradle.cache.internal.ProducerGuard$AdaptiveProducerGuard.guardByKey(ProducerGuard.java:97) at org.gradle.internal.resource.transfer.DefaultCacheAwareExternalResourceAccessor.getResource(DefaultCacheAwareExternalResourceAccessor.java:82)