机器人操作系统ROS 编程开发--详细总结

本文涉及的产品
实时数仓Hologres,5000CU*H 100GB 3个月
智能开放搜索 OpenSearch行业算法版,1GB 20LCU 1个月
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
简介: 最近工作涉及到自动驾驶的,需要学习ROS,学习中总结了一些知识点,分享给大家。机器人操作系统ROS,是一种分布式处理框架(又名Nodes),ROS常用C++和python编程语言开发;(这里项目开发采用C++ 11版本)。ROS的点对点设计以及服务和节点管理器等机制,可以分散由计算机视觉和语音识别等功能带来的实时计算压力,能够适应多机器人遇到的挑战。ROS免费并且开源。

最近工作涉及到自动驾驶的,需要学习ROS,学习中总结了一些知识点,分享给大家。

ROS基本介绍

 机器人操作系统ROS,是一种分布式处理框架(又名Nodes),ROS常用C++和python编程语言开发;(这里项目开发采用C++ 11版本)。ROS的点对点设计以及服务和节点管理器等机制,可以分散由计算机视觉和语音识别等功能带来的实时计算压力,能够适应多机器人遇到的挑战。ROS免费并且开源。


ROS常用的概念

1node: 节点. 节点就是一些直行运算任务的进程。节点之间是通过传送消息进行通讯的;ROS中,通常来讲我们写的c++程序主函数所在的程序称为一个节点;

2message: 消息.机器人需要传感器,传感器采集到的信息,即这儿的message.(如:位置消息,温度、湿度等);消息以一种发布/订阅的方式传递;

3topic: 话题.node交换Messages的命名总线异步通讯机制,传输消息;

4package: 是组织ROS代码的最基本单位,每一个Package都可以包括库文件,可执行文件,脚本及其它的一些文件。

5workSpace: 工作空间 用来存放很多不同package的。

6roslaunch: 启动文件,其目的是一次性启动多个节点s。

7Master: 节点管理器,ROS名称服务,帮助节点找到彼此。

8publish : 发布器,把相关的信息发送到topic

9、subscribe: 订阅器,订阅相应的topic,接收话题的信息


ROS的结构是怎样的?

ROS分为两层,底层是操作系统层,上层则是广大用户编写提供的各种功能不同的软件包,比如定位导航,行动规划等等。

所以ROS实际上可以看成是一个中间层,提供和重新封装了底层硬件调用的API,这些重新封装的API称为客户端库,运用这些库可是实现硬件调用,以此实现各种不同的功能,如使用激光雷达扫描生成周围环境的2D地图……

   ROS框架基于集中式拓扑图结构,它的进程(即节点,ROS以节点形式进行通信,以此实现功能)是分布式的,进程分布在各个功能不同的功能包里面。


ROS发布消息-- publish

1、流程

  • 初始化 ROS 系统
  • 在 ROS 网络内广播中,我们将要在 chatter 话题上,发布 std_msgs/String 类型的消息
  • 以每秒 10 次的频率(可以设置修改)在 chatter 上发布消息

2、Ros发布信息 例子

/**
 *本程序演示了通过ROS系统简单发送消息。
*/
#include "ros/ros.h"                         //要使用ROS,得包含这个头文件
#include "std_msgs/String.h"                 //导入 String类型的头文件
#include <sstream>                           //c++自带的头文件 实现输入输出流等
int main(int argc, char **argv)
{
  ros::init(argc, argv, "talker");          //ros初始化,talker就是node(节点)的名字
  ros::NodeHandle n;                        //为这个进程的节点创建一个句柄。
  ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
                                           //定义要publish(发布)信息的对象
  ros::Rate loop_rate(10);                 //发布的信息的快慢 速度为10Hz
  int count = 0;
  while (ros::ok())
  {
    std_msgs::String msg;
    std::stringstream ss;
    ss << "hello world " << count;                
    msg.data = ss.str();
    ROS_INFO("%s", msg.data.c_str());    //可以理解为ROS里的printf()
    chatter_pub.publish(msg);            //用来发布信息(msg中的内容)
    ros::spinOnce();                     //这个函数是用于接收器,是检测一次
    loop_rate.sleep();
    ++count;
  }
  return 0;
}

image.gif

3、程序重点降解:

ros::NodeHandle n;

  • 第一个创建的 NodeHandle 会为节点进行初始化,最后一个销毁的 NodeHandle 则会释放该节点所占用的所有资源

ros:: Publisher chatter_pub = n.advertise < std_msgs::String>("chatter", 1000);

  • n.advertise 通过NodeHandle的对象n告诉ROS系统我要创建一个可以发布信息的对象
  • < std_msgs::String>告诉ROS我要发布的是标准信息中的String类型,chatter 通讯时的topic(主题)
  • 1000这个数字的意思是要缓冲的信息数量
  • advertise返回一个 ros::Publisher 对象,它有两个作用:

    1) 它有一个 publish() 成员函数可以让你在topic上发布消息;

     2) 如果消息类型不对,它会拒绝发布。

4、为什么需要缓冲呢?

这发布和接收之间并不是瞬间进行的,发布消息和接收到消息之间的时间差。缓冲区接收最新的信息放到信息序列的最后。即缓冲区的信息的数据结构是queue。第一条来的信息在序列满了的情况下会被第一个丢弃。

ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);

1000这个数字的意思是要缓冲的信息数量


ROS订阅消息subscribe

1、流程

  • 初始化ROS系统
  • 订阅 chatter 话题
  • 进入自循环,等待消息的到达
  • 当消息到达,调用 chatterCallback() 函数

Ros订阅信息 例子

/**
* 本程序演示了通过ROS系统简单接收消息。
*/
#include "ros/ros.h"
#include "std_msgs/String.h"
void chatterCallback(const std_msgs::String::ConstPtr& msg)
  //是一个回调函数,当接收到 chatter 话题的时候就会被调用。{
ROS_INFO("I heard: [%s]", msg->data.c_str());//msg->data就是一个std::string类型的量
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "listener");//节点的名字换成了listener
ros::NodeHandle n;
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
                                //定义接收器,topic话题:chatter,第三个参数chatterCallback称为回调函数
ros::spin();            //会使程序在循环中,一直检测有没有接收到新的消息
return 0;
}

image.gif

3、注意点

  1. 如果ROS遇到了相同的节点名字那么他会停止掉旧节点的名字然后使用新节点的那个程序(这时候旧的节点如果 有ros::ok(),那么他会就变得不OK了 = = 。这是ros::ok()返回false的第三种情况)
  2. 注意node的名字得独一无二,但是topic的名字得和你想接收的信息的topic一样!
  3. chatterCallback称为回调函数,接收器每一次接收到消息,就会调用名字为它的函数;一般命名为...Callback这样一看就知道这是ROS使用得回调函数
  4. std_msgs::String对象msg,类包含数据成员data,调用方式为msg.data。如果类的指针叫msg,那么调用该成员的方式是msg->data


ROS 编译程序

1、流程

  • 编辑CMakeLists文件(指定要编译的文件)
  • 再使用catkin_make编译

2、编译例子:

来到编写好程序的包目录中

cd ~/catkin_ws/src/pub_sub_test/

image.gif

编辑CMakeLists文件

gedit CMakeLists.txt

image.gif

在后面添加如下内容:

add_executable(pub_string src/pub_string.cpp)
target_link_libraries(pub_string ${catkin_LIBRARIES})

image.gif

第一行表示我们要编译add_executable表示我们要添加一个可执行文件,

pub_string是这个可执行文件的名字

src/pub_string.cpp指定要编译的源文件的位置.

第二行target_link_libraries表示我们要将可执行文件链接到一个库,我们要使用ROS当然是要链接到ROS的库了,

pub_string指定要链接可执行文件的名字,后面是指定要链接的库的名字.

来到工作空间中,执行catkin_make命令

cd ~/catkin_ws/
catkin_make

image.gif


执行ROS程序方法1:

方法1:

打开第一个terminal,执行roscore命令(打开rosmaster 服务器)

roscore

image.gif

roscore是为了让各种节点之间能够沟通用的

打开第二个terminal,进入工作空间,执行source devel/setup.bash 命令,

cd ~/catkin_ws/
source devel/setup.bash

image.gif

使用rosrun 命令运行程序(例如:执行pub_string.cpp):

rosrun pub_sub_test pub_string

image.gif

第一个参数:程序pub_string.cpp所在的包(package)的名字

第二个参数:运行程序的名称


方法2: 使用roslaunch来运行程序

特点:

可以便捷开启多个节点,自动开启rosmaster服务

区别:

    • 方法一在使用rosrun之前,我们一定得需要启动rosmaster,即开启一个窗口输入roscore,来开启rosmaster 服务器;
    • 运行roslaunch文件后rosmaster会自动启动.当然你关闭了roslaucn之后rosmaster也会关闭

    流程:

      1. 来到程序所在包的文件夹目录下,新建一个名字叫launch 的文件夹
      2. 在launch中建立的一个文件,名字可以随意,后缀必须是launch
      3. 编译程序
      4. 编辑launch文件
      5. 通过roslaunch,执行节点程序

      详细说明:

        1. 编辑launch文件

        格式: <

        launch>
            <node name="pub_string" pkg = "pub_sub_test" type = "pub_string" output = "screen">
            </node>
            <node name="pub_int8" pkg = "pub_sub_test" type = "pub_int8" output = "screen">
            </node>
        </launch>

        image.gif

        roslaunch使用的是xml语言,launch文件的内容是跑一个node简单的形式

        <launch>,<\launch>表示launch文件的开始和结束

        <node ....>表示接下来输入node相关的内容,比如说首先输入的是node的名字,这个东西一般和type后面输入的内容一样,

        type需要被赋值为节点对应的可执行文件的名字,

        name则是节点的名字.具体区别是你在CMakeLists.txt文件里编译文件的命令

        output = "screen"  设置通过print()输出的信息,打印到命令窗口中,默认时关闭的

        通过roslaunch,执行节点程序

        格式:

        关键字:roslaunch

        第一个参数: xx.launch 所在的包的名称

        第二个参数: 要执行的.launch 的文件



        ROS发布和接收图

        image.gif编辑

        rosbag

        简介

          1. rosbag 指命令行中数据包相关命令;
          2. rosbag 主要用于记录、回放、分析 rostopic 中的数据。它可以将指定 rostopic 中的数据记录到 .bag 后缀的数据包中,便于对其中的数据进行离线分析和处理。
          3. 对于 subscribe 某个 topic 的节点来说,它无法区分这个 topic 中的数据到底是实时获取的数据还是从 rosbag 中回放的数据。这就有助于我们基于离线数据快速重现曾经的实际场景,进行可重复、低成本的分析和调试。

          rosbag 的命令  

          image.gif编辑


          rosbag录制:

          录制所有发布出来的话题,此时默认将话题保存在一个以当时时间戳命名的文件夹中:

          $ rosbag record –a

          录制指定话题:

          $ rosbag record /topic1 /topic12


          rosbag回放:

          基本功能:

          $ rosbag play <your bagfile name>

          等待一定时间之后发布bag文件中的内容

          $ rosbag play <your bagfile name> -d <delay time>

          按一定频率回放,-r选项用来设定消息发布速率,如下面命令则表示以3倍原始速率发布话题

          $ rosbag play -r 3 <your bagfile name>

          回放指定话题:

          $ rosbag play <your bagfile name> --topics <topics>


          rosbag检查和回放

          1. rosbag info指令可以显示数据包中的信息:

          rosbag  info filename.bag

          这些信息包括 topic 的名称、类型和 message 数量。

          2) 接下来回放数据包中的 topic。

          首先在turtle_teleop_key 所在的终端窗口中按Ctrl+C退出该键盘控制节点。保留turtlesim节点继续运行。在终端中bag文件所在目录下运行以下命令:

          rosbag play <bagfile>

          就能够回放出 bag 中包含的 topic 内容了。


          如果想改变消息的发布速率,可以用下面的命令

          rosbag play -r 2 <bagfile>

          这时的轨迹相当于以两倍的速度通过按键发布控制命令时产生的轨迹。 -r 后面的数字对应播放速率。


          如果希望 rosbag 循环播放,可以用命令

          rosbag play -l  <bagfile>  # -l == --loop


          如果只播放感兴趣的 topic ,则用命令

          rosbag play <bagfile> --topic /topic1

          在上述播放命令执行期间,空格键可以暂停播放。


          CMakeLists

          简介:

          ROS中创建软件包所依赖的文件CMakeList.txtcatkin_make会根据你写的CMakeList.txt来配置编译软件包。

          格式:

          所需CMake版本                  cmake_minimum_required(VERSION 2.8.3)

          软件包名称                         project()

          查找构建此包所需的包      find_package()

          消息 / 服务 / 动作生成器     add_message_files(),add_service_files(),add_action_files

          消息 / 服务 / 动作生成        generate_messages()

          指定包构建的消息导出        catkin_package()

          要建立的库 / 可执行文件   add_library() / add_executable() / target_link_libraries())

          例如:

          image.gif编辑

          其中:add_executable(read_param src/show_param.cpp)和target_link_libraries(read_param ${catkin_LIBRARIES})  是新添加的,指定可执行文件

          详细解释:

          1.所需CMake版本

          catkin_make的底层是使用cmake进行编译的,这里指定cmake的版本(最低版本)

          2.软件包名称

          project(package_name)

          在使用catkin_create_pkg创建包时,后面跟的参数(包名)就是此处的package_name

          在CMakeList.txt后面的部分可以使用 ${PROJECT_NAME} 来使用此参数

          3. 查找此包创建时所需要的其它包(依赖包)

          可以将所依赖的包写成下面的形式:

          find_package(catkin REQUIRED)

          find_package(roscpp REQUIRED)

          find_package(rospy REQUIRED)

          find_package(std_msgs REQUIRED)

          其中catkin是创建每个包所必须的依赖项,创建包时所依赖的其它项又可以将其组成catkin的组件,所以上面可以总写为:

          find_package(catkin REQUIRED COMPONENTS

           roscpp

           rospy

           std_msgs

          )

          上面中的roscpprospystd_msgs 是运行程序所需要的依赖包

          roscpp: 用C++ 语言进行ros开发要用到的包

          rospy: 用python 语言进行ros开发要用到的包

          std_msgs: 基本的数据类型int stringfloat、double等的依赖包

          4.add_message_files()

          像message service 和action的定义需要在catkin_package()之前

          ## Generate messages in the 'msg' folder

          # add_message_files(

          #   FILES

          #   Message1.msg

          #   Message2.msg

          # )

          比如:

          add_message_files(

           FILES Num1.msg Num2.msg

          )

          5.catkin_package()

          用来向编译系统指明catkin-specific的信息,格式如下:

          catkin_package(

            INCLUDE_DIRS include  # 此项打开之后该软件包的include文件可以被其它包所引用

            LIBRARIES ${PROJECT_NAME} #同理

            CATKIN_DEPENDS roscpp nodelet

            DEPENDS eigen opencv)

          6.包含文件目录

          include_directories(include ${catkin_INCLUDE_DIRS})  

          其中include是指包含本软件包下的头文件, ${catkin_INCLUDE_DIRS}是指ROS下其它包的头文件,include需要写在${catkin_INCLUDE_DIRS}前面。

          7.生成可执行文件

          #其中talker为将要生成的二进制文件,在ros所有的包中必须是独一无二不能重复的,src/talker.cpp为需要编译的源文件

          add_executable(talker src/talker.cpp)  

          target_link_libraries(talker ${catkin_LIBRARIES})

          生成的可执行文件会存放在./devel/lib/*pack_name*/下:


          问题与解决方案总结

          缺失依赖库

          image.gif编辑

          解决思路:

          1.在错误原因中,找到错误代码端,分析,确定缺失的包

          2.在程序所在包下,添加依赖包

          添加依赖包,详细说明:

          思路:

          1. 一个是包目录下的的CMakeLists.txt,添加依赖包
          2. 然后在位于同一位置的package.xml中,添加添加依赖包

          例如

          1. 打开的CMakeLists.txt,发现就在最前面几行,有下面的内容。
          find_package(catkin REQUIRED COMPONENTS
            roscpp
            rospy
            std_msgs
          )

          image.gif

          括号中的内容正好一一对应我们创建包时添加的依赖项,在后面添加geometry_msgs,变成下面的样子,保存退出。

          find_package(catkin REQUIRED COMPONENTS
            roscpp
            rospy
            std_msgs
            geometry_msgs
          )

          image.gif

           

          打开位于同一目录下的package.xml

          <build_depend>roscpp</build_depend>
           <build_depend>rospy</build_depend>
            <build_depend>std_msgs</build_depend>
            <build_export_depend>roscpp</build_export_depend>
            <build_export_depend>rospy</build_export_depend>
            <build_export_depend>std_msgs</build_export_depend>
            <exec_depend>roscpp</exec_depend>
            <exec_depend>rospy</exec_depend>
            <exec_depend>std_msgs</exec_depend>

          image.gif

          发现std_msgs, rospy, roscpp,每个出现了三次,所以只我们需要按照这个文档里相同的语法让geometry_msgs出现三次就行了。更改之后该文件同样位置变成下面的内容:

          <build_depend>roscpp</build_depend>
            <build_depend>rospy</build_depend>
            <build_depend>std_msgs</build_depend>
            <build_depend>geometry_msgs</build_depend>
            <build_export_depend>roscpp</build_export_depend>
            <build_export_depend>rospy</build_export_depend>
            <build_export_depend>std_msgs</build_export_depend>
            <build_export_depend>geometry_msgs</build_export_depend>
            <exec_depend>roscpp</exec_depend>
            <exec_depend>rospy</exec_depend>
            <exec_depend>std_msgs</exec_depend>
            <exec_depend>geometry_msgs</exec_depend>
            <exec_depend>geometry_msgs</exec_depend>

          image.gif

          保存退出。这时候再用catkin_make编译,就成功了。改变上面两个文档的内容就相当于我们在创建包时添加了依赖项geometry_msgs。


          catkin_make编译错误

          问题:

          Could not find a package configuration file provided by

           "gazebo_ros_control" with any of the following names:

             gazebo_ros_controlConfig.cmake

          gazebo_ros_control-config.cmake

          分析

          提示缺少“gazebo_ros_control”功能包,

          解决方案:

          sudo apt-get install ros-kinetic-gazebo-ros-control

            问题:

            Could not find a package configuration file provided by "move_base_msgs"

             with any of the following names:

               move_base_msgsConfig.cmake

            move_base_msgs-config.cmake

            分析

            提示缺少“gazebo_ros_control”功能包

            解决方案:

            sudo apt-get install ros-kinetic-move-base-msgs

              问题:

              alsa/asoundlib.h: No such file or directory

              分析

              缺少一个库,libasound2-dev

              解决方案:

              sudo apt-get install libasound2-dev

              希望对你有帮助。

              相关实践学习
              使用ROS创建VPC和VSwitch
              本场景主要介绍如何利用阿里云资源编排服务,定义资源编排模板,实现自动化创建阿里云专有网络和交换机。
              阿里云资源编排ROS使用教程
              资源编排(Resource Orchestration)是一种简单易用的云计算资源管理和自动化运维服务。用户通过模板描述多个云计算资源的依赖关系、配置等,并自动完成所有资源的创建和配置,以达到自动化部署、运维等目的。编排模板同时也是一种标准化的资源和应用交付方式,并且可以随时编辑修改,使基础设施即代码(Infrastructure as Code)成为可能。 产品详情:https://www.aliyun.com/product/ros/
              相关文章
              |
              1月前
              |
              安全 搜索推荐 Android开发
              移动应用与系统:探索开发趋势与操作系统优化策略####
              当今数字化时代,移动应用已成为日常生活不可或缺的一部分,而移动操作系统则是支撑这些应用运行的基石。本文旨在探讨当前移动应用开发的最新趋势,分析主流移动操作系统的特点及优化策略,为开发者提供有价值的参考。通过深入剖析技术创新、市场动态与用户需求变化,本文力求揭示移动应用与系统协同发展的内在逻辑,助力行业持续进步。 ####
              44 9
              |
              16天前
              |
              安全 前端开发 Android开发
              探索移动应用与系统:从开发到操作系统的深度解析
              在数字化时代的浪潮中,移动应用和操作系统成为了我们日常生活的重要组成部分。本文将深入探讨移动应用的开发流程、关键技术和最佳实践,同时分析移动操作系统的核心功能、架构和安全性。通过实际案例和代码示例,我们将揭示如何构建高效、安全且用户友好的移动应用,并理解不同操作系统之间的差异及其对应用开发的影响。无论你是开发者还是对移动技术感兴趣的读者,这篇文章都将为你提供宝贵的见解和知识。
              |
              24天前
              |
              自动驾驶 安全 机器人
              ROS2:从初识到深入,探索机器人操作系统的进化之路
              前言 最近开始接触到基于DDS的这个系统,是在稚晖君的机器人项目中了解和认识到。于是便开始自己买书学习起来,感觉挺有意思的,但是只是单纯的看书籍,总会显得枯燥无味,于是自己又开始在网上找了一些视频教程结合书籍一起来看,便让我对ROS系统有了更深的认识和理解。 ROS的发展历程 ROS诞生于2007年的斯坦福大学,这是早期PR2机器人的原型,这个项目很快被一家商业公司Willow Garage看中,类似现在的风险投资一样,他们投了一大笔钱给这群年轻人,PR2机器人在资本的助推下成功诞生。 2010年,随着PR2机器人的发布,其中的软件正式确定了名称,就叫做机器人操作系统,Robot Op
              63 14
              |
              16天前
              |
              人工智能 搜索推荐 Android开发
              移动应用与系统:探索开发趋势与操作系统演进####
              本文深入剖析了移动应用开发的最新趋势与移动操作系统的演进历程,揭示了技术创新如何不断推动移动互联网生态的变革。通过对比分析不同操作系统的特性及其对应用开发的影响,本文旨在为开发者提供洞察未来技术方向的视角,同时探讨在多样化操作系统环境中实现高效开发的策略。 ####
              18 0
              |
              1月前
              |
              人工智能 Android开发 数据安全/隐私保护
              移动应用与系统:探索开发趋势与操作系统的协同进化####
              当今时代,移动应用不再仅仅是简单的软件工具,它们已成为扩展智能手机及平板等设备功能的关键。本文旨在深入分析当前移动应用的开发趋势,探讨移动操作系统的最新进展及其对应用开发的影响,并阐述两者如何相互促进、协同进化,共同推动移动互联网技术向前发展。 ####
              |
              1月前
              |
              人工智能 物联网 Android开发
              移动应用与系统:探索开发趋势与操作系统的协同进化####
              本文深入探讨了移动应用开发的当前趋势,以及这些趋势如何与移动操作系统的发展相互影响、协同进化。通过分析最新的技术动态、市场数据及用户行为变化,本文旨在为开发者提供关于未来移动应用开发方向的洞察,并讨论操作系统层面的创新如何促进或制约应用的发展。 ####
              |
              15天前
              |
              编解码 网络协议 机器人
              顶顶通电话机器人开发接口对接大语言模型之实时流TTS对接介绍
              大语言模型通常流式返回文字,若一次性TTS会导致严重延迟。通过标点断句或流TTS可实现低延迟的文本到语音转换。本文介绍了电话机器人接口适配流TTS的原理及技术点,包括FreeSWITCH通过WebSocket流TTS放音,以及推流协议和旁路流对接的详细说明。
              |
              15天前
              |
              5G 数据安全/隐私保护 Android开发
              移动应用与系统:探索开发趋势与操作系统革新####
              本文深入剖析当前移动应用开发的最新趋势,涵盖跨平台开发框架的兴起、人工智能技术的融合、5G技术对移动应用的影响,以及即时应用的发展现状。随后,文章将探讨主流移动操作系统的最新特性及其对开发者社区的影响,包括Android的持续进化、iOS的创新举措及华为鸿蒙OS的崛起。最后,还将讨论移动应用开发中面临的挑战与未来的发展机遇,为读者提供全面而深入的行业洞察。 ####
              |
              1月前
              |
              自动驾驶 安全 机器人
              ROS2:从初识到深入,探索机器人操作系统的进化之路
              【11月更文挑战第4天】ROS2的学习过程和应用,介绍DDS系统的框架和知识。
              |
              22天前
              |
              人工智能 5G 开发工具
              移动应用与系统的未来趋势:开发、操作系统创新及挑战###
              本文探讨了移动应用开发和移动操作系统的最新发展趋势,包括人工智能的集成、跨平台开发工具的兴起以及5G技术对移动生态的影响。同时,还分析了开发者面临的主要挑战,如安全性问题、性能优化和用户体验提升等。通过具体案例和技术解析,本文旨在为开发者提供前瞻性指导,帮助他们在快速变化的移动科技领域保持竞争力。 ###

              推荐镜像

              更多
              下一篇
              DataWorks