vsomeip源码梳理 -- application初始化

本文基于vsomeip 3.1.20.3总结而成
源码地址:https://github.com/GENIVI/vsomeip.git

application的创建

在之前的文章中有写过双机通讯的Demo来了解使用API,这篇文章就从API的入口开始,来分析一下整个协议栈的源码实现,application共享指针是通过Runtime来创建的

using namespace vsomeip;
std::shared_ptr<runtime> rtm_ = runtime::get();
std::shared_ptr<application> app_= rtm_->create_application("name")

对应的源码如下:

std::shared_ptr<application> runtime_impl::create_application(
        const std::string &_name) {
    static std::uint32_t postfix_id = 0;
    std::lock_guard<std::mutex> its_lock(applications_mutex_);
    std::string its_name_ = _name;
    //从当前runtime的环境中查找是否存在相同名称的application引用,
    //如果已经存在相同名称的app,则将当前的name加上后缀之后重命名
    //然后创建一个application的共享指针,并保存到运行环境的缓存map中
    auto found_application = applications_.find(_name);
    if( found_application != applications_.end()) {
        its_name_ += "_" + std::to_string(postfix_id++);
    }
    std::shared_ptr<application> application = std::make_shared<application_impl>(its_name_);
    applications_[its_name_] = application;
    return application;
}

从上面代码中我们可以看到,创建app的流程比较简单,就是实例化一个application类,并创建其共享指针,然后按照名称将对象的弱指针保存到运行时环境的map缓存中。

application.hpp的实现在vsomeip/implementation/runtime/application_impl.cpp中,类中定义了众多的属性跟方法,以及回调函数,这里我们只关心在初始化过程中的相关属性值

application_impl::application_impl(const std::string &_name)
        : runtime_(runtime::get()),
          client_(VSOMEIP_CLIENT_UNSET), //默认client id = 0xFFFF
          session_(0), //初始化session id = 0
          is_initialized_(false), 
          name_(_name), //应用名称
          work_(std::make_shared<boost::asio::io_service::work>(io_)),
          //route_manager指针, 有两种角色,一个是host,一个是proxy,实现不一样
          routing_(0),
          //初始化app状态为DEREGISTERRED状态
          state_(state_type_e::ST_DEREGISTERED),
          //安全模式
          security_mode_(security_mode_e::SM_OFF),
#ifdef VSOMEIP_ENABLE_SIGNAL_HANDLING
          signals_(io_, SIGINT, SIGTERM),
          catched_signal_(false),
#endif
          is_dispatching_(false),
          max_dispatchers_(VSOMEIP_MAX_DISPATCHERS),
          max_dispatch_time_(VSOMEIP_MAX_DISPATCH_TIME),
          stopped_(false),
          block_stopping_(false),
          is_routing_manager_host_(false),
          stopped_called_(false),
          watchdog_timer_(io_),
          client_side_logging_(false)

至此,application的创建流程完毕,主要是调用runtime的api,创建application对象,然后初始化application中的一些属性值。

init流程

顺着demo中的api调用往下走的话,就来到了application的init流程,这个流程中又包含了若干个子模块的初始化流程,比较复杂,按照阶段,将其归类为如下几个阶段:

  • 配置模块初始化
  • 路由模块初始化

流程图如下所示
在这里插入图片描述
下面开始手撕代码, 内容比较多,省略了中间client side logging的处理逻辑:

bool application_impl::init() {
    //首先如果应用已经被初始化了,则直接不处理返回初始化成功
    if(is_initialized_) {
        VSOMEIP_WARNING << "Trying to initialize an already initialized application.";
        return true;
    }
    //如果应用程序的名称为空,那么此处就从环境变量中取application名称
    // Application name
    if (name_ == "") {
        const char *its_name = getenv(VSOMEIP_ENV_APPLICATION_NAME);
        if (nullptr != its_name) {
            name_ = its_name;
        }
    }

    std::string configuration_path;

    // load configuration from module
    std::string config_module = "";
    const char *its_config_module = getenv(VSOMEIP_ENV_CONFIGURATION_MODULE);
    if (nullptr != its_config_module) {
        // TODO: Add loading of custom configuration module
    } else { // load default module
#ifndef VSOMEIP_ENABLE_MULTIPLE_ROUTING_MANAGERS
        //如果协议栈不支持多路由管理,则配置信息模块的创建以及初始化走这里
        //从插件管理器中查找配置信息模块。这个在下文中单独列出
        auto its_plugin = plugin_manager::get()->get_plugin(
                plugin_type_e::CONFIGURATION_PLUGIN, VSOMEIP_CFG_LIBRARY);
        if (its_plugin) {
            auto its_configuration_plugin
                = std::dynamic_pointer_cast<configuration_plugin>(its_plugin);
            if (its_configuration_plugin) {
             //通过配置信息插件获取应用程序对应的配置信息
                configuration_ = its_configuration_plugin->get_configuration(name_);
                VSOMEIP_INFO << "Configuration module loaded.";
            } else {
                std::cerr << "Invalid configuration module!" << std::endl;
                std::exit(EXIT_FAILURE);
            }
        } else {
            std::cerr << "Configuration module could not be loaded!" << std::endl;
            std::exit(EXIT_FAILURE);
        }
#else
        //如果协议栈定义了多路由管理,则配置信息模块的初始化走这里
        configuration_ = std::dynamic_pointer_cast<configuration>(
                std::make_shared<vsomeip_v3::cfg::configuration_impl>());
        if (configuration_path.length()) {
            configuration_->set_configuration_path(configuration_path);
        }
        configuration_->load(name_);
#endif // VSOMEIP_ENABLE_MULTIPLE_ROUTING_MANAGERS
    }

    //这里是初始化安全模式,根据配置信息文件中的定义来取值
    // Set security mode
    auto its_security = security::get();
    if (its_security->is_enabled()) {
        if (its_security->is_audit()) {
            security_mode_ = security_mode_e::SM_AUDIT;
        } else {
            security_mode_ = security_mode_e::SM_ON;
        }
    } else {
        security_mode_ = security_mode_e::SM_OFF;
    }
    ...
	...
	...

    std::shared_ptr<configuration> its_configuration = get_configuration();
    if (its_configuration) {
        VSOMEIP_INFO << "Initializing vsomeip application \"" << name_ << "\".";
        client_ = its_configuration->get_id(name_);

        // Max dispatchers is the configured maximum number of dispatchers and
        // the main dispatcher
        max_dispatchers_ = its_configuration->get_max_dispatchers(name_) + 1;
        max_dispatch_time_ = its_configuration->get_max_dispatch_time(name_);

#ifdef VSOMEIP_HAS_SESSION_HANDLING_CONFIG
        has_session_handling_ = its_configuration->has_session_handling(name_);
        if (!has_session_handling_)
            VSOMEIP_INFO << "application: " << name_
                << " has session handling switched off!";
#endif // VSOMEIP_HAS_SESSION_HANDLING_CONFIG

        //初始化路由模块
        //首先从当前应用程序的配置信息中获取路由管理的名称
        std::string its_routing_host = its_configuration->get_routing_host();
        if (its_routing_host != "") {
            //如果配置的路由管理名称与应用程序名称一致,则表示当前的应用程序也是路由管理者
            //(host模式,反之为proxy模式)
            is_routing_manager_host_ = (its_routing_host == name_);
            if (is_routing_manager_host_ &&
                    !utility::is_routing_manager(configuration_)) {
#ifndef VSOMEIP_ENABLE_MULTIPLE_ROUTING_MANAGERS
                VSOMEIP_ERROR << "application: " << name_ << " configured as "
                        "routing but other routing manager present. Won't "
                        "instantiate routing";
                is_routing_manager_host_ = false;
                return false;
#else
            is_routing_manager_host_ = true;
#endif // VSOMEIP_ENABLE_MULTIPLE_ROUTING_MANAGERS
            }
        } else {
            //如果配置信息中没有找到相关路由管理名称,则根据环境变量中定义的基础路径中的文件锁去
            //判断当前应用程序是否为路由,能操作文件锁,则为路由。
            is_routing_manager_host_ = utility::is_routing_manager(configuration_);
        }
		
        if (is_routing_manager_host_) {
            //创建host类型的路由
            VSOMEIP_INFO << "Instantiating routing manager [Host].";
            if (client_ == VSOMEIP_CLIENT_UNSET) {
                client_ = static_cast<client_t>(
                          (configuration_->get_diagnosis_address() << 8)
                        & configuration_->get_diagnosis_mask());
                utility::request_client_id(configuration_, name_, client_);
            }
            routing_ = std::make_shared<routing_manager_impl>(this);
        } else {
            //创建proxy类型的路由代理
            VSOMEIP_INFO << "Instantiating routing manager [Proxy].";
            routing_ = std::make_shared<routing_manager_proxy>(this, client_side_logging_, client_side_logging_filter_);
        }
		//设置当前的client id
        routing_->set_client(client_);
        //路由模块初始化
        routing_->init();

#ifdef USE_DLT
        // Tracing
        std::shared_ptr<trace::connector_impl> its_connector
            = trace::connector_impl::get();
        std::shared_ptr<cfg::trace> its_trace_configuration
            = its_configuration->get_trace();
        its_connector->configure(its_trace_configuration);
#endif

        VSOMEIP_INFO << "Application(" << (name_ != "" ? name_ : "unnamed")
                << ", " << std::hex << std::setw(4) << std::setfill('0') << client_
                << ") is initialized ("
                << std::dec << max_dispatchers_ << ", "
                << std::dec << max_dispatch_time_ << ").";

        is_initialized_ = true;
    }

#ifdef VSOMEIP_ENABLE_SIGNAL_HANDLING
    //这里如果协议栈编译支持系统信号处理,则添加信号监听器,用于应用停止。
    if (is_initialized_) {
        signals_.add(SIGINT);
        signals_.add(SIGTERM);

        // Register signal handler
        auto its_signal_handler =
                [this] (boost::system::error_code const &_error, int _signal) {
                    if (!_error) {
                        switch (_signal) {
                            case SIGTERM:
                            case SIGINT:
                                catched_signal_ = true;
                                stop();
                                break;
                            default:
                                break;
                        }
                    }
                };
        signals_.async_wait(its_signal_handler);
    }
#endif

   //通知当前应用状态变更事件
    if (configuration_) {
        auto its_plugins = configuration_->get_plugins(name_);
        auto its_app_plugin_info = its_plugins.find(plugin_type_e::APPLICATION_PLUGIN);
        if (its_app_plugin_info != its_plugins.end()) {
            for (auto its_library : its_app_plugin_info->second) {
                auto its_application_plugin = plugin_manager::get()->get_plugin(
                        plugin_type_e::APPLICATION_PLUGIN, its_library);
                if (its_application_plugin) {
                    VSOMEIP_INFO << "Client 0x" << std::hex << get_client()
                            << " Loading plug-in library: " << its_library << " succeeded!";
                    std::dynamic_pointer_cast<application_plugin>(its_application_plugin)->
                            on_application_state_change(name_, application_plugin_state_e::STATE_INITIALIZED);
                }
            }
        }
    } else {
        std::cerr << "Configuration module could not be loaded!" << std::endl;
        std::exit(EXIT_FAILURE);
    }
    //返回初始化结果
    return is_initialized_;
}

上述对application的init函数中的功能做了一个整体流程梳理,流程中还包含了两个比较大的模块的初始化,这里我们来接着跟。

配置信息模块初始化

首先是配置信息模块的初始化流程,在之前的demo中我们有设置VSOMEIP_CONFIGURATION这个环境变量来指定xxx.json这个文件的路径,这个文件就是协议栈运行所配置的一些信息,在协议栈的初始化过程中,会解析这个json文件,然后将其中的配置字存放在内存中供其他模块使用查询。那么现在我们就来看看这个配置信息模块的一个初始化流程吧。

配置信息加载的流程实现,在vsomeip/implementation/configuration/src/configuration_impl.cpp这个文件中实现,configuration_impl这个类的实例由插件管理器通过dlopen加载libvsomeip3-cfg.so库来完成,插件管理器的实现这里先不分析,我们只需知道通过配置插件模块中的get_configuration函数得到了一个类型为configuration_impl的共享指针即可。

//传入的参数就是application的名称
std::shared_ptr<configuration>
configuration_plugin_impl::get_configuration(const std::string &_name) {
    std::lock_guard<std::mutex> its_lock(mutex_);
    if (!default_) {
        default_ = std::make_shared<cfg::configuration_impl>();
        default_->load(_name);
    }

接下来就到了configuration_impl中的load方法中,这个函数里面做了加载解析的主要逻辑,主要工作如下:

  • 定位配置文件或者文件夹的路径

代码逻辑如下:

// Predefine file / folder
/*1. 首先初始化文件或者文件夹的路径为宏定义中的默认路径,VSOMEIP_DEFAULT_CONFIGURATION_FILE宏定义了
默认配置文件的路径,VSOMEIP_DEFAULT_CONFIGURATION_FOLDER定义了默认配置文件夹的路径(这个默认的
是最低优先级,如果后续两个配置存在,则默认的不起作用)*/
    std::string its_file(VSOMEIP_DEFAULT_CONFIGURATION_FILE); // configuration file
    std::string its_folder(VSOMEIP_DEFAULT_CONFIGURATION_FOLDER); // configuration folder

 //再去获取可执行程序所在路径中是否存在配置文件与文件夹,如果存在,则覆盖默认的配置
    // Override with local file / folder (if existing)
    std::string its_local_file(VSOMEIP_LOCAL_CONFIGURATION_FILE);
    if (utility::is_file(its_local_file)) {
        its_file = its_local_file;
    }

    std::string its_local_folder(VSOMEIP_LOCAL_CONFIGURATION_FOLDER);
    if (utility::is_folder(its_local_folder)) {
        its_folder = its_local_folder;
    }

//再从进程的环境变量中查找名为VSOMEIP_CONFIGURATION的环境变量,如果环境变量中保存的值是一个文件
//   路径,则使用该配置文件,如果是文件夹,则从该文件夹中查找配置
    // Override with path from environment (if existing)
    const char *its_env = getenv(VSOMEIP_ENV_CONFIGURATION);
    if (nullptr != its_env) {
        if (utility::is_file(its_env)) {
            its_file = its_env;
            its_folder = "";
        } else if (utility::is_folder(its_env)) {
            its_folder = its_env;
            its_file = "";
        }
    }

拿到路径后,基本上就是获取json文件,解析json文件,逻辑依次如下:

load_logging(e, its_warnings) 
load_unicast_address(e);
load_netmask(e);
load_device(e);
load_service_discovery(e);
load_npdu_default_timings(e);
load_services(e);
load_internal_services(e);
load_clients(e);
load_watchdog(e);
load_selective_broadcasts_support(e);
load_e2e(e);
load_debounce(e);
load_acceptances(e);
load_secure_services(e);

解析完成后,所有的值都会被保存在内存中,后续供给其他模块使用。

路由模块初始化

配置信息解析完成后,在主流程的代码里面看到有对route_manager接口的初始化,路由模块的实现在源码的vsomeip/implementation/routing中,路由模块会根据角色不同分为两种,分别是host与proxy, 他们其中的关系如下图:
在这里插入图片描述
上面简单的画了一个图梳理了一下路由中各个接口的对应关系,这个在后面梳理消息传递的逻辑中对照着看会比较轻松。这里没有画类图,接口中的方法实在是太多了,大家将就着看。

Host端路由模块初始化

Host模式的路由管理器实现在routing_manager_impl.cpp中, 构造函数如下:

routing_manager_impl::routing_manager_impl(routing_manager_host *_host) :
        routing_manager_base(_host),
        version_log_timer_(_host->get_io()),
        if_state_running_(false),
        sd_route_set_(false),
        routing_running_(false),
        status_log_timer_(_host->get_io()),
        memory_log_timer_(_host->get_io()),
        //创建了一个通信端点管理器
        ep_mgr_impl_(std::make_shared<endpoint_manager_impl>(this, io_, configuration_)),
        pending_remote_offer_id_(0),
        last_resume_(std::chrono::steady_clock::now().min()),
        statistics_log_timer_(_host->get_io()),
        ignored_statistics_counter_(0)
{
}

routing_manager_base::routing_manager_base(routing_manager_host *_host) :
 //这个host_的指针指向application_impl
        host_(_host),
        io_(host_->get_io()),
        client_(host_->get_client()),
        configuration_(host_->get_configuration())
#ifdef USE_DLT
        , tc_(trace::connector_impl::get())
#endif
{
 ...
 ...
}

然后再看init方法

void routing_manager_impl::init() {
    //将通信端点管理器赋给父类中的ep_mgr_指针
    routing_manager_base::init(ep_mgr_impl_);

    //创建stub端
    // TODO: Only instantiate the stub if needed
    stub_ = std::make_shared<routing_manager_stub>(this, configuration_);
    //stub端执行init
    stub_->init();
	/*
	    stub的init代码就是如下一行,创建一个用于本地域通讯的服务端
	    endpoint_ = host_->get_endpoint_manager()->create_local_server(
            &is_socket_activated_, shared_from_this());
	*/

    //加载SD模块,SD模块的init流程又是一大块,放在文后分析
    if (configuration_->is_sd_enabled()) {
        VSOMEIP_INFO<< "Service Discovery enabled. Trying to load module.";
        auto its_plugin = plugin_manager::get()->get_plugin(
                plugin_type_e::SD_RUNTIME_PLUGIN, VSOMEIP_SD_LIBRARY);
        if (its_plugin) {
            VSOMEIP_INFO << "Service Discovery module loaded.";
            discovery_ = std::dynamic_pointer_cast<sd::runtime>(its_plugin)->create_service_discovery(this, configuration_);
            discovery_->init();
        } else {
            VSOMEIP_ERROR << "Service Discovery module could not be loaded!";
            std::exit(EXIT_FAILURE);
        }
    }

好了,到这里总结一下,host的路由管理在初始化的时候,application_impl会创建routing_manager_impl类,并将自身的指针传给路由实现类,路由实现类创建的时候,会创建通信端点管理类,路由stub类,然后通过Stub类的init创建了一个用于本机内部通讯的Server端网络端点,之后再启动SD模块。

相关时序图如下:
在这里插入图片描述

Proxy端路由模块初始化

Proxy模式的路由管理器实现在routing_manager_proxy.cpp中, 相对于host端,proxy的初始化要简单的多,其构造函数如下:

routing_manager_proxy::routing_manager_proxy(routing_manager_host *_host,
            bool _client_side_logging,
            const std::set<std::tuple<service_t, instance_t> > & _client_side_logging_filter) :
        routing_manager_base(_host),
        is_connected_(false),
        is_started_(false),
        state_(inner_state_type_e::ST_DEREGISTERED),
        sender_(nullptr),
        receiver_(nullptr),
        register_application_timer_(io_),
        request_debounce_timer_ (io_),
        request_debounce_timer_running_(false),
        client_side_logging_(_client_side_logging),
        client_side_logging_filter_(_client_side_logging_filter)
{
}

构造函数初始化了一些状态与属性

void routing_manager_proxy::init() {
    routing_manager_base::init(std::make_shared<endpoint_manager_base>(this, io_, configuration_));
    {
        std::lock_guard<std::mutex> its_lock(sender_mutex_);
        sender_ = ep_mgr_->create_local(VSOMEIP_ROUTING_CLIENT);
    }
}

init函数就调用了网络通信端点管理器则创建了一个local_client_endpoint赋值给sender_, 用来与host端通信。

SD模块初始化

SD模块的初始化也比较简单,就是对SD协议中规定的各类参数进行赋值

service_discovery_impl::service_discovery_impl(
        service_discovery_host *_host,
        const std::shared_ptr<configuration>& _configuration)
    : io_(_host->get_io()),
      host_(_host),
      configuration_(_configuration),
      port_(VSOMEIP_SD_DEFAULT_PORT),
      reliable_(false),
      serializer_(std::make_shared<serializer>(
                      configuration_->get_buffer_shrink_threshold())),
      deserializer_(std::make_shared<deserializer>(
                      configuration_->get_buffer_shrink_threshold())),
      ttl_timer_(_host->get_io()),
      ttl_timer_runtime_(VSOMEIP_SD_DEFAULT_CYCLIC_OFFER_DELAY / 2),
      ttl_(VSOMEIP_SD_DEFAULT_TTL),
      subscription_expiration_timer_(_host->get_io()),
      max_message_size_(VSOMEIP_MAX_UDP_SD_PAYLOAD),
      initial_delay_(0),
      offer_debounce_time_(VSOMEIP_SD_DEFAULT_OFFER_DEBOUNCE_TIME),
      repetitions_base_delay_(VSOMEIP_SD_DEFAULT_REPETITIONS_BASE_DELAY),
      repetitions_max_(VSOMEIP_SD_DEFAULT_REPETITIONS_MAX),
      cyclic_offer_delay_(VSOMEIP_SD_DEFAULT_CYCLIC_OFFER_DELAY),
      offer_debounce_timer_(_host->get_io()),
      find_debounce_time_(VSOMEIP_SD_DEFAULT_FIND_DEBOUNCE_TIME),
      find_debounce_timer_(_host->get_io()),
      main_phase_timer_(_host->get_io()),
      is_suspended_(false),
      is_diagnosis_(false),
      last_msg_received_timer_(_host->get_io()),
      last_msg_received_timer_timeout_(VSOMEIP_SD_DEFAULT_CYCLIC_OFFER_DELAY +
                                           (VSOMEIP_SD_DEFAULT_CYCLIC_OFFER_DELAY / 10)) {
    next_subscription_expiration_ = std::chrono::steady_clock::now() + std::chrono::hours(24);
}

void
service_discovery_impl::init() {
    runtime_ = std::dynamic_pointer_cast<sd::runtime>(
            plugin_manager::get()->get_plugin(
                    plugin_type_e::SD_RUNTIME_PLUGIN, VSOMEIP_SD_LIBRARY));

    unicast_ = configuration_->get_unicast_address();
    sd_multicast_ = configuration_->get_sd_multicast();
    boost::system::error_code ec;
    sd_multicast_address_ = boost::asio::ip::address::from_string(sd_multicast_, ec);

    port_ = configuration_->get_sd_port();
    reliable_ = (configuration_->get_sd_protocol() == "tcp");
    max_message_size_ = (reliable_ ? VSOMEIP_MAX_TCP_SD_PAYLOAD :
            VSOMEIP_MAX_UDP_SD_PAYLOAD);

    ttl_ = configuration_->get_sd_ttl();

    // generate random initial delay based on initial delay min and max
    std::uint32_t initial_delay_min =
            configuration_->get_sd_initial_delay_min();
    std::uint32_t initial_delay_max =
            configuration_->get_sd_initial_delay_max();
    if (initial_delay_min > initial_delay_max) {
        const std::uint32_t tmp(initial_delay_min);
        initial_delay_min = initial_delay_max;
        initial_delay_max = tmp;
    }

    std::random_device r;
    std::mt19937 e(r());
    std::uniform_int_distribution<std::uint32_t> distribution(
            initial_delay_min, initial_delay_max);
    initial_delay_ = std::chrono::milliseconds(distribution(e));


    repetitions_base_delay_ = std::chrono::milliseconds(
            configuration_->get_sd_repetitions_base_delay());
    repetitions_max_ = configuration_->get_sd_repetitions_max();
    cyclic_offer_delay_ = std::chrono::milliseconds(
            configuration_->get_sd_cyclic_offer_delay());
    offer_debounce_time_ = std::chrono::milliseconds(
            configuration_->get_sd_offer_debounce_time());
    ttl_timer_runtime_ = cyclic_offer_delay_ / 2;

    ttl_factor_offers_ = configuration_->get_ttl_factor_offers();
    ttl_factor_subscriptions_ = configuration_->get_ttl_factor_subscribes();
    last_msg_received_timer_timeout_ = cyclic_offer_delay_
            + (cyclic_offer_delay_ / 10);
}

基本的初始化分析完了,下一篇分析application的启动流程

  • 18
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: 安卓源码合集-csdn是一个非常有用的工具和资源网站,提供了大量的开源安卓项目源码供开发者学习和使用。 首先,这个合集汇集了来自开源社区的各种优秀的安卓项目源码。这些源码不仅涵盖了安卓开发的各个方面,如UI界面、网络通信、数据存储等,还有一些特定领域的应用,如音视频处理、图像处理、机器学习等。从这些源码中,开发者可以学习到各种技术和知识,拓宽自己的视野和开发能力。 其次,通过这个合集,开发者可以找到适合自己需求的开源项目源码。不同的项目可能有不同的功能和特点,可以根据自己的需求进行选择和使用。有些源码可能是完整的应用程序,可以直接使用或基于此进行二次开发;有些源码可能是某个功能模块的实现,可以作为学习参考或直接集成到自己的项目中。 此外,这个合集还提供了源码的下载和查看方式。开发者可以通过下载源码来进行学习和使用,也可以在线查看源码进行参考。对于一些比较复杂的项目,还提供了详细的项目文档和使用说明,方便开发者的使用和理解。 总的来说,安卓源码合集-csdn为开发者提供了一个集成了优秀开源项目源码的平台,为开发者学习和使用安卓开发技术提供了便利。无论是初学者还是有一定经验的开发者,都可以在这里找到适合自己的项目源码,提升自己的开发水平和能力。 ### 回答2: 安卓源码合集 -csdn 是一个在 CSDN 上收集和分享安卓开源项目的一个资源合集。CSDN(博客专区)是一个面向IT技术人员的专业社区平台,它提供了丰富的技术资源和社交交流的平台。 安卓源码合集 -csdn 这个资源合集汇集了众多优秀的安卓开源项目,包括应用程序源码、工具类库、框架和示例代码等。通过该合集,开发者们可以快速地找到他们感兴趣的项目,并获取源码用于学习和参考。 这个合集的优点在于它的更新频率高,可以及时收录最新的开源项目。同时,合集中的项目都经过精心筛选,保证了其质量和可靠性。此外,该合集还提供了搜索功能,开发者们可以根据关键词进行搜索,找到适合自己需求的项目。 使用安卓源码合集 -csdn,开发者们可以快速地找到自己需要的安卓开源项目,可以通过学习他人的代码,了解他们的实现思路和技术。对于新手开发者来说,这个合集也可以作为一个学习的平台,他们可以通过阅读和理解他人的代码,提高自己的编码能力。 总之,安卓源码合集 -csdn 是一个非常有用的资源合集,可以帮助开发者们快速找到优秀的安卓开源项目,提高自己的技术水平。无论是初学者还是资深开发者,都可以从中受益。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值