Android 车载应用开发指南 - CarService 详解(下)

车载应用正在改变人们的出行体验。从导航到娱乐、从安全到信息服务,车载应用的开发已成为汽车智能化发展的重要组成部分。而对于开发者来说,如何将自己的应用程序无缝集成到车载系统中,利用汽车的硬件和服务能力,是一个极具挑战性的话题

那么,在Android平台上开发车载应用时,CarService究竟扮演了什么样的角色?它的功能和使用方法有哪些关键点?

随着车联网技术的普及,车载应用的种类和功能也在迅速增加,从简单的导航和音乐播放,到智能语音助手、驾驶行为分析等多样化的服务。CarService作为Android车载应用开发的基础组件,为开发者提供了与车辆深度交互的能力。在未来,车载应用的智能化、个性化将进一步提升驾驶体验,并成为汽车产品竞争的重要元素。

03 CarService 实现原理

想要弄清楚CarService实现方式,首先需要搞明白CarService的启动流程。

CarService 启动流程主要分为以下四个步骤:

  1. SystemServer 启动 CarServiceHelperService 服务

  2. 在调用 startService() 后,CarServiceHelperService 的onStart() 方法通过 bindService 的方式启动 CarService(一个系统级别的 APK,位于 system/priv-app)

  3. 启动 CarService 后首先调用 onCreate(),创建 ICarImpl 对象并初始化,在此时创建了一系列 Car 相关的核心服务,并遍历 init 初始化

  4. 然后调用 onBind 将该 ICarImpl 对象返回给CarServiceHelperService,CarServiceHelperService 在内部的一个 Binder 对象 ICarServiceHelperImpl传递给 CarService,建立双向跨进程

3.1 启动 CarServiceHelperService 服务

SystemServer会在startOtherServices()方法中让SystemServiceManager先通过反射的形式创建出StartCarServiceHelperService对象。

  • 源码路径:frameworks/base/services/java/com/android/server/SystemServer.java

 private void startOtherServices(@NonNull TimingsTraceAndSlog t) {     ...     // 仅在 automotive 中启动     if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {         t.traceBegin("StartCarServiceHelperService");         final SystemService cshs = mSystemServiceManager             .startService(CAR_SERVICE_HELPER_SERVICE_CLASS);         if (cshs instanceof Dumpable) {             mDumper.addDumpable((Dumpable) cshs);         }         if (cshs instanceof DevicePolicySafetyChecker) {             dpms.setDevicePolicySafetyChecker((DevicePolicySafetyChecker) cshs);         }         t.traceEnd();     }     ... }

然后在SystemServiceManager中调用StartCarServiceHelperService的onStart()方法。

CarServiceHelperService是CarService的 SystemService 端的配套服务。

  • 源码路径: 

frameworks/base/services/core/java/com/android/server/SystemServiceManager.java​​​​​​​

 public SystemService startService(String className) {     final Class<SystemService> serviceClass = loadClassFromLoader(className,             this.getClass().getClassLoader());     return startService(serviceClass); }  public void startService(@NonNull final SystemService service) {     // Register it. mServices.add(service);     long time = SystemClock.elapsedRealtime();     try {         service.onStart();     } catch (RuntimeException ex) {         throw new RuntimeException("Failed to start service " + service.getClass().getName()                 + ": onStart threw an exception", ex);     }     warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onStart"); } 

3.2 绑定 CarService 服务

  • 源码路径:frameworks/opt/car/services/src/com/android/internal/car/CarServiceHelperService.java​​​​​​​

     private static final String CAR_SERVICE_INTERFACE = "android.car.ICar";      @Override     public void onStart() {         EventLog.writeEvent(EventLogTags.CAR_HELPER_START);          IntentFilter filter = new IntentFilter(Intent.ACTION_REBOOT);         filter.addAction(Intent.ACTION_SHUTDOWN);         mContext.registerReceiverForAllUsers(mShutdownEventReceiver, filter, null, null);         mCarWatchdogDaemonHelper.addOnConnectionChangeListener(mConnectionListener);         mCarWatchdogDaemonHelper.connect();         Intent intent = new Intent();         intent.setPackage("com.android.car");  // 绑定包名,设置广播仅对该包有效         intent.setAction(CAR_SERVICE_INTERFACE);  // 绑定 action,表明想要启动能够响应设置的这个 action 的活动,并在清单文件 AndroidManifest.xml 中设置 action 属性         // 绑定后回调         if (!mContext.bindServiceAsUser(intent, mCarServiceConnection, Context.BIND_AUTO_CREATE,                 mHandler, UserHandle.SYSTEM)) {             Slogf.wtf(TAG, "cannot start car service");         }         loadNativeLibrary();     }

  • 源码路径:packages/services/Car/service/AndroidManifest.xml

sharedUserId 是系统级别的,类似 SystemUI,它编译出来同样是一个 APK 文件

  • 设备文件路径:/system/priv-app/CarService/CarService.apk

 <manifest xmlns:android="http://schemas.android.com/apk/res/android"         xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"         package="com.android.car"         coreApp="true"         android:sharedUserId="android.uid.system">      ......     <application android:label="@string/app_title"          android:directBootAware="true"          android:allowBackup="false"          android:persistent="true">          <service android:name=".CarService"              android:singleUser="true"              android:exported="true">             <intent-filter>                 <action android:name="android.car.ICar"/>             </intent-filter>         </service>         ......     </application>

3.3 CarService 初始化

CarService进入启动时序后,会在onCreate()方法中进行一系列自身的初始化操作,步骤如下:

1)通过 HIDL 接口获取到 HAL 层的 IHwBinder 对象IVehicle,与 AIDL 的用法类似,必须持有 IHwBinder 对象我们才可以与 Vehicle HAL 层进行通信。

2)创建 ICarImpl 对象,并调用init方法,它就是ICar.aidl接口的实现类,我们需要通过它才能拿到其他的 Service 的 IBinder 对象。

3)将ICar.aidl的实现类添加到 ServiceManager 中。

4)设定 SystemProperty,将CarService设定为创建完成状态,只有包含CarService在内的所有的核心 Service 都完成初始化,才能结束开机动画并发送开机广播。

  • 源码路径:packages/services/Car/service/src/com/android/car/CarService.java​​​​​​​

     @Override     public void onCreate() {         LimitedTimingsTraceLog initTiming = new LimitedTimingsTraceLog(CAR_SERVICE_INIT_TIMING_TAG,                 Trace.TRACE_TAG_SYSTEM_SERVER, CAR_SERVICE_INIT_TIMING_MIN_DURATION_MS);         initTiming.traceBegin("CarService.onCreate");          initTiming.traceBegin("getVehicle");         // 获取 hal 层的 Vehicle service         mVehicle = getVehicle();         initTiming.traceEnd();         ...         //创建 ICarImpl 实例         mICarImpl = new ICarImpl(this,                 mVehicle,                 SystemInterface.Builder.defaultSystemInterface(this).build(),                 mVehicleInterfaceName);         //然后调用 ICarImpl 的 init 初始化方法         mICarImpl.init();          linkToDeath(mVehicle, mVehicleDeathRecipient);         //将该 service 注册到 ServiceManager         ServiceManager.addService("car_service", mICarImpl);         //设置 boot.car_service_created 属性         SystemProperties.set("boot.car_service_created", "1");          super.onCreate();          initTiming.traceEnd(); // "CarService.onCreate"     }      @Nullable     private static IVehicle getVehicle() {         final String instanceName = SystemProperties.get("ro.vehicle.hal", "default");          try {             //该 service 启动文件 hardware/interfaces/automotive/vehicle/2.0/default/android.hardware.automotive.vehicle@2.0-service.rc             return android.hardware.automotive.vehicle.V2_0.IVehicle.getService(instanceName);         } catch (RemoteException e) {             Slog.e(CarLog.TAG_SERVICE, "Failed to get IVehicle/" + instanceName + " service", e);         } catch (NoSuchElementException e) {             Slog.e(CarLog.TAG_SERVICE, "IVehicle/" + instanceName + " service not registered yet");         }         return null;     }

接着再看ICarImpl的实现,如下所示:

1)创建各个核心服务对象

2)把服务对象缓存到 CarLocalServices 中,这里主要是为了方便 Service 之间的相互访问

  • 源码路径:

/packages/services/Car/service/src/com/android/car/ICarImpl.java​​​​​​​

     @VisibleForTesting     ICarImpl(Context serviceContext, IVehicle vehicle, SystemInterface systemInterface,             String vehicleInterfaceName,             @Nullable CarUserService carUserService,             @Nullable CarWatchdogService carWatchdogService,             @Nullable ICarPowerPolicySystemNotification powerPolicyDaemon) {         ...         mContext = serviceContext;         mSystemInterface = systemInterface;         CarLocalServices.addService(SystemInterface.class, mSystemInterface);         //创建 VehicleHal 对象         mHal = constructWithTrace(t, VehicleHal.class,                 () -> new VehicleHal(serviceContext, vehicle));         ...         // 创建核心服务对象,并缓存到 CarLocalServices         mCarPropertyService = constructWithTrace(t, CarPropertyService.class, () -> new CarPropertyService(serviceContext, mHal.getPropertyHal()));         mCarDrivingStateService = constructWithTrace(t, CarDrivingStateService.class,() -> new CarDrivingStateService(serviceContext, mCarPropertyService));         mCarUXRestrictionsService = constructWithTrace(t, CarUxRestrictionsManagerService.class, () -> new CarUxRestrictionsManagerService(serviceContext, mCarDrivingStateService, mCarPropertyService));         ...          // 将创建的服务对象依次添加到一个 list 中保存起来         List<CarServiceBase> allServices = new ArrayList<>();         allServices.add(mFeatureController);         allServices.add(mCarUXRestrictionsService); // mCarUserService depends on it         allServices.add(mCarUserService);         allServices.add(mSystemActivityMonitoringService);         allServices.add(mCarPowerManagementService);         allServices.add(mCarPropertyService);         allServices.add(mCarDrivingStateService);         allServices.add(mCarOccupantZoneService);         addServiceIfNonNull(allServices, mOccupantAwarenessService);         allServices.add(mCarPackageManagerService);         allServices.add(mCarInputService);         allServices.add(mGarageModeService);            ...     }      @MainThread     void init() {         LimitedTimingsTraceLog t = new LimitedTimingsTraceLog(CAR_SERVICE_INIT_TIMING_TAG,                 Trace.TRACE_TAG_SYSTEM_SERVER, CAR_SERVICE_INIT_TIMING_MIN_DURATION_MS);          t.traceBegin("ICarImpl.init");          t.traceBegin("VHAL.init");         mHal.init();         t.traceEnd();          t.traceBegin("CarService.initAllServices");         //启动的所有服务遍历调用 init 初始化(各个都继承了 CarServiceBase)         for (CarServiceBase service : mAllServices) {             t.traceBegin(service.getClass().getSimpleName());             service.init();             t.traceEnd();         }         t.traceEnd(); // "CarService.initAllServices"          t.traceEnd(); // "ICarImpl.init"     }

然后将上面 onCreate() 创建的 mICarImpl 对象返回:

  1. onBind() 回调方法会继续传递通过 bindService() 传递来的 intent 对象(即上面的bindServiceAsUser方法)

  2. onUnbind() 会处理传递给 unbindService() 的 intent 对象。如果 service 允许绑定,onBind() 会返回客户端与服务互相联系的通信句柄

  • 源码路径:

/packages/services/Car/service/src/com/android/car/CarService.java​​​​​​​

     @Override     public IBinder onBind(Intent intent) {         return mICarImpl;     }

所以此处的 mICarImpl 会作为 IBinder 返回给CarServiceHelperService.java - bindServiceAsUser方法中的参数 mCarServiceConnection(回调)

3.4 回调 ServiceConnection

  • ICarImpl 初始化完毕,会作为 IBinder 返回给CarServiceHelperService.java - bindServiceAsUser方法中绑定此服务的 mCarServiceConnection(回调)

mCarServiceConnection 初始化如下:

  1. 其中返回的 ICarImpl 被保存在了 CarServiceHelperService 的 mCarService

  2. mCarService.transact 跨进程通信,调用 ICar.aidl 中定义的第一个方法 setCarServiceHelper

  • 源码路径:

/frameworks/opt/car/services/src/com/android/internal/car/CarServiceHelperService.java​​​​​​​

 private static final String CAR_SERVICE_INTERFACE = "android.car.ICar"; private IBinder mCarService; private final ICarServiceHelperImpl mHelper = new ICarServiceHelperImpl();      private final ServiceConnection mCarServiceConnection = new ServiceConnection() {         @Override         public void onServiceConnected(ComponentName componentName, IBinder iBinder) {             if (DBG) {                 Slogf.d(TAG, "onServiceConnected: %s", iBinder);             }             handleCarServiceConnection(iBinder);         }          @Override         public void onServiceDisconnected(ComponentName componentName) {             handleCarServiceCrash();         }     };          @VisibleForTesting     void handleCarServiceConnection(IBinder iBinder) {         synchronized (mLock) {             if (mCarServiceBinder == iBinder) {                 return; // already connected.             }             Slogf.i(TAG, "car service binder changed, was %s new: %s", mCarServiceBinder, iBinder);             //1. 返回的 ICarImpl 被保存在了 CarServiceHelperService 的 mCarServiceBinder             mCarServiceBinder = iBinder;             Slogf.i(TAG, "**CarService connected**");         }          sendSetSystemServerConnectionsCall();         ...     }      private void sendSetSystemServerConnectionsCall() {         Parcel data = Parcel.obtain();         data.writeInterfaceToken(CAR_SERVICE_INTERFACE);         data.writeStrongBinder(mHelper.asBinder());         //将 ICarServiceHelperImpl 类型的对象作为数据跨进程传递         data.writeStrongBinder(mCarServiceConnectedCallback.asBinder());         IBinder binder;         synchronized (mLock) {             binder = mCarServiceBinder;         }         int code = IBinder.FIRST_CALL_TRANSACTION;         try {             //2. 跨进程传输             //对端是 mCarService 即 ICarImpl,调用 binder 的 transact 进行跨进程通信             //其 code 代表需要调用的对端方法,data 为携带的传输数据             //FIRST_CALL_TRANSACTION  = 0x00000001,即调用对端 ICar.aidl 中定义的第一个方法 setCarServiceHelper             if (VERBOSE) Slogf.v(TAG, "calling one-way binder transaction with code %d", code);             // oneway void setSystemServerConnections(in IBinder helper, in IBinder receiver) = 0;             binder.transact(code, data, null, Binder.FLAG_ONEWAY);             if (VERBOSE) Slogf.v(TAG, "finished one-way binder transaction with code %d", code);         }         ...     }

跨进程 setSystemServerConnections​​​​​​​

     @Override     public void setSystemServerConnections(IBinder helper, IBinder receiver) {         Bundle bundle;         try {             EventLog.writeEvent(EventLogTags.CAR_SERVICE_SET_CAR_SERVICE_HELPER,                     Binder.getCallingPid());             assertCallingFromSystemProcess();             //将 ICarServiceHelper 的代理端保存在 ICarImpl 内部 mICarServiceHelper             ICarServiceHelper carServiceHelper = ICarServiceHelper.Stub.asInterface(helper);             synchronized (mLock) {                 mICarServiceHelper = carServiceHelper;             }             //同时也传给了 SystemInterface             //此时他们有能力跨进程访问 CarServiceHelperService             mSystemInterface.setCarServiceHelper(carServiceHelper);             mCarOccupantZoneService.setCarServiceHelper(carServiceHelper);             mCarUserService.setCarServiceHelper(carServiceHelper);             ...     }

3.5 小结

CarService的启动时序如下所示:

 

 

04 总结

本文讲解了CarService的总体结构、使用方法及启动流程。


CarService中实现的功能非常庞大,可以说相比传统手机端的 Android 系统,AAOS 中独特且最重要的部分都在 Framework 的CarService中。

  • 首先 CarService 是一个系统级别的服务 APK,类似 SystemUI,其在开机时由 SystemServer 通过 CarServiceHelperService 启动。

  • CarServiceHelperService 通过绑定服务的方式启动 CarService,启动之后创建了一个 Binder 对象 ICarImpl,并通过 onBind 返回给 system_server 进程。

  • ICarImpl 构造方法中创建了一系列和汽车相关的核心服务,并依次启动这些服务即调用各自 init 方法。ICarImpl 返回给 CarServiceHelperService 之后,CarServiceHelperService 也将其内部的一个 Binder 对象(ICarServiceHelperImpl)传递到了 CarService 进程,自此 CarService 和 system_server 两个进程建立了双向 Binder 通信。

  • ICarImpl 返回给 CarServiceHelperService 之后,CarServiceHelperService 也将其内部的一个 Binder 对象(ICarServiceHelperImpl)传递到了 CarService 进程,自此 CarService 和 system_server 两个进程建立了双向 Binder 通信。

 

CarService为Android车载应用开发者提供了一个强大而灵活的平台,让应用程序能够充分利用汽车的硬件和服务能力,打造更加智能化和便捷的驾驶体验。掌握CarService的使用,是车载应用开发中的重要一环,也是实现车载生态系统中创新应用的关键。

“在车载应用的世界里,技术的每一次进步,都是为了让行驶的每一公里更加安全、便捷和愉悦。”

 END 

链接:https://juejin.cn/post/7353827463632404517  本文为转载,转载文章所包含的文字来源于作者。如因内容或版权等问题,请联系进行删除

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/431732.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络:物理层 --- 基本概念、编码与调制

目录 一. 物理层的基本概念 二. 数据通信系统的模型 三. 编码 3.1 基本概念 3.2 不归零制编码 3.3 归零制编码 3.4 曼切斯特编码 3.5 差分曼切斯特编码 ​编辑 四. 调制 4.1 调幅 4.2 调频 4.3 调相 4.4 混合调制 今天我们讲的是物理…

【JavaEE】——线程的安全问题和解决方式

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯&#xff0c;你们的点赞收藏是我前进最大的动力&#xff01;&#xff01;希望本文内容能够帮助到你&#xff01; 目录 一&#xff1a;问题引入 二&#xff1a;问题深入 1&#xff1a;举例说明 2&#xff1a;图解双线程计算…

机器学习04-逻辑回归(python)-02原理与损失函数

​​​​​​​ 1. 逻辑回归概念 逻辑回归&#xff08;Logistic Regression&#xff09; 是一种 分类模型&#xff0c;主要用于解决 二分类问题&#xff08;即分成两类&#xff0c;如是否通过、是否患病等&#xff09;。逻辑回归的目标是根据输入的特征预测一个 概率&#xff0…

AI大模型项目实战v0.2: 结合个人知识库

前言 在AI大模型项目实战v0.1版本中&#xff0c;我们实现了一个最简单的基于纯LLM的问答机器人Tbot。 今天升级到v0.2版本&#xff0c;结合个人知识库。 本系列每个版本&#xff0c;都将提供完整的代码文档&#xff0c;获取方法见文末。 下面开启我们的v0.2版本之旅。 v0.2 Tb…

Icarus翼星求生教你使用服务器开服

1、购买后登录服务器&#xff08;百度莱卡云游戏面板&#xff09; 登录面板的信息在绿色的登陆面板按键下方&#xff0c;不是你的莱卡云账号 进入控制面板后会出现正在安装的界面&#xff0c;大约10分钟左右就能安装完成 2、创建端口 点击目录上的网络&#xff0c;再次页面下点…

中伟视界:AI算法如何精准识别井下与传送带上堆料,提升矿山安全生产效率,减少事故风险

传送带堆料分为两种情况&#xff0c;一种是传送带的井下堆料检测AI算法&#xff0c;一种是传送带上面的堆料检测AI算法&#xff0c;传送带井下堆料检测AI算法是在带式输送机的漏煤下方井下安装摄像仪&#xff0c;通过视频分析检测井下堆煤情况&#xff0c;当洒煤堆积到一定程度…

【Git入门】使用 Git 进行项目管理:Word Count 程序开发与托管

在软件开发过程中&#xff0c;版本控制工具是不可或缺的。Git 作为一款强大的分布式版本控制工具&#xff0c;为开发者提供了高效的代码管理和协作方式。本博客将介绍如何下载安装 Git 版本管理工具&#xff0c;并使用 Git 和 GitHub 平台进行一个名为 Word Count 的项目开发与…

二分

LeetCode34 在排序数组中查找元素的第一个和最后一个位置&#xff08;二分模板题&#xff0c;左闭右开写法&#xff09; /** lc appleetcode.cn id34 langcpp** [34] 在排序数组中查找元素的第一个和最后一个位置*/// lc codestart #include<iostream> using namespace s…

Python发送邮件教程:如何实现自动化发信?

Python发送邮件有哪些方法&#xff1f;如何利用python发送邮件&#xff1f; 无论是工作汇报、客户通知还是个人提醒&#xff0c;邮件都能快速传递信息。Python发送邮件的自动化功能就显得尤为重要。AokSend将详细介绍如何使用Python发送邮件&#xff0c;实现自动化发信&#x…

逆向推理+ChatGPT,让论文更具说服力

学境思源&#xff0c;一键生成论文初稿&#xff1a; AcademicIdeas - 学境思源AI论文写作 使用ChatGPT辅助“逆向推理”技巧&#xff0c;可以显著提升论文的质量和说服力。逆向推理从结论出发&#xff0c;倒推所需的证据和论点&#xff0c;确保整个论证过程逻辑严密且无漏洞。…

Spring Cloud :Hystrix实现优雅的服务容错

目录 Hystrix概述&#xff1a;第一个Hystrix程序步骤1&#xff1a;创建父工程hystrix-1步骤2&#xff1a;改造服务提供者步骤3&#xff1a;改造服务消费者为Hystrix客户端&#xff08;1&#xff09;添加Hystrix依赖&#xff08;2&#xff09;添加EnableHystrix注解&#xff08;…

编程练习2 数据单元的变量替换

示例1: 1,2<A>00 示例2: 1,2<A>00,3<A>00 示例3: <B>12,1,2<B>1 示例4: <B<12,1 输出依次如下&#xff1a; #include<iostream> #include<vector> #include<string>using namespace std;/* 字符分割函数 将传入…

人工智能-大语言模型-微调技术-LoRA及背后原理简介

1. 《LORA: LOW-RANK ADAPTATION OF LARGE LANGUAGE MODELS》 LORA: 大型语言模型的低秩适应 摘要&#xff1a; 随着大规模预训练模型的发展&#xff0c;全参数微调变得越来越不可行。本文提出了一种名为LoRA&#xff08;低秩适应&#xff09;的方法&#xff0c;通过在Transf…

STM32 使用 CubeMX 实现按键外部中断

目录 问题背景知识参考需要改什么注意尽量不要在中断函数使用 循环函数做延时中断函数中延时方法调试 问题 我想实现按钮触发紧急停止类似功能&#xff0c;需要使用按键中断功能。 背景知识 GPIO 点亮 LED。stm32cubemx hal学习记录&#xff1a;GPIO输入输出。STM32—HAL库 …

活动系统开发之采用设计模式与非设计模式的区别-后台功能总结

1、数据库ER图 2、后台功能字段 题目功能字段 数据列表 编号题目名称选项数量状态 1启用0禁用创建时间修改时间保存 题目名称选项集 选项内容是否正确答案 1正确0错误启禁用删除素材图库功能字段 数据列表 编号原文件名称文件类型文件大小加密后文件名文件具体路径上传类型状态…

从零开始学习Python

目录 从零开始学习Python 引言 环境搭建 安装Python解释器 选择IDE 基础语法 注释 变量和数据类型 变量命名规则 数据类型 运算符 算术运算符 比较运算符 逻辑运算符 输入和输出 控制流 条件语句 循环语句 for循环 while循环 循环控制语句 函数和模块 定…

29 C 语言中的随机数实现:rand 与 srand

目录 1 为什么需要随机数&#xff1f; 1.1 背景介绍 1.2 应用场景 2 C 语言实现随机数 2.1 rand() 函数 2.1.1 函数原型 2.1.2 功能说明 2.1.3 案例演示 2.2 srand() 函数 2.2.1 函数原型 2.2.2 功能说明 2.2.3 案例演示 2.3 指定范围的随机数 2.3.1 获…

【JavaEE】数据链路层协议和DNS

&#x1f525;个人主页&#xff1a; 中草药 &#x1f525;专栏&#xff1a;【Java】登神长阶 史诗般的Java成神之路 &#x1f45c;一.以太网 以太网&#xff08;Ethernet&#xff09;是一种局域网技术&#xff0c;它定义了开放系统互连&#xff08;OSI&#xff09;模型中的物理…

Python网络爬虫获取Wallhaven壁纸图片(源码)

** 话不多说&#xff0c;直接附源码&#xff0c;可运行&#xff01; ** import requests from lxml import etree from fake_useragent import UserAgent import timeclass wallhaven(object):def __init__(self):# yellow# self.url "https://wallhaven.cc/search?co…

K8S介绍---搭建集群

Kubernetes介绍 官网&#xff1a;https://kubernetes.io/ 一、应用部署方式演变 1、传统部署&#xff1a;互联网早期&#xff0c;会直接将应用程序部署在物理机上 优点&#xff1a;简单&#xff0c;不需要其他技术的参与 缺点&#xff1a;不能为应用程序定义资源使用边界&a…