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

news2024/11/24 7:04:37

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

那么,在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.coloradmin.cn/o/2164325.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据报文解析

数据报文解析 报文介绍 如下图所示&#xff0c;每一层把上传的协议包当作数据部分&#xff0c;加上自己的协议头部&#xff0c;组成自己的协议包。 一般说法&#xff1a;默认以太网协议包&#xff08;网络层从IP头部开始计算&#xff09;最大传输单元&#xff08;MTU&#x…

记一次sql查询优化

记一次sql查询优化 前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&#xff1a;神的孩子都在歌唱 今天测试环境发现一个问题&#xff0c;就是测试同事在测试的时候&#xff0c;发现cpu一直居高不下&#xff0c;然…

CNN网络训练WISDM数据集:模型仿真及可视化分析

卷积神经网络&#xff08;CNN&#xff09;因其强大的特征提取能力和深度学习架构而备受推崇&#xff0c;CNN在处理图像数据时展现出的卓越性能&#xff0c;使其成为解决各种视觉识别任务的首选工具。WISDM数据集是一个广泛用于运动估计研究的基准数据集&#xff0c;它包含了多个…

EmguCV学习笔记 VB.Net 11.9 姿势识别 OpenPose

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 EmguCV是一个基于OpenCV的开源免费的跨平台计算机视觉库,它向C#和VB.NET开发者提供了OpenCV库的大部分功能。 教程VB.net版本请访问…

AMEYA360代理:兆易创新GD32A7系列全新一代车规级MCU介绍

兆易创新GigaDevice(股票代码 603986)今日宣布&#xff0c;重磅推出全新一代车规级MCU GD32A7系列。与上一代采用Arm Cortex-M4/M33的产品相比&#xff0c;GD32A7系列搭载了超高性能Arm Cortex-M7内核&#xff0c;提供GD32A71x/GD32A72x/GD32A74x等多款型号供用户选择。该系列产…

git 生成和查看密钥

项目场景&#xff1a; 在前端项目开发中&#xff0c;经常会用到git。一般的小公司很少去设置git令牌或者密钥&#xff1b;而在一些大公司&#xff0c;会用到这个。今天主要整理下git如何生成和查看密钥。 密钥 1、生成密钥 cat ~/.ssh/id_rsa.pub 2、查看密钥 ssh-keygen…

电容笔最建议买哪一款?2024百元价位性价比首选榜单,速速码住!

现在电容笔已经成为我们日常学习、工作和创作中不可或缺的辅助工具。无论是记笔记、做习题&#xff0c;还是进行绘画、设计&#xff0c;电容笔都发挥着关键作用。然而&#xff0c;市场上的电容笔品牌和款式繁多&#xff0c;价格也从几十元到上千元不等&#xff0c;这让消费者在…

OSError: [Errno 16] Device or resource busy: ‘.nfs*‘报错解决办法

目录 1 项目场景&问题描述&#xff1a;2 原因分析&#xff1a;2.1 问题背景&#xff1a; 3 解决方案&#xff1a;3.1 创建存放临时文件的目录3.2 使用该目录3.2.1 设置环境变量 TMPDIR3.2.2 运行时设置&#xff08;推荐&#xff09;3.2.3 代码中设置 4 总结 1 项目场景&…

Redis 键值对数据库学习

目录 一、介绍 二、安装以及连接 三、设置连接密码 四、连接报错 五、redis 操作字符串以及过期时间 六、 redis 列表操作 七、redis 集合操作 八、hash 哈希操作 九、redis 发布和订阅操作 十、RDB和AOF的两种数据持久化机制 十一、 其他机器连接redis 十二、 pyt…

【Linux】解锁文件描述符奥秘,高效缓存区的实战技巧

fd和缓冲区 1. 文件描述符fd1.1. 概念与本质1.2. 打开文件的管理1.3. 一切皆文件的理解1.4. 分配规则1.5. 重定向的本质1.5.1. dup2 2. FILE中的缓冲区2.1. 概念2.2. 存在的原因2.3. 类型(刷新方案)2.4. 存放的位置2.4.1. 代码证明、现象解释 2.5. 模拟C标准库中的方法 1. 文件…

LiveGBS流媒体平台GB/T28181功能-支持电子放大拉框放大直播视频拉框放大录像视频流拉框放大电子放大

LiveGBS流媒体平台GB/T28181功能-支持电子放大拉框放大直播视频拉框放大录像视频流拉框放大电子放大 1、直播播放2、录像播放3、搭建GB28181视频直播平台 1、直播播放 国标设备-》查看通道-》播放 &#xff0c;左键单击可以拉取矩形框&#xff0c;放大选中的范围&#xff0c;释…

IPD如何解决产品开发的典型问题

IPD&#xff08;Integrated Product Development&#xff0c;集成产品开发&#xff09;是一种领先的、成熟的产品开发的管理思想和管理模式。它是根据大量成功的产品开发管理实践总结出来的&#xff0c;并被大量实践证明的高效的产品开发模式。从汉捷咨询二十多年来为五百多家企…

如何通过代理使用 Squid: 综合指南

文章目录 一、简介二、什么是 Squid?三、为什么在代理服务器中使用 Squid&#xff1f;四、设置代理五、使用代理设置 Squid5.1. 第一步5.2. 第二步5.3. 第三步 六、在网络搜索中使用代理&#xff1a;实用代码示例6.1. 使用代理的 cURL6.2. 使用代理的 Python 请求 七、结论 一…

嘉立创EDA-- 线宽、过孔和电流大小对比图

导线宽度和电流大小如何来考虑 1 电流大小需要考虑问题 1、允许的温升&#xff1a;如果能够允许的铜线升高的温度越高&#xff0c;那么允许通过的电流自然也就越高 2、走线的线宽&#xff1a;线越宽 &#xff0c;导线横截面积越大&#xff0c;电阻越小&#xff0c;发热越小&a…

磨具生产制造9人共用一台工作站

随着技术的不断进步与工业自动化的深入发展&#xff0c;如何优化生产流程、提高设备利用率成为了众多企业面临的重大课题。那么在磨具生产制造中实现9人共用一台工作站呢&#xff1f; 一、背景与挑战 在磨具制造行业&#xff0c;高精度、高效率的生产要求与复杂多变的工艺流程…

smartctl 命令:查看硬盘健康状态

一、命令简介 ​smartctl​ 命令用于获取硬盘的 SMART 信息。 介绍硬盘SMART 硬盘的 SMART (Self-Monitoring, Analysis, and Reporting Technology) 技术用于监控硬盘的健康状态&#xff0c;并能提供一些潜在故障的预警信息。通过查看 SMART 数据&#xff0c;用户可以了解硬…

如何选择渲染集群管理软件?

选择适合渲染集群管理软件可以考虑以下几个方面&#xff1a; 1.渲染需求&#xff1a;明确自己的渲染任务类型、规模和复杂度。如果需要处理大型、复杂的项目&#xff0c;对渲染效率和速度要求较高&#xff0c;就需要选择性能强劲的软件。 2.软件兼容性&#xff1a;确保软件支持…

[极客大挑战 2019]EasySQL1

前言&#xff1a; 记录一下web方面的题(第一次接触。。。&#xff09; 学校课程要学web…… - - 行吧&#xff0c;尝试一下&#xff0c;至少学过MySQL。。。 不过&#xff0c;实际上&#xff0c;现实现在SQL漏洞少得可怜&#xff0c;但学习不会有错。 参考&#xff1a;&…

vue-cli,element-plus,axios,proxy

一、vue-cli vue-cli俗称vue脚手架&#xff0c;是vue官方提供的快速生成vue 工程化项目的工具。 1.官网&#xff1a;https://cn.vuejs.org/ 中文官网: https://cli.vuejs.org/zh/ 特点&#xff1a;基于webpack&#xff0c;功能丰富且易于扩展&#xff0c;支持创建vue2和vu…

学校快递站点管理|基于springboot学校快递站点管理设计与实现(源码+数据库+文档)

学校快递站点管理系统目录 目录 基于springboot学校快递站点管理设计与实现 一、前言 二、系统功能设计 三、系统实现 前台功能模块 后台功能角模块 四、数据库设计 1、实体ER图 2、具体的表设计如下所示&#xff1a; 五、核心代码 六、论文参考 七、最新计算机毕设…