前提
我一直想捣鼓一下机器人开发,想着自己独立去设计一款机器人,但了解到整个流程中所要学习的技能以及自己为数不多的业余时间,这事儿让我觉得有点犯难。直到去年看到古月居发布了OriginBot智能小车,在看完它的整体资料后,我觉得它有以下几个特点比较吸引我:
- 开源特性,基本上展现了如何从头到尾去设计开发一款小型的机器人,这和我的初始想法非常吻合。
- 软硬件的可拓展性,旭日X3派提升了它的可开发性,毕竟我不希望买它回来只是跑跑例程。
- 价格,在同类型的产品中它的价格比较亲民。
在组装好小车并跑了一些例程后,我觉得小车还缺少一个遥控器,毕竟不能一直端着笔记本电脑去控制小车吧,但是我又不想买手柄,于是我把目光投向了我身边的那部沉睡已久的安卓手机,想法来了,那就用手机去遥控小车吧。
其实用手机控制小车这也不是什么新鲜事,网上例子也很多,蓝牙,WiFi都可以,虽然都是学习,但是我也想整点不一样的,后来查资料发现ROS2也有安卓的版本,这就正合我意了,而且有了ROS2也能做更多的事情了。
我其实并不会安卓开发,也不会java,但是安卓开发作为一门很成熟的技术,咱们程序员稍微学习一下,开发点简单的应用应该没什么问题。问题的关键是如何编译ROS2的安卓版本,下面就来讲讲整个编译过程以及我踩过的坑吧。
编译过程
系统:Ubuntu22.04
GitHub上的原项目:https://github.com/esteve/ros2_java
编译的步骤基本和原项目一致,但是这个项目的版本比较老,有些步骤直接使用会报错,所以经过我踩坑后的步骤如下:
- 配置Android SDK
可以直接下载Android studio进行配置,在初次启动Android studio时会提示用户安装必要的sdk和其他模块,一般Ubuntu系统会安装在用户目录下。
- 配置Android NDK
可以直接在Android studio中进行下载配置,也可以前往NDK官网下载,下载后可以和SDK放在同一目录,至于NDK的版本选最新的就可以。
-
设置环境变量
在.bashrc文件最后一排设置SDK和NDK的环境变量,如下
export ANDROID_HOEM=~/Android/Sdk export PATH=$PATH:$ANDROID_HOEM/tools:$ANDROID_HOEM/platform-tool export ANDROID_NDK=~/Android/ndk-r25b
export ANDROID_HOEM=~/Android/Sdk
export PATH=$PATH:$ANDROID_HOEM/tools:$ANDROID_HOEM/platform-tool
export ANDROID_NDK=~/Android/ndk-r25b
export ANDROID_HOEM=~/Android/Sdk export PATH=$PATH:$ANDROID_HOEM/tools:$ANDROID_HOEM/platform-tool export ANDROID_NDK=~/Android/ndk-r25b
export ANDROID_HOEM=~/Android/Sdk export PATH=$PATH:$ANDROID_HOEM/tools:$ANDROID_HOEM/platform-tool export ANDROID_NDK=~/Android/ndk-r25b
export ANDROID_HOEM=~/Android/Sdk export PATH=$PATH:$ANDROID_HOEM/tools:$ANDROID_HOEM/platform-tool export ANDROID_NDK=~/Android/ndk-r25b
export ANDROID_HOEM=~/Android/Sdk export PATH=$PATH:$ANDROID_HOEM/tools:$ANDROID_HOEM/platform-tool export ANDROID_NDK=~/Android/ndk-r25b
export ANDROID_HOEM=~/Android/Sdk export PATH=$PATH:$ANDROID_HOEM/tools:$ANDROID_HOEM/platform-tool export ANDROID_NDK=~/Android/ndk-r25b
复制
-
克隆ros2和ros2 java源码
mkdir -p $HOME/ros2_android_ws/src cd $HOME/ros2_android_ws curl https://raw.githubusercontent.com/ros2-java/ros2_java/main/ros2_java_android.repos | vcs import src
mkdir -p $HOME/ros2_android_ws/src
cd $HOME/ros2_android_ws
curl https://raw.githubusercontent.com/ros2-java/ros2_java/main/ros2_java_android.repos | vcs import src
mkdir -p $HOME/ros2_android_ws/src cd $HOME/ros2_android_ws curl https://raw.githubusercontent.com/ros2-java/ros2_java/main/ros2_java_android.repos | vcs import src
mkdir -p $HOME/ros2_android_ws/src cd $HOME/ros2_android_ws curl https://raw.githubusercontent.com/ros2-java/ros2_java/main/ros2_java_android.repos | vcs import src
mkdir -p $HOME/ros2_android_ws/src cd $HOME/ros2_android_ws curl https://raw.githubusercontent.com/ros2-java/ros2_java/main/ros2_java_android.repos | vcs import src
复制
mkdir -p $HOME/ros2_android_ws/src cd $HOME/ros2_android_ws curl https://raw.githubusercontent.com/ros2-java/ros2_java/main/ros2_java_android.repos | vcs import src
mkdir -p $HOME/ros2_android_ws/src cd $HOME/ros2_android_ws curl https://raw.githubusercontent.com/ros2-java/ros2_java/main/ros2_java_android.repos | vcs import src
复制
原项目的版本是ros2 galactic,想要换成最新的长期支持版本humble也可以,需要将ros2_java_android.repos里面的galactic字样修改为humble,其中Fast DDS的版本也可以更新为最新的版本。
我也整理了一份humble的repos:
curl https://raw.githubusercontent.com/uglymie/ros2-humble-for-android/main/ros2_java_android.repos | vcs import src
curl https://raw.githubusercontent.com/uglymie/ros2-humble-for-android/main/ros2_java_android.repos | vcs import src
curl https://raw.githubusercontent.com/uglymie/ros2-humble-for-android/main/ros2_java_android.repos | vcs import src
curl https://raw.githubusercontent.com/uglymie/ros2-humble-for-android/main/ros2_java_android.repos | vcs import src
curl https://raw.githubusercontent.com/uglymie/ros2-humble-for-android/main/ros2_java_android.repos | vcs import src
curl https://raw.githubusercontent.com/uglymie/ros2-humble-for-android/main/ros2_java_android.repos | vcs import src
curl https://raw.githubusercontent.com/uglymie/ros2-humble-for-android/main/ros2_java_android.repos | vcs import src
复制
此处要特别注意一个问题,因为本项目需要大量下载GitHub上的ROS2相关模块,所以要求电脑终端必须要能流畅的访问GitHub,强调一下,这个是必要条件。
最终得到的文件目录如下图:
-
设置编译配置
export PYTHON3_EXEC="$( which python3 )" export PYTHON3_LIBRARY="$( ${PYTHON3_EXEC} -c 'import os.path; from distutils import sysconfig; print(os.path.realpath(os.path.join(sysconfig.get_config_var("LIBPL"), sysconfig.get_config_var("LDLIBRARY"))))' )" export PYTHON3_INCLUDE_DIR="$( ${PYTHON3_EXEC} -c 'from distutils import sysconfig; print(sysconfig.get_config_var("INCLUDEPY"))' )" export ANDROID_ABI=armeabi-v7a export ANDROID_NATIVE_API_LEVEL=android-29 export ANDROID_TOOLCHAIN_NAME=arm-linux-androideabi-clang
export PYTHON3_EXEC="$( which python3 )"
export PYTHON3_LIBRARY="$( ${PYTHON3_EXEC} -c 'import os.path; from distutils import sysconfig; print(os.path.realpath(os.path.join(sysconfig.get_config_var("LIBPL"), sysconfig.get_config_var("LDLIBRARY"))))' )"
export PYTHON3_INCLUDE_DIR="$( ${PYTHON3_EXEC} -c 'from distutils import sysconfig; print(sysconfig.get_config_var("INCLUDEPY"))' )"
export ANDROID_ABI=armeabi-v7a
export ANDROID_NATIVE_API_LEVEL=android-29
export ANDROID_TOOLCHAIN_NAME=arm-linux-androideabi-clang
export PYTHON3_EXEC="$( which python3 )" export PYTHON3_LIBRARY="$( ${PYTHON3_EXEC} -c 'import os.path; from distutils import sysconfig; print(os.path.realpath(os.path.join(sysconfig.get_config_var("LIBPL"), sysconfig.get_config_var("LDLIBRARY"))))' )" export PYTHON3_INCLUDE_DIR="$( ${PYTHON3_EXEC} -c 'from distutils import sysconfig; print(sysconfig.get_config_var("INCLUDEPY"))' )" export ANDROID_ABI=armeabi-v7a export ANDROID_NATIVE_API_LEVEL=android-29 export ANDROID_TOOLCHAIN_NAME=arm-linux-androideabi-clang
export PYTHON3_EXEC="$( which python3 )" export PYTHON3_LIBRARY="$( ${PYTHON3_EXEC} -c 'import os.path; from distutils import sysconfig; print(os.path.realpath(os.path.join(sysconfig.get_config_var("LIBPL"), sysconfig.get_config_var("LDLIBRARY"))))' )" export PYTHON3_INCLUDE_DIR="$( ${PYTHON3_EXEC} -c 'from distutils import sysconfig; print(sysconfig.get_config_var("INCLUDEPY"))' )" export ANDROID_ABI=armeabi-v7a export ANDROID_NATIVE_API_LEVEL=android-29 export ANDROID_TOOLCHAIN_NAME=arm-linux-androideabi-clang
export PYTHON3_EXEC="$( which python3 )" export PYTHON3_LIBRARY="$( ${PYTHON3_EXEC} -c 'import os.path; from distutils import sysconfig; print(os.path.realpath(os.path.join(sysconfig.get_config_var("LIBPL"), sysconfig.get_config_var("LDLIBRARY"))))' )" export PYTHON3_INCLUDE_DIR="$( ${PYTHON3_EXEC} -c 'from distutils import sysconfig; print(sysconfig.get_config_var("INCLUDEPY"))' )" export ANDROID_ABI=armeabi-v7a export ANDROID_NATIVE_API_LEVEL=android-29 export ANDROID_TOOLCHAIN_NAME=arm-linux-androideabi-clang
export PYTHON3_EXEC="$( which python3 )" export PYTHON3_LIBRARY="$( ${PYTHON3_EXEC} -c 'import os.path; from distutils import sysconfig; print(os.path.realpath(os.path.join(sysconfig.get_config_var("LIBPL"), sysconfig.get_config_var("LDLIBRARY"))))' )" export PYTHON3_INCLUDE_DIR="$( ${PYTHON3_EXEC} -c 'from distutils import sysconfig; print(sysconfig.get_config_var("INCLUDEPY"))' )" export ANDROID_ABI=armeabi-v7a export ANDROID_NATIVE_API_LEVEL=android-29 export ANDROID_TOOLCHAIN_NAME=arm-linux-androideabi-clang
export PYTHON3_EXEC="$( which python3 )" export PYTHON3_LIBRARY="$( ${PYTHON3_EXEC} -c 'import os.path; from distutils import sysconfig; print(os.path.realpath(os.path.join(sysconfig.get_config_var("LIBPL"), sysconfig.get_config_var("LDLIBRARY"))))' )" export PYTHON3_INCLUDE_DIR="$( ${PYTHON3_EXEC} -c 'from distutils import sysconfig; print(sysconfig.get_config_var("INCLUDEPY"))' )" export ANDROID_ABI=armeabi-v7a export ANDROID_NATIVE_API_LEVEL=android-29 export ANDROID_TOOLCHAIN_NAME=arm-linux-androideabi-clang
复制
其中ANDROID相关选项可以更换,如
export ANDROID_ABI=arm64-v8a export ANDROID_NATIVE_API_LEVEL=android-29 export ANDROID_TOOLCHAIN_NAME=aarch64-linux-android-clang
export ANDROID_ABI=arm64-v8a
export ANDROID_NATIVE_API_LEVEL=android-29
export ANDROID_TOOLCHAIN_NAME=aarch64-linux-android-clang
export ANDROID_ABI=arm64-v8a export ANDROID_NATIVE_API_LEVEL=android-29 export ANDROID_TOOLCHAIN_NAME=aarch64-linux-android-clang
export ANDROID_ABI=arm64-v8a export ANDROID_NATIVE_API_LEVEL=android-29 export ANDROID_TOOLCHAIN_NAME=aarch64-linux-android-clang
export ANDROID_ABI=arm64-v8a export ANDROID_NATIVE_API_LEVEL=android-29 export ANDROID_TOOLCHAIN_NAME=aarch64-linux-android-clang
export ANDROID_ABI=arm64-v8a export ANDROID_NATIVE_API_LEVEL=android-29 export ANDROID_TOOLCHAIN_NAME=aarch64-linux-android-clang
export ANDROID_ABI=arm64-v8a export ANDROID_NATIVE_API_LEVEL=android-29 export ANDROID_TOOLCHAIN_NAME=aarch64-linux-android-clang
复制
这里我们选择 ANDROID_ABI 为arm64-v8a
-
编译命令
colcon build \ --packages-ignore cyclonedds rcl_logging_log4cxx rcl_logging_spdlog rosidl_generator_py rclandroid ros2_talker_android ros2_listener_android \ --cmake-args \ -DPYTHON_EXECUTABLE=${PYTHON3_EXEC} \ -DPYTHON_LIBRARY=${PYTHON3_LIBRARY} \ -DPYTHON_INCLUDE_DIR=${PYTHON3_INCLUDE_DIR} \ -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} \ -DANDROID=ON \ -DANDROID_FUNCTION_LEVEL_LINKING=OFF \ -DANDROID_NATIVE_API_LEVEL=${ANDROID_TARGET} \ -DANDROID_TOOLCHAIN_NAME=${ANDROID_TOOLCHAIN_NAME} \ -DANDROID_STL=c++_shared \ -DANDROID_ABI=${ANDROID_ABI} \ -DANDROID_NDK=${ANDROID_NDK} \ -DTHIRDPARTY=ON \ -DCOMPILE_EXAMPLES=OFF \ -DCMAKE_FIND_ROOT_PATH="${PWD}/install" \ -DBUILD_TESTING=OFF \ -DRCL_LOGGING_IMPLEMENTATION=rcl_logging_noop \ -DTHIRDPARTY_android-ifaddrs=FORCE
colcon build \
--packages-ignore cyclonedds rcl_logging_log4cxx rcl_logging_spdlog rosidl_generator_py rclandroid ros2_talker_android ros2_listener_android \
--cmake-args \
-DPYTHON_EXECUTABLE=${PYTHON3_EXEC} \
-DPYTHON_LIBRARY=${PYTHON3_LIBRARY} \
-DPYTHON_INCLUDE_DIR=${PYTHON3_INCLUDE_DIR} \
-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} \
-DANDROID=ON \
-DANDROID_FUNCTION_LEVEL_LINKING=OFF \
-DANDROID_NATIVE_API_LEVEL=${ANDROID_TARGET} \
-DANDROID_TOOLCHAIN_NAME=${ANDROID_TOOLCHAIN_NAME} \
-DANDROID_STL=c++_shared \
-DANDROID_ABI=${ANDROID_ABI} \
-DANDROID_NDK=${ANDROID_NDK} \
-DTHIRDPARTY=ON \
-DCOMPILE_EXAMPLES=OFF \
-DCMAKE_FIND_ROOT_PATH="${PWD}/install" \
-DBUILD_TESTING=OFF \
-DRCL_LOGGING_IMPLEMENTATION=rcl_logging_noop \
-DTHIRDPARTY_android-ifaddrs=FORCE
colcon build \ --packages-ignore cyclonedds rcl_logging_log4cxx rcl_logging_spdlog rosidl_generator_py rclandroid ros2_talker_android ros2_listener_android \ --cmake-args \ -DPYTHON_EXECUTABLE=${PYTHON3_EXEC} \ -DPYTHON_LIBRARY=${PYTHON3_LIBRARY} \ -DPYTHON_INCLUDE_DIR=${PYTHON3_INCLUDE_DIR} \ -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} \ -DANDROID=ON \ -DANDROID_FUNCTION_LEVEL_LINKING=OFF \ -DANDROID_NATIVE_API_LEVEL=${ANDROID_TARGET} \ -DANDROID_TOOLCHAIN_NAME=${ANDROID_TOOLCHAIN_NAME} \ -DANDROID_STL=c++_shared \ -DANDROID_ABI=${ANDROID_ABI} \ -DANDROID_NDK=${ANDROID_NDK} \ -DTHIRDPARTY=ON \ -DCOMPILE_EXAMPLES=OFF \ -DCMAKE_FIND_ROOT_PATH="${PWD}/install" \ -DBUILD_TESTING=OFF \ -DRCL_LOGGING_IMPLEMENTATION=rcl_logging_noop \ -DTHIRDPARTY_android-ifaddrs=FORCE
colcon build \ --packages-ignore cyclonedds rcl_logging_log4cxx rcl_logging_spdlog rosidl_generator_py rclandroid ros2_talker_android ros2_listener_android \ --cmake-args \ -DPYTHON_EXECUTABLE=${PYTHON3_EXEC} \ -DPYTHON_LIBRARY=${PYTHON3_LIBRARY} \ -DPYTHON_INCLUDE_DIR=${PYTHON3_INCLUDE_DIR} \ -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} \ -DANDROID=ON \ -DANDROID_FUNCTION_LEVEL_LINKING=OFF \ -DANDROID_NATIVE_API_LEVEL=${ANDROID_TARGET} \ -DANDROID_TOOLCHAIN_NAME=${ANDROID_TOOLCHAIN_NAME} \ -DANDROID_STL=c++_shared \ -DANDROID_ABI=${ANDROID_ABI} \ -DANDROID_NDK=${ANDROID_NDK} \ -DTHIRDPARTY=ON \ -DCOMPILE_EXAMPLES=OFF \ -DCMAKE_FIND_ROOT_PATH="${PWD}/install" \ -DBUILD_TESTING=OFF \ -DRCL_LOGGING_IMPLEMENTATION=rcl_logging_noop \ -DTHIRDPARTY_android-ifaddrs=FORCE
colcon build \ --packages-ignore cyclonedds rcl_logging_log4cxx rcl_logging_spdlog rosidl_generator_py rclandroid ros2_talker_android ros2_listener_android \ --cmake-args \ -DPYTHON_EXECUTABLE=${PYTHON3_EXEC} \ -DPYTHON_LIBRARY=${PYTHON3_LIBRARY} \ -DPYTHON_INCLUDE_DIR=${PYTHON3_INCLUDE_DIR} \ -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} \ -DANDROID=ON \ -DANDROID_FUNCTION_LEVEL_LINKING=OFF \ -DANDROID_NATIVE_API_LEVEL=${ANDROID_TARGET} \ -DANDROID_TOOLCHAIN_NAME=${ANDROID_TOOLCHAIN_NAME} \ -DANDROID_STL=c++_shared \ -DANDROID_ABI=${ANDROID_ABI} \ -DANDROID_NDK=${ANDROID_NDK} \ -DTHIRDPARTY=ON \ -DCOMPILE_EXAMPLES=OFF \ -DCMAKE_FIND_ROOT_PATH="${PWD}/install" \ -DBUILD_TESTING=OFF \ -DRCL_LOGGING_IMPLEMENTATION=rcl_logging_noop \ -DTHIRDPARTY_android-ifaddrs=FORCE
colcon build \ --packages-ignore cyclonedds rcl_logging_log4cxx rcl_logging_spdlog rosidl_generator_py rclandroid ros2_talker_android ros2_listener_android \ --cmake-args \ -DPYTHON_EXECUTABLE=${PYTHON3_EXEC} \ -DPYTHON_LIBRARY=${PYTHON3_LIBRARY} \ -DPYTHON_INCLUDE_DIR=${PYTHON3_INCLUDE_DIR} \ -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} \ -DANDROID=ON \ -DANDROID_FUNCTION_LEVEL_LINKING=OFF \ -DANDROID_NATIVE_API_LEVEL=${ANDROID_TARGET} \ -DANDROID_TOOLCHAIN_NAME=${ANDROID_TOOLCHAIN_NAME} \ -DANDROID_STL=c++_shared \ -DANDROID_ABI=${ANDROID_ABI} \ -DANDROID_NDK=${ANDROID_NDK} \ -DTHIRDPARTY=ON \ -DCOMPILE_EXAMPLES=OFF \ -DCMAKE_FIND_ROOT_PATH="${PWD}/install" \ -DBUILD_TESTING=OFF \ -DRCL_LOGGING_IMPLEMENTATION=rcl_logging_noop \ -DTHIRDPARTY_android-ifaddrs=FORCE
colcon build \ --packages-ignore cyclonedds rcl_logging_log4cxx rcl_logging_spdlog rosidl_generator_py rclandroid ros2_talker_android ros2_listener_android \ --cmake-args \ -DPYTHON_EXECUTABLE=${PYTHON3_EXEC} \ -DPYTHON_LIBRARY=${PYTHON3_LIBRARY} \ -DPYTHON_INCLUDE_DIR=${PYTHON3_INCLUDE_DIR} \ -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} \ -DANDROID=ON \ -DANDROID_FUNCTION_LEVEL_LINKING=OFF \ -DANDROID_NATIVE_API_LEVEL=${ANDROID_TARGET} \ -DANDROID_TOOLCHAIN_NAME=${ANDROID_TOOLCHAIN_NAME} \ -DANDROID_STL=c++_shared \ -DANDROID_ABI=${ANDROID_ABI} \ -DANDROID_NDK=${ANDROID_NDK} \ -DTHIRDPARTY=ON \ -DCOMPILE_EXAMPLES=OFF \ -DCMAKE_FIND_ROOT_PATH="${PWD}/install" \ -DBUILD_TESTING=OFF \ -DRCL_LOGGING_IMPLEMENTATION=rcl_logging_noop \ -DTHIRDPARTY_android-ifaddrs=FORCE
复制
此编译命令经过多次问题排查更改,已经可以避免大多数错误。但是仍然可能出现其他错误:
fatal error: jni.h: No such file or directory
fatal error: jni.h: No such file or directory
fatal error: jni.h: No such file or directory
fatal error: jni.h: No such file or directory
fatal error: jni.h: No such file or directory
fatal error: jni.h: No such file or directory
fatal error: jni.h: No such file or directory
复制
解决方法: 可以添加全局搜索路径到 .bashrc 或者配置文件 /etc/profile 中,其中java-11-openjdk-amd64为当前系统已经安装的jdk版本,CPATH关键字表示适用于所有语言。
export CPATH=/usr/lib/jvm/java-11-openjdk-amd64/include:$CPATH export CPATH=/usr/lib/jvm/java-11-openjdk-amd64/include/linux:$CPATH
export CPATH=/usr/lib/jvm/java-11-openjdk-amd64/include:$CPATH
export CPATH=/usr/lib/jvm/java-11-openjdk-amd64/include/linux:$CPATH
export CPATH=/usr/lib/jvm/java-11-openjdk-amd64/include:$CPATH export CPATH=/usr/lib/jvm/java-11-openjdk-amd64/include/linux:$CPATH
export CPATH=/usr/lib/jvm/java-11-openjdk-amd64/include:$CPATH export CPATH=/usr/lib/jvm/java-11-openjdk-amd64/include/linux:$CPATH
export CPATH=/usr/lib/jvm/java-11-openjdk-amd64/include:$CPATH export CPATH=/usr/lib/jvm/java-11-openjdk-amd64/include/linux:$CPATH
复制
export CPATH=/usr/lib/jvm/java-11-openjdk-amd64/include:$CPATH export CPATH=/usr/lib/jvm/java-11-openjdk-amd64/include/linux:$CPATH
export CPATH=/usr/lib/jvm/java-11-openjdk-amd64/include:$CPATH export CPATH=/usr/lib/jvm/java-11-openjdk-amd64/include/linux:$CPATH
复制
- 编译到Fast-DDS时可能出现找不到asio和tinyxml2的头文件
解决方法: 可以在其目录下(eProsima/Fast-DDS)
的CMakeList.txt文件里添加包含头文件路径
ros2_android_ws/src/eProsima/Fast-DDS
include_directories(thirdparty/asio/asio/include)
include_directories(thirdparty/tinyxml2)
include_directories(thirdparty/asio/asio/include) include_directories(thirdparty/tinyxml2)
include_directories(thirdparty/asio/asio/include) include_directories(thirdparty/tinyxml2)
include_directories(thirdparty/asio/asio/include) include_directories(thirdparty/tinyxml2)
复制
include_directories(thirdparty/asio/asio/include) include_directories(thirdparty/tinyxml2)
include_directories(thirdparty/asio/asio/include) include_directories(thirdparty/tinyxml2)
复制
include_directories(thirdparty/asio/asio/include) include_directories(thirdparty/tinyxml2)
打包过程
- jar文件和so文件
编译完成后在install目录下会生成很多文件夹,其中包含了所有的jar文件和so文件,可以直接在目录下搜索,如下图:
可以将两种文件分别拷贝并整理到单独的文件目录下,比如:
ros2-humble/arm64-v8a/jar ros2-humble/arm64-v8a/so
ros2-humble/arm64-v8a/jar
ros2-humble/arm64-v8a/so
ros2-humble/arm64-v8a/jar ros2-humble/arm64-v8a/so
ros2-humble/arm64-v8a/jar ros2-humble/arm64-v8a/so
ros2-humble/arm64-v8a/jar ros2-humble/arm64-v8a/so
复制
ros2-humble/arm64-v8a/jar ros2-humble/arm64-v8a/so
ros2-humble/arm64-v8a/jar ros2-humble/arm64-v8a/so
复制
2. 根据需要加载库文件
编译好的jar文件和so文件包含了比较完整的ros常用功能包,以下是so文件:
可以看到共有1144个文件,事实上很多消息类的库文件我们是按需所用,比如项目中只需要用到std_msgs,那么其他的消息类库文件就可以不用再加载,如果加载所有的库文件会使最终的apk文件变得比较大。
测试应用程序
下面是一个测试APP的简单说明,这个应用也是我之前学习的时候在GitHub上找到的:https://github.com/YasuChiba/ros2-android-test-app
https://github.com/YasuChiba/ros2-android-test-app
我觉得拿来作为测试用的应用比较合适,当然这个应用使用的库文件是ros2 galactic编译好的,我将其替换成humble也是没问题的。
测试APP主界面包括四个Button和一个TextView(空白处),如下图:
要实现的功能也比较简单,建立一个发布者和一个订阅者,定时发布消息,其话题名称为/chatter,消息类型为字符串,可以开始和暂停发布或订阅。
Android相关代码这里就不做说明,可以看上面完整的项目;下面看一下在java语言下的ros2发布及订阅节点的实现。
发布者节点,TalkerNode.java:
package com.example.ros2_android_test_app;
import java.util.concurrent.TimeUnit;
import android.util.Log;
import org.ros2.rcljava.node.BaseComposableNode; // 引入ROS2节点相关库
import org.ros2.rcljava.publisher.Publisher; // 引入发布者相关库
import org.ros2.rcljava.timer.WallTimer; // 引入计时器相关库
public class TalkerNode extends BaseComposableNode {
private static String logtag = TalkerNode.class.getName();
private final String topic; // 定义节点发布的话题
public Publisher<std_msgs.msg.String> publisher; // 声明发布者
private int count; // 定义计数器
private WallTimer timer; // 声明计时器
public TalkerNode(final String name, final String topic) {
super(name);
this.topic = topic;
// 创建发布者
this.publisher = this.node.<std_msgs.msg.String>createPublisher(
std_msgs.msg.String.class, this.topic);
}
public void start() {
Log.d(logtag, "TalkerNode::start()");
if (this.timer != null) {
this.timer.cancel(); // 如果计时器已存在,取消计时器
}
this.count = 0; // 将计数器归零
// 创建计时器,每500毫秒执行一次onTimer函数
this.timer = node.createWallTimer(500, TimeUnit.MILLISECONDS, this::onTimer);
}
private void onTimer() {
std_msgs.msg.String msg = new std_msgs.msg.String(); // 创建消息对象
msg.setData("Hello! ROS2 Humble! " + this.count); // 设置消息内容
this.count++; // 计数器自增
this.publisher.publish(msg); // 发布消息
}
public void stop() {
Log.d(logtag, "TalkerNode::stop()");
if (this.timer != null) {
this.timer.cancel(); // 如果计时器已存在,取消计时器
}
}
package com.example.ros2_android_test_app; import java.util.concurrent.TimeUnit; import android.util.Log; import org.ros2.rcljava.node.BaseComposableNode; // 引入ROS2节点相关库 import org.ros2.rcljava.publisher.Publisher; // 引入发布者相关库 import org.ros2.rcljava.timer.WallTimer; // 引入计时器相关库 public class TalkerNode extends BaseComposableNode { private static String logtag = TalkerNode.class.getName(); private final String topic; // 定义节点发布的话题 public Publisher<std_msgs.msg.String> publisher; // 声明发布者 private int count; // 定义计数器 private WallTimer timer; // 声明计时器 public TalkerNode(final String name, final String topic) { super(name); this.topic = topic; // 创建发布者 this.publisher = this.node.<std_msgs.msg.String>createPublisher( std_msgs.msg.String.class, this.topic); } public void start() { Log.d(logtag, "TalkerNode::start()"); if (this.timer != null) { this.timer.cancel(); // 如果计时器已存在,取消计时器 } this.count = 0; // 将计数器归零 // 创建计时器,每500毫秒执行一次onTimer函数 this.timer = node.createWallTimer(500, TimeUnit.MILLISECONDS, this::onTimer); } private void onTimer() { std_msgs.msg.String msg = new std_msgs.msg.String(); // 创建消息对象 msg.setData("Hello! ROS2 Humble! " + this.count); // 设置消息内容 this.count++; // 计数器自增 this.publisher.publish(msg); // 发布消息 } public void stop() { Log.d(logtag, "TalkerNode::stop()"); if (this.timer != null) { this.timer.cancel(); // 如果计时器已存在,取消计时器 } }
package com.example.ros2_android_test_app; import java.util.concurrent.TimeUnit; import android.util.Log; import org.ros2.rcljava.node.BaseComposableNode; // 引入ROS2节点相关库 import org.ros2.rcljava.publisher.Publisher; // 引入发布者相关库 import org.ros2.rcljava.timer.WallTimer; // 引入计时器相关库 public class TalkerNode extends BaseComposableNode { private static String logtag = TalkerNode.class.getName(); private final String topic; // 定义节点发布的话题 public Publisher<std_msgs.msg.String> publisher; // 声明发布者 private int count; // 定义计数器 private WallTimer timer; // 声明计时器 public TalkerNode(final String name, final String topic) { super(name); this.topic = topic; // 创建发布者 this.publisher = this.node.<std_msgs.msg.String>createPublisher( std_msgs.msg.String.class, this.topic); } public void start() { Log.d(logtag, "TalkerNode::start()"); if (this.timer != null) { this.timer.cancel(); // 如果计时器已存在,取消计时器 } this.count = 0; // 将计数器归零 // 创建计时器,每500毫秒执行一次onTimer函数 this.timer = node.createWallTimer(500, TimeUnit.MILLISECONDS, this::onTimer); } private void onTimer() { std_msgs.msg.String msg = new std_msgs.msg.String(); // 创建消息对象 msg.setData("Hello! ROS2 Humble! " + this.count); // 设置消息内容 this.count++; // 计数器自增 this.publisher.publish(msg); // 发布消息 } public void stop() { Log.d(logtag, "TalkerNode::stop()"); if (this.timer != null) { this.timer.cancel(); // 如果计时器已存在,取消计时器 } }
package com.example.ros2_android_test_app; import java.util.concurrent.TimeUnit; import android.util.Log; import org.ros2.rcljava.node.BaseComposableNode; // 引入ROS2节点相关库 import org.ros2.rcljava.publisher.Publisher; // 引入发布者相关库 import org.ros2.rcljava.timer.WallTimer; // 引入计时器相关库 public class TalkerNode extends BaseComposableNode { private static String logtag = TalkerNode.class.getName(); private final String topic; // 定义节点发布的话题 public Publisher<std_msgs.msg.String> publisher; // 声明发布者 private int count; // 定义计数器 private WallTimer timer; // 声明计时器 public TalkerNode(final String name, final String topic) { super(name); this.topic = topic; // 创建发布者 this.publisher = this.node.<std_msgs.msg.String>createPublisher( std_msgs.msg.String.class, this.topic); } public void start() { Log.d(logtag, "TalkerNode::start()"); if (this.timer != null) { this.timer.cancel(); // 如果计时器已存在,取消计时器 } this.count = 0; // 将计数器归零 // 创建计时器,每500毫秒执行一次onTimer函数 this.timer = node.createWallTimer(500, TimeUnit.MILLISECONDS, this::onTimer); } private void onTimer() { std_msgs.msg.String msg = new std_msgs.msg.String(); // 创建消息对象 msg.setData("Hello! ROS2 Humble! " + this.count); // 设置消息内容 this.count++; // 计数器自增 this.publisher.publish(msg); // 发布消息 } public void stop() { Log.d(logtag, "TalkerNode::stop()"); if (this.timer != null) { this.timer.cancel(); // 如果计时器已存在,取消计时器 } }
复制
package com.example.ros2_android_test_app; import java.util.concurrent.TimeUnit; import android.util.Log; import org.ros2.rcljava.node.BaseComposableNode; // 引入ROS2节点相关库 import org.ros2.rcljava.publisher.Publisher; // 引入发布者相关库 import org.ros2.rcljava.timer.WallTimer; // 引入计时器相关库 public class TalkerNode extends BaseComposableNode { private static String logtag = TalkerNode.class.getName(); private final String topic; // 定义节点发布的话题 public Publisher<std_msgs.msg.String> publisher; // 声明发布者 private int count; // 定义计数器 private WallTimer timer; // 声明计时器 public TalkerNode(final String name, final String topic) { super(name); this.topic = topic; // 创建发布者 this.publisher = this.node.<std_msgs.msg.String>createPublisher( std_msgs.msg.String.class, this.topic); } public void start() { Log.d(logtag, "TalkerNode::start()"); if (this.timer != null) { this.timer.cancel(); // 如果计时器已存在,取消计时器 } this.count = 0; // 将计数器归零 // 创建计时器,每500毫秒执行一次onTimer函数 this.timer = node.createWallTimer(500, TimeUnit.MILLISECONDS, this::onTimer); } private void onTimer() { std_msgs.msg.String msg = new std_msgs.msg.String(); // 创建消息对象 msg.setData("Hello! ROS2 Humble! " + this.count); // 设置消息内容 this.count++; // 计数器自增 this.publisher.publish(msg); // 发布消息 } public void stop() { Log.d(logtag, "TalkerNode::stop()"); if (this.timer != null) { this.timer.cancel(); // 如果计时器已存在,取消计时器 } }
package com.example.ros2_android_test_app; import java.util.concurrent.TimeUnit; import android.util.Log; import org.ros2.rcljava.node.BaseComposableNode; // 引入ROS2节点相关库 import org.ros2.rcljava.publisher.Publisher; // 引入发布者相关库 import org.ros2.rcljava.timer.WallTimer; // 引入计时器相关库 public class TalkerNode extends BaseComposableNode { private static String logtag = TalkerNode.class.getName(); private final String topic; // 定义节点发布的话题 public Publisher<std_msgs.msg.String> publisher; // 声明发布者 private int count; // 定义计数器 private WallTimer timer; // 声明计时器 public TalkerNode(final String name, final String topic) { super(name); this.topic = topic; // 创建发布者 this.publisher = this.node.<std_msgs.msg.String>createPublisher( std_msgs.msg.String.class, this.topic); } public void start() { Log.d(logtag, "TalkerNode::start()"); if (this.timer != null) { this.timer.cancel(); // 如果计时器已存在,取消计时器 } this.count = 0; // 将计数器归零 // 创建计时器,每500毫秒执行一次onTimer函数 this.timer = node.createWallTimer(500, TimeUnit.MILLISECONDS, this::onTimer); } private void onTimer() { std_msgs.msg.String msg = new std_msgs.msg.String(); // 创建消息对象 msg.setData("Hello! ROS2 Humble! " + this.count); // 设置消息内容 this.count++; // 计数器自增 this.publisher.publish(msg); // 发布消息 } public void stop() { Log.d(logtag, "TalkerNode::stop()"); if (this.timer != null) { this.timer.cancel(); // 如果计时器已存在,取消计时器 } }
复制
package com.example.ros2_android_test_app; import java.util.concurrent.TimeUnit; import android.util.Log; import org.ros2.rcljava.node.BaseComposableNode; // 引入ROS2节点相关库 import org.ros2.rcljava.publisher.Publisher; // 引入发布者相关库 import org.ros2.rcljava.timer.WallTimer; // 引入计时器相关库 public class TalkerNode extends BaseComposableNode { private static String logtag = TalkerNode.class.getName(); private final String topic; // 定义节点发布的话题 public Publisher<std_msgs.msg.String> publisher; // 声明发布者 private int count; // 定义计数器 private WallTimer timer; // 声明计时器 public TalkerNode(final String name, final String topic) { super(name); this.topic = topic; // 创建发布者 this.publisher = this.node.<std_msgs.msg.String>createPublisher( std_msgs.msg.String.class, this.topic); } public void start() { Log.d(logtag, "TalkerNode::start()"); if (this.timer != null) { this.timer.cancel(); // 如果计时器已存在,取消计时器 } this.count = 0; // 将计数器归零 // 创建计时器,每500毫秒执行一次onTimer函数 this.timer = node.createWallTimer(500, TimeUnit.MILLISECONDS, this::onTimer); } private void onTimer() { std_msgs.msg.String msg = new std_msgs.msg.String(); // 创建消息对象 msg.setData("Hello! ROS2 Humble! " + this.count); // 设置消息内容 this.count++; // 计数器自增 this.publisher.publish(msg); // 发布消息 } public void stop() { Log.d(logtag, "TalkerNode::stop()"); if (this.timer != null) { this.timer.cancel(); // 如果计时器已存在,取消计时器 } }
订阅者节点,ListenerNode.java:
package com.example.ros2_android_test_app;
import android.util.Log;
import android.widget.TextView;
import org.ros2.rcljava.node.BaseComposableNode;
import org.ros2.rcljava.subscription.Subscription;
public class ListenerNode extends BaseComposableNode {
private final String topic; // 订阅的 ROS2 消息主题名称
private final TextView listenerView; // 用于在 Android UI 上显示消息内容的 TextView 控件
private Subscription<std_msgs.msg.String> subscriber; // ROS2 订阅者对象,用于接收消息
public ListenerNode(final String name, final String topic,
final TextView listenerView) {
super(name); // 调用父类 BaseComposableNode 的构造方法,传入节点名称
this.topic = topic; // 保存订阅的主题名称
this.listenerView = listenerView; // 保存用于显示消息内容的 TextView 控件
// 创建 ROS2 订阅者对象,订阅指定主题的 std_msgs/String 类型的消息
this.subscriber = this.node.<std_msgs.msg.String>createSubscription(
std_msgs.msg.String.class, this.topic, msg
-> {
// 当接收到新消息时,将其内容显示在 TextView 控件上
this.listenerView.setText("Hello ROS2 from Android: " + msg.getData() +
"\r\n" + listenerView.getText());
});
}
}
package com.example.ros2_android_test_app; import android.util.Log; import android.widget.TextView; import org.ros2.rcljava.node.BaseComposableNode; import org.ros2.rcljava.subscription.Subscription; public class ListenerNode extends BaseComposableNode { private final String topic; // 订阅的 ROS2 消息主题名称 private final TextView listenerView; // 用于在 Android UI 上显示消息内容的 TextView 控件 private Subscription<std_msgs.msg.String> subscriber; // ROS2 订阅者对象,用于接收消息 public ListenerNode(final String name, final String topic, final TextView listenerView) { super(name); // 调用父类 BaseComposableNode 的构造方法,传入节点名称 this.topic = topic; // 保存订阅的主题名称 this.listenerView = listenerView; // 保存用于显示消息内容的 TextView 控件 // 创建 ROS2 订阅者对象,订阅指定主题的 std_msgs/String 类型的消息 this.subscriber = this.node.<std_msgs.msg.String>createSubscription( std_msgs.msg.String.class, this.topic, msg -> { // 当接收到新消息时,将其内容显示在 TextView 控件上 this.listenerView.setText("Hello ROS2 from Android: " + msg.getData() + "\r\n" + listenerView.getText()); }); } }
package com.example.ros2_android_test_app; import android.util.Log; import android.widget.TextView; import org.ros2.rcljava.node.BaseComposableNode; import org.ros2.rcljava.subscription.Subscription; public class ListenerNode extends BaseComposableNode { private final String topic; // 订阅的 ROS2 消息主题名称 private final TextView listenerView; // 用于在 Android UI 上显示消息内容的 TextView 控件 private Subscription<std_msgs.msg.String> subscriber; // ROS2 订阅者对象,用于接收消息 public ListenerNode(final String name, final String topic, final TextView listenerView) { super(name); // 调用父类 BaseComposableNode 的构造方法,传入节点名称 this.topic = topic; // 保存订阅的主题名称 this.listenerView = listenerView; // 保存用于显示消息内容的 TextView 控件 // 创建 ROS2 订阅者对象,订阅指定主题的 std_msgs/String 类型的消息 this.subscriber = this.node.<std_msgs.msg.String>createSubscription( std_msgs.msg.String.class, this.topic, msg -> { // 当接收到新消息时,将其内容显示在 TextView 控件上 this.listenerView.setText("Hello ROS2 from Android: " + msg.getData() + "\r\n" + listenerView.getText()); }); } }
package com.example.ros2_android_test_app; import android.util.Log; import android.widget.TextView; import org.ros2.rcljava.node.BaseComposableNode; import org.ros2.rcljava.subscription.Subscription; public class ListenerNode extends BaseComposableNode { private final String topic; // 订阅的 ROS2 消息主题名称 private final TextView listenerView; // 用于在 Android UI 上显示消息内容的 TextView 控件 private Subscription<std_msgs.msg.String> subscriber; // ROS2 订阅者对象,用于接收消息 public ListenerNode(final String name, final String topic, final TextView listenerView) { super(name); // 调用父类 BaseComposableNode 的构造方法,传入节点名称 this.topic = topic; // 保存订阅的主题名称 this.listenerView = listenerView; // 保存用于显示消息内容的 TextView 控件 // 创建 ROS2 订阅者对象,订阅指定主题的 std_msgs/String 类型的消息 this.subscriber = this.node.<std_msgs.msg.String>createSubscription( std_msgs.msg.String.class, this.topic, msg -> { // 当接收到新消息时,将其内容显示在 TextView 控件上 this.listenerView.setText("Hello ROS2 from Android: " + msg.getData() + "\r\n" + listenerView.getText()); }); } }
复制
package com.example.ros2_android_test_app; import android.util.Log; import android.widget.TextView; import org.ros2.rcljava.node.BaseComposableNode; import org.ros2.rcljava.subscription.Subscription; public class ListenerNode extends BaseComposableNode { private final String topic; // 订阅的 ROS2 消息主题名称 private final TextView listenerView; // 用于在 Android UI 上显示消息内容的 TextView 控件 private Subscription<std_msgs.msg.String> subscriber; // ROS2 订阅者对象,用于接收消息 public ListenerNode(final String name, final String topic, final TextView listenerView) { super(name); // 调用父类 BaseComposableNode 的构造方法,传入节点名称 this.topic = topic; // 保存订阅的主题名称 this.listenerView = listenerView; // 保存用于显示消息内容的 TextView 控件 // 创建 ROS2 订阅者对象,订阅指定主题的 std_msgs/String 类型的消息 this.subscriber = this.node.<std_msgs.msg.String>createSubscription( std_msgs.msg.String.class, this.topic, msg -> { // 当接收到新消息时,将其内容显示在 TextView 控件上 this.listenerView.setText("Hello ROS2 from Android: " + msg.getData() + "\r\n" + listenerView.getText()); }); } }
package com.example.ros2_android_test_app; import android.util.Log; import android.widget.TextView; import org.ros2.rcljava.node.BaseComposableNode; import org.ros2.rcljava.subscription.Subscription; public class ListenerNode extends BaseComposableNode { private final String topic; // 订阅的 ROS2 消息主题名称 private final TextView listenerView; // 用于在 Android UI 上显示消息内容的 TextView 控件 private Subscription<std_msgs.msg.String> subscriber; // ROS2 订阅者对象,用于接收消息 public ListenerNode(final String name, final String topic, final TextView listenerView) { super(name); // 调用父类 BaseComposableNode 的构造方法,传入节点名称 this.topic = topic; // 保存订阅的主题名称 this.listenerView = listenerView; // 保存用于显示消息内容的 TextView 控件 // 创建 ROS2 订阅者对象,订阅指定主题的 std_msgs/String 类型的消息 this.subscriber = this.node.<std_msgs.msg.String>createSubscription( std_msgs.msg.String.class, this.topic, msg -> { // 当接收到新消息时,将其内容显示在 TextView 控件上 this.listenerView.setText("Hello ROS2 from Android: " + msg.getData() + "\r\n" + listenerView.getText()); }); } }
复制
用于管理 ROS2 的执行器(Executor)和在 Android 设备上运行的 ROS2 节点,ROSActivity.java:
package com.example.hyperbot;
import android.os.Bundle;
import android.os.Handler;
import androidx.appcompat.app.AppCompatActivity;
import org.ros2.rcljava.RCLJava;
import org.ros2.rcljava.executors.Executor;
import org.ros2.rcljava.executors.SingleThreadedExecutor;
import java.util.Timer;
import java.util.TimerTask;
public class ROSActivity extends AppCompatActivity {
private Executor rosExecutor; // ROS2 执行器对象,用于处理节点的消息
private Timer timer; // 定时器对象,定时执行节点的 spinSome() 方法
private Handler handler; // Android UI 线程的 Handler 对象,用于在 UI 线程上执行节点的 spinSome() 方法
private static String logtag = ROSActivity.class.getName();
private static long SPINNER_DELAY = 0; // 定时器的启动延迟时间(单位:毫秒)
private static long SPINNER_PERIOD_MS = 200; // 定时器的周期时间(单位:毫秒)
// 生命周期方法,当活动第一次创建时调用
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.handler = new Handler(getMainLooper()); // 创建 Android UI 线程的 Handler 对象
RCLJava.rclJavaInit(); // 初始化 RCLJava 库
this.rosExecutor = this.createExecutor(); // 创建 ROS2 执行器对象
}
// 生命周期方法,当活动从暂停状态恢复时调用
@Override
protected void onResume() {
super.onResume();
timer = new Timer(); // 创建定时器对象
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
Runnable runnable = new Runnable() {
public void run() {
rosExecutor.spinSome(); // 在 UI 线程上执行节点的 spinSome() 方法
}
};
handler.post(runnable); // 将 Runnable 对象提交到 UI 线程的消息队列中, 避免在子线程中更新UI
}
}, this.getDelay(), this.getPeriod()); // 启动定时器,定时执行节点的 spinSome() 方法
}
// 生命周期方法,当活动暂停时调用
@Override
protected void onPause() {
super.onPause();
if (timer != null) {
timer.cancel(); // 取消定时器的执行
}
}
public void run() {
rosExecutor.spinSome(); // 执行节点的 spinSome() 方法
}
public Executor getExecutor() {
return this.rosExecutor; // 获取 ROS2 执行器对象
}
protected Executor createExecutor() {
return new SingleThreadedExecutor(); // 创建单线程的 ROS2 执行器对象
}
protected long getDelay() {
return SPINNER_DELAY; // 获取定时器的启动延迟时间
}
protected long getPeriod() {
return SPINNER_PERIOD_MS; // 获取定时器的周期时间
}
}
package com.example.hyperbot; import android.os.Bundle; import android.os.Handler; import androidx.appcompat.app.AppCompatActivity; import org.ros2.rcljava.RCLJava; import org.ros2.rcljava.executors.Executor; import org.ros2.rcljava.executors.SingleThreadedExecutor; import java.util.Timer; import java.util.TimerTask; public class ROSActivity extends AppCompatActivity { private Executor rosExecutor; // ROS2 执行器对象,用于处理节点的消息 private Timer timer; // 定时器对象,定时执行节点的 spinSome() 方法 private Handler handler; // Android UI 线程的 Handler 对象,用于在 UI 线程上执行节点的 spinSome() 方法 private static String logtag = ROSActivity.class.getName(); private static long SPINNER_DELAY = 0; // 定时器的启动延迟时间(单位:毫秒) private static long SPINNER_PERIOD_MS = 200; // 定时器的周期时间(单位:毫秒) // 生命周期方法,当活动第一次创建时调用 @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.handler = new Handler(getMainLooper()); // 创建 Android UI 线程的 Handler 对象 RCLJava.rclJavaInit(); // 初始化 RCLJava 库 this.rosExecutor = this.createExecutor(); // 创建 ROS2 执行器对象 } // 生命周期方法,当活动从暂停状态恢复时调用 @Override protected void onResume() { super.onResume(); timer = new Timer(); // 创建定时器对象 timer.scheduleAtFixedRate(new TimerTask() { public void run() { Runnable runnable = new Runnable() { public void run() { rosExecutor.spinSome(); // 在 UI 线程上执行节点的 spinSome() 方法 } }; handler.post(runnable); // 将 Runnable 对象提交到 UI 线程的消息队列中, 避免在子线程中更新UI } }, this.getDelay(), this.getPeriod()); // 启动定时器,定时执行节点的 spinSome() 方法 } // 生命周期方法,当活动暂停时调用 @Override protected void onPause() { super.onPause(); if (timer != null) { timer.cancel(); // 取消定时器的执行 } } public void run() { rosExecutor.spinSome(); // 执行节点的 spinSome() 方法 } public Executor getExecutor() { return this.rosExecutor; // 获取 ROS2 执行器对象 } protected Executor createExecutor() { return new SingleThreadedExecutor(); // 创建单线程的 ROS2 执行器对象 } protected long getDelay() { return SPINNER_DELAY; // 获取定时器的启动延迟时间 } protected long getPeriod() { return SPINNER_PERIOD_MS; // 获取定时器的周期时间 } }
package com.example.hyperbot; import android.os.Bundle; import android.os.Handler; import androidx.appcompat.app.AppCompatActivity; import org.ros2.rcljava.RCLJava; import org.ros2.rcljava.executors.Executor; import org.ros2.rcljava.executors.SingleThreadedExecutor; import java.util.Timer; import java.util.TimerTask; public class ROSActivity extends AppCompatActivity { private Executor rosExecutor; // ROS2 执行器对象,用于处理节点的消息 private Timer timer; // 定时器对象,定时执行节点的 spinSome() 方法 private Handler handler; // Android UI 线程的 Handler 对象,用于在 UI 线程上执行节点的 spinSome() 方法 private static String logtag = ROSActivity.class.getName(); private static long SPINNER_DELAY = 0; // 定时器的启动延迟时间(单位:毫秒) private static long SPINNER_PERIOD_MS = 200; // 定时器的周期时间(单位:毫秒) // 生命周期方法,当活动第一次创建时调用 @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.handler = new Handler(getMainLooper()); // 创建 Android UI 线程的 Handler 对象 RCLJava.rclJavaInit(); // 初始化 RCLJava 库 this.rosExecutor = this.createExecutor(); // 创建 ROS2 执行器对象 } // 生命周期方法,当活动从暂停状态恢复时调用 @Override protected void onResume() { super.onResume(); timer = new Timer(); // 创建定时器对象 timer.scheduleAtFixedRate(new TimerTask() { public void run() { Runnable runnable = new Runnable() { public void run() { rosExecutor.spinSome(); // 在 UI 线程上执行节点的 spinSome() 方法 } }; handler.post(runnable); // 将 Runnable 对象提交到 UI 线程的消息队列中, 避免在子线程中更新UI } }, this.getDelay(), this.getPeriod()); // 启动定时器,定时执行节点的 spinSome() 方法 } // 生命周期方法,当活动暂停时调用 @Override protected void onPause() { super.onPause(); if (timer != null) { timer.cancel(); // 取消定时器的执行 } } public void run() { rosExecutor.spinSome(); // 执行节点的 spinSome() 方法 } public Executor getExecutor() { return this.rosExecutor; // 获取 ROS2 执行器对象 } protected Executor createExecutor() { return new SingleThreadedExecutor(); // 创建单线程的 ROS2 执行器对象 } protected long getDelay() { return SPINNER_DELAY; // 获取定时器的启动延迟时间 } protected long getPeriod() { return SPINNER_PERIOD_MS; // 获取定时器的周期时间 } }
package com.example.hyperbot; import android.os.Bundle; import android.os.Handler; import androidx.appcompat.app.AppCompatActivity; import org.ros2.rcljava.RCLJava; import org.ros2.rcljava.executors.Executor; import org.ros2.rcljava.executors.SingleThreadedExecutor; import java.util.Timer; import java.util.TimerTask; public class ROSActivity extends AppCompatActivity { private Executor rosExecutor; // ROS2 执行器对象,用于处理节点的消息 private Timer timer; // 定时器对象,定时执行节点的 spinSome() 方法 private Handler handler; // Android UI 线程的 Handler 对象,用于在 UI 线程上执行节点的 spinSome() 方法 private static String logtag = ROSActivity.class.getName(); private static long SPINNER_DELAY = 0; // 定时器的启动延迟时间(单位:毫秒) private static long SPINNER_PERIOD_MS = 200; // 定时器的周期时间(单位:毫秒) // 生命周期方法,当活动第一次创建时调用 @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.handler = new Handler(getMainLooper()); // 创建 Android UI 线程的 Handler 对象 RCLJava.rclJavaInit(); // 初始化 RCLJava 库 this.rosExecutor = this.createExecutor(); // 创建 ROS2 执行器对象 } // 生命周期方法,当活动从暂停状态恢复时调用 @Override protected void onResume() { super.onResume(); timer = new Timer(); // 创建定时器对象 timer.scheduleAtFixedRate(new TimerTask() { public void run() { Runnable runnable = new Runnable() { public void run() { rosExecutor.spinSome(); // 在 UI 线程上执行节点的 spinSome() 方法 } }; handler.post(runnable); // 将 Runnable 对象提交到 UI 线程的消息队列中, 避免在子线程中更新UI } }, this.getDelay(), this.getPeriod()); // 启动定时器,定时执行节点的 spinSome() 方法 } // 生命周期方法,当活动暂停时调用 @Override protected void onPause() { super.onPause(); if (timer != null) { timer.cancel(); // 取消定时器的执行 } } public void run() { rosExecutor.spinSome(); // 执行节点的 spinSome() 方法 } public Executor getExecutor() { return this.rosExecutor; // 获取 ROS2 执行器对象 } protected Executor createExecutor() { return new SingleThreadedExecutor(); // 创建单线程的 ROS2 执行器对象 } protected long getDelay() { return SPINNER_DELAY; // 获取定时器的启动延迟时间 } protected long getPeriod() { return SPINNER_PERIOD_MS; // 获取定时器的周期时间 } }
复制
package com.example.hyperbot; import android.os.Bundle; import android.os.Handler; import androidx.appcompat.app.AppCompatActivity; import org.ros2.rcljava.RCLJava; import org.ros2.rcljava.executors.Executor; import org.ros2.rcljava.executors.SingleThreadedExecutor; import java.util.Timer; import java.util.TimerTask; public class ROSActivity extends AppCompatActivity { private Executor rosExecutor; // ROS2 执行器对象,用于处理节点的消息 private Timer timer; // 定时器对象,定时执行节点的 spinSome() 方法 private Handler handler; // Android UI 线程的 Handler 对象,用于在 UI 线程上执行节点的 spinSome() 方法 private static String logtag = ROSActivity.class.getName(); private static long SPINNER_DELAY = 0; // 定时器的启动延迟时间(单位:毫秒) private static long SPINNER_PERIOD_MS = 200; // 定时器的周期时间(单位:毫秒) // 生命周期方法,当活动第一次创建时调用 @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.handler = new Handler(getMainLooper()); // 创建 Android UI 线程的 Handler 对象 RCLJava.rclJavaInit(); // 初始化 RCLJava 库 this.rosExecutor = this.createExecutor(); // 创建 ROS2 执行器对象 } // 生命周期方法,当活动从暂停状态恢复时调用 @Override protected void onResume() { super.onResume(); timer = new Timer(); // 创建定时器对象 timer.scheduleAtFixedRate(new TimerTask() { public void run() { Runnable runnable = new Runnable() { public void run() { rosExecutor.spinSome(); // 在 UI 线程上执行节点的 spinSome() 方法 } }; handler.post(runnable); // 将 Runnable 对象提交到 UI 线程的消息队列中, 避免在子线程中更新UI } }, this.getDelay(), this.getPeriod()); // 启动定时器,定时执行节点的 spinSome() 方法 } // 生命周期方法,当活动暂停时调用 @Override protected void onPause() { super.onPause(); if (timer != null) { timer.cancel(); // 取消定时器的执行 } } public void run() { rosExecutor.spinSome(); // 执行节点的 spinSome() 方法 } public Executor getExecutor() { return this.rosExecutor; // 获取 ROS2 执行器对象 } protected Executor createExecutor() { return new SingleThreadedExecutor(); // 创建单线程的 ROS2 执行器对象 } protected long getDelay() { return SPINNER_DELAY; // 获取定时器的启动延迟时间 } protected long getPeriod() { return SPINNER_PERIOD_MS; // 获取定时器的周期时间 } }
package com.example.hyperbot; import android.os.Bundle; import android.os.Handler; import androidx.appcompat.app.AppCompatActivity; import org.ros2.rcljava.RCLJava; import org.ros2.rcljava.executors.Executor; import org.ros2.rcljava.executors.SingleThreadedExecutor; import java.util.Timer; import java.util.TimerTask; public class ROSActivity extends AppCompatActivity { private Executor rosExecutor; // ROS2 执行器对象,用于处理节点的消息 private Timer timer; // 定时器对象,定时执行节点的 spinSome() 方法 private Handler handler; // Android UI 线程的 Handler 对象,用于在 UI 线程上执行节点的 spinSome() 方法 private static String logtag = ROSActivity.class.getName(); private static long SPINNER_DELAY = 0; // 定时器的启动延迟时间(单位:毫秒) private static long SPINNER_PERIOD_MS = 200; // 定时器的周期时间(单位:毫秒) // 生命周期方法,当活动第一次创建时调用 @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.handler = new Handler(getMainLooper()); // 创建 Android UI 线程的 Handler 对象 RCLJava.rclJavaInit(); // 初始化 RCLJava 库 this.rosExecutor = this.createExecutor(); // 创建 ROS2 执行器对象 } // 生命周期方法,当活动从暂停状态恢复时调用 @Override protected void onResume() { super.onResume(); timer = new Timer(); // 创建定时器对象 timer.scheduleAtFixedRate(new TimerTask() { public void run() { Runnable runnable = new Runnable() { public void run() { rosExecutor.spinSome(); // 在 UI 线程上执行节点的 spinSome() 方法 } }; handler.post(runnable); // 将 Runnable 对象提交到 UI 线程的消息队列中, 避免在子线程中更新UI } }, this.getDelay(), this.getPeriod()); // 启动定时器,定时执行节点的 spinSome() 方法 } // 生命周期方法,当活动暂停时调用 @Override protected void onPause() { super.onPause(); if (timer != null) { timer.cancel(); // 取消定时器的执行 } } public void run() { rosExecutor.spinSome(); // 执行节点的 spinSome() 方法 } public Executor getExecutor() { return this.rosExecutor; // 获取 ROS2 执行器对象 } protected Executor createExecutor() { return new SingleThreadedExecutor(); // 创建单线程的 ROS2 执行器对象 } protected long getDelay() { return SPINNER_DELAY; // 获取定时器的启动延迟时间 } protected long getPeriod() { return SPINNER_PERIOD_MS; // 获取定时器的周期时间 } }
复制
最后在MainActivity中创建新的发布和订阅者节点对象,并将其添加到执行器:
listenerNode = new ListenerNode("ros2_humble_node_listener", "/chatter", listenerView);
talkerNode = new TalkerNode("ros2_humble_node_talker", "/chatter");
getExecutor().addNode(listenerNode);
getExecutor().addNode(talkerNode );
listenerNode = new ListenerNode("ros2_humble_node_listener", "/chatter", listenerView); talkerNode = new TalkerNode("ros2_humble_node_talker", "/chatter"); getExecutor().addNode(listenerNode); getExecutor().addNode(talkerNode );
listenerNode = new ListenerNode("ros2_humble_node_listener", "/chatter", listenerView); talkerNode = new TalkerNode("ros2_humble_node_talker", "/chatter"); getExecutor().addNode(listenerNode); getExecutor().addNode(TalkerNode);
listenerNode = new ListenerNode("ros2_humble_node_listener", "/chatter", listenerView); talkerNode = new TalkerNode("ros2_humble_node_talker", "/chatter"); getExecutor().addNode(listenerNode); getExecutor().addNode(TalkerNode);
复制
listenerNode = new ListenerNode("ros2_humble_node_listener", "/chatter", listenerView); talkerNode = new TalkerNode("ros2_humble_node_talker", "/chatter"); getExecutor().addNode(listenerNode); getExecutor().addNode(TalkerNode);
listenerNode = new ListenerNode("ros2galacticnode_listener", "/chatter", listenerView); talkerNode = new TalkerNode("ros2galacticnode_talker", "/chatter"); getExecutor().addNode(listenerNode); getExecutor().addNode(TalkerNode);
复制
listenerNode = new ListenerNode("ros2galacticnode_listener", "/chatter", listenerView); talkerNode = new TalkerNode("ros2galacticnode_talker", "/chatter"); getExecutor().addNode(listenerNode); getExecutor().addNode(TalkerNode);
最后的运行效果如下:
将手机和电脑连接到同一网络下,可以在终端中看到手机端发布的话题及消息,如下图:
至此,关于ros2 humble的Android版本的编译以及测试到此结束;下一篇来聊聊如何开发APP来控制OriginBot的运动,敬请期待。
评论(23)
您还未登录,请登录后发表或查看评论