从车窗升降一探 Android 车机的重要 API:车辆属性 CarProperty

前言
前面我们介绍过 Android 车机 Automotive OS 的几块重要内容:
- 一文了解 Android 车机如何处理中控的旋钮输入
- 从实体按键看 Android 车机的自定义事件机制
- 深度入门 Android 车机核心 CarService 的构成和链路
本篇文章我们聚焦 Android 车机上最重要、最常用的接口:即车辆属性 CarPropertyManager。
并结合车窗升降这种典型的场景来探究它的完整链路。
实现车窗升降
CarPropertyManager 通常针对某个 Property 发起读写,这些属性有很多,从车窗到空调、油量到续航等等。
想要控制它们,得需要知道它的唯一标识,并和系统定义的 ID 保持一致。那么车窗对应的 ID 为 VehiclePropertyIds 中的 WINDOW_POS,其要求 app 拥有专用的权限:
android.car.Car.PERMISSION_CONTROL_CAR_WINDOWS
属性监听
当目标属性发生变化,可以通过 CarPropertyEventCallback 通知到请求 App,为了满足各种场景,系统提供了设置通知频次的可能。
总共有如下几种:
| 通知频次类型 | 频次(HZ) |
|---|---|
| SENSOR_RATE_ONCHANGE | - |
| SENSOR_RATE_FASTEST | 100 |
| SENSOR_RATE_FAST | 10 |
| SENSOR_RATE_NORMAL | 1 |
| SENSOR_RATE_UI | 5 |
对于车窗、入座这些即时信号,采用 SENSOR_RATE_ONCHANGE 类型即可,意味着只在变化的时候通知。当然,注册的时候会立即回调一次以通知当前的数值。
代码很简单,构建 CarPropertyEventCallback 实例,并传递目标 Property ID 和上述的通知类型,即可完成该属性的监听。
class CarEventCallBack: CarPropertyManager.CarPropertyEventCallback {override fun onChangeEvent(value: CarPropertyValue<*>?) { }
}val car = Car.createCar(context)
val carPropertyManager =car?.getCarManager(Car.PROPERTY_SERVICE) as CarPropertyManagercarPropertyManager.registerCallback(CarEventCallBack(),VehiclePropertyIds.WINDOW_POS,CarPropertyManager.SENSOR_RATE_ONCHANGE
)
属性读写
对于车窗硬件来说,用户关心的是其升降的状况,系统用 0~100 来进行定义,继而决定了它的值为 Int 型。
那么读取的 API 为 getIntProperty(),参数:
- prop:希望读取的属性 ID,比如上面的车窗 Property ID:WINDOW_POS
- area:希望读取属性的位置信息 zone,对应到
VehicleAreaWindow类型中常量
注意:该方法是同步的,而且因为车窗等属性的操作耗时,建议在子线程 invoke。
写入的 API 为 setIntProperty(),参数:
- prop:希望改写的属性 ID,
- areaId:该属性对应的位置薪资
- val:Value to set,比如车窗即 0 ~ 100,对应着完全打开到完全关闭
和 getIntProperty() 一样,set 一样耗时,需要同样运行在子线程中。
系统预设的和 Window 相关的 zone areaId 如下,比如前排、驾驶侧、副驾驶侧、乘客侧、天窗、挡风玻璃等。
package android.hardware.automotive.vehicle;public @interface VehicleAreaWindow {public static final int FRONT_WINDSHIELD = 1;public static final int REAR_WINDSHIELD = 2;public static final int ROW_1_LEFT = 16;public static final int ROW_1_RIGHT = 64;public static final int ROW_2_LEFT = 256;public static final int ROW_2_RIGHT = 1024;public static final int ROW_3_LEFT = 4096;public static final int ROW_3_RIGHT = 16384;public static final int ROOF_TOP_1 = 65536;public static final int ROOF_TOP_2 = 131072;
}
如下代码展示如何了完全打开驾驶位车窗。
Thread().run {carPropertyManager.setIntProperty(VehiclePropertyIds.WINDOW_POS,VehicleAreaWindow.WINDOW_ROW_1_LEFT,0)
}
工作原理
首先,车窗相关的 area 在 HAL 层有相应的定义:
// android/hardware/automotive/vehicle/2.0/types.h /*** Various windshields/windows in the car.*/
enum class VehicleAreaWindow : int32_t {FRONT_WINDSHIELD = 1 /* 0x00000001 */,REAR_WINDSHIELD = 2 /* 0x00000002 */,ROW_1_LEFT = 16 /* 0x00000010 */,ROW_1_RIGHT = 64 /* 0x00000040 */,ROW_2_LEFT = 256 /* 0x00000100 */,ROW_2_RIGHT = 1024 /* 0x00000400 */,ROW_3_LEFT = 4096 /* 0x00001000 */,ROW_3_RIGHT = 16384 /* 0x00004000 */,ROOF_TOP_1 = 65536 /* 0x00010000 */,ROOF_TOP_2 = 131072 /* 0x00020000 */,
};
读取
直接看 getIntProperty(),首先调用 checkSupportedProperty() 检查是否支持该属性,当不支持的话抛出:
IllegalArgumentException: “Unsupported property:xxx”
接着调用 getProperty(),不过指定了返回的数据类型。
public class CarPropertyManager extends CarManagerBase {public int getIntProperty(int prop, int area) {checkSupportedProperty(prop);CarPropertyValue<Integer> carProp = getProperty(Integer.class, prop, area);return handleNullAndPropertyStatus(carProp, area, 0);}private void checkSupportedProperty(int propId) {switch (propId) {case VehiclePropertyIds.INITIAL_USER_INFO:case VehiclePropertyIds.SWITCH_USER:case VehiclePropertyIds.CREATE_USER:case VehiclePropertyIds.REMOVE_USER:case VehiclePropertyIds.USER_IDENTIFICATION_ASSOCIATION:throw new IllegalArgumentException("Unsupported property: "+ VehiclePropertyIds.toString(propId) + " (" + propId + ")");}}...
}
getProperty() 的实现在于 CarPropertyService。
public class CarPropertyManager extends CarManagerBase {public <E> CarPropertyValue<E> getProperty(@NonNull Class<E> clazz, int propId, int areaId) {checkSupportedProperty(propId);try {CarPropertyValue<E> propVal = mService.getProperty(propId, areaId);if (propVal != null && propVal.getValue() != null) {Class<?> actualClass = propVal.getValue().getClass();}return propVal;}...}...
}
CarPropertyService 按照如下步骤进行:
-
先到存放所有 Property ID 的
SparseArray中检查是否确实存在该 Property,如果不存在的话打印 error 提醒并结束 -
获取该 Property 的 permission 配置,如果不存在的话,抛出:
SecurityException: Platform does not have permission to read value for property Id: 0x…
-
assertPermission()检查当前 CarService 是否确实被授予了如上 permission -
最后调用持有的
PropertyHalService继续发出读取的调用
public class CarPropertyService extends ICarProperty.Stubimplements CarServiceBase, PropertyHalService.PropertyHalListener {@Overridepublic CarPropertyValue getProperty(int prop, int zone) ... {synchronized (mLock) {if (mConfigs.get(prop) == null) {// Do not attempt to register an invalid propIdSlogf.e(TAG, "getProperty: propId is not in config list:0x" + toHexString(prop));return null;}}// Checks if android has permission to read property.String permission = mHal.getReadPermission(prop);if (permission == null) {throw new SecurityException("Platform does not have permission to read value for "+ "property Id: 0x" + Integer.toHexString(prop));}CarServiceUtils.assertPermission(mContext, permission);return runSyncOperationCheckLimit(() -> {return mHal.getProperty(prop, zone);});}...
}
PropertyHalService 首先调用 managerToHalPropId() 将 Property ID 转为 HAL 中该 ID 的定义,并再度检查该 HAL ID 是否确实存在。如果不存在的话亦抛出:
IllegalArgumentException:Invalid property Id : 0x…
接着,通过 VehicleHal 传递 HAL 中 ID 继续读取得到 HalPropValue,当读取的 value 存在的话,首先得获取该 Property 在 HAL 层和上层定义的 HalPropConfig 规则。
最后依据 config 将 value 解析成 CarPropertyValue 类型返回。
public class PropertyHalService extends HalServiceBase {
'/ ' ...public CarPropertyValue getProperty(int mgrPropId, int areaId)throws IllegalArgumentException, ServiceSpecificException {int halPropId = managerToHalPropId(mgrPropId);if (!isPropertySupportedInVehicle(halPropId)) {throw new IllegalArgumentException("Invalid property Id : 0x" + toHexString(mgrPropId));}// CarPropertyManager catches and rethrows exception, no need to handle here.HalPropValue value = mVehicleHal.get(halPropId, areaId);if (value == null) {return null;}HalPropConfig propConfig;synchronized (mLock) {propConfig = mHalPropIdToPropConfig.get(halPropId);}return value.toCarPropertyValue(mgrPropId, propConfig);}...
}
其实 VehicleHal 并未做太多处理就直接交给了 HalClient 来处理。
public class VehicleHal implements HalClientCallback {...public HalPropValue get(int propertyId)throws IllegalArgumentException, ServiceSpecificException {return get(propertyId, NO_AREA);}...public HalPropValue get(int propertyId, int areaId)throws IllegalArgumentException, ServiceSpecificException {return mHalClient.getValue(mPropValueBuilder.build(propertyId, areaId));}...
}
HalClient 通过 invokeRetriable() 进行超时为 50ms 的 internalGet() 调用:如果结果是 TRY_AGAIN 并且尚未超时的话,再次调用;反之已经超时或者结果成功获取到的话,即结束。
后续会再次检查该 Result 中的 status,是否是不合法的、空的值等等,通过检查的话则返回 HalPropValue 出去。
final class HalClient {...private static final int SLEEP_BETWEEN_RETRIABLE_INVOKES_MS = 50;HalPropValue getValue(HalPropValue requestedPropValue)throws IllegalArgumentException, ServiceSpecificException {ObjectWrapper<ValueResult> resultWrapper = new ObjectWrapper<>();resultWrapper.object = new ValueResult();int status = invokeRetriable(() -> {resultWrapper.object = internalGet(requestedPropValue);return resultWrapper.object.status;}, mWaitCapMs, mSleepMs);ValueResult result = resultWrapper.object;if (StatusCode.INVALID_ARG == status) {throw new IllegalArgumentException(getValueErrorMessage("get", requestedPropValue, result.errorMsg));}if (StatusCode.OK != status || result.propValue == null) {if (StatusCode.OK == status) {status = StatusCode.NOT_AVAILABLE;}throw new ServiceSpecificException(status, getValueErrorMessage("get", requestedPropValue, result.errorMsg));}return result.propValue;}private ValueResult internalGet(HalPropValue requestedPropValue) {final ValueResult result = new ValueResult();try {result.propValue = mVehicle.get(requestedPropValue);result.status = StatusCode.OK;result.errorMsg = new String();}...return result;}...
}
internalGet() 的实现由持有的 VehicleStub 实例的 get 方法完成,其实现对应于依据 HIDL 的配置调用 HAL 侧获取相应数据。
public abstract class VehicleStub {...@Nullablepublic abstract HalPropValue get(HalPropValue requestedPropValue)throws RemoteException, ServiceSpecificException;...
}
写入
set 写入的链路和 get 大同小异,主要区别是:
- 事先构建待写入的属性实例
CarPropertyValue并传入 - 传入属性变化时 callback 用的
CarPropertyEventListenerToService实例
public class CarPropertyManager extends CarManagerBase {public void setIntProperty(int prop, int areaId, int val) {setProperty(Integer.class, prop, areaId, val);}public <E> void setProperty(@NonNull Class<E> clazz, int propId, int areaId, @NonNull E val) {checkSupportedProperty(propId);try {runSyncOperation(() -> {mService.setProperty(new CarPropertyValue<>(propId, areaId, val),mCarPropertyEventToService);return null;});}...}
}
下一层 CarPropertyService 的实现也是通过 PropertyHalService 进行。
传入的 CarPropertyEventListenerToService 其实是 ICarPropertyEventListener AIDL 代理,这里会将其转为 Binder 对象,按照调用的源头 client 缓存起来,在属性变化的时候用。
public class CarPropertyService extends ICarProperty.Stubimplements CarServiceBase, PropertyHalService.PropertyHalListener {public void setProperty(CarPropertyValue prop, ICarPropertyEventListener listener)throws IllegalArgumentException, ServiceSpecificException {int propId = prop.getPropertyId();...runSyncOperationCheckLimit(() -> {mHal.setProperty(prop);return null;});IBinder listenerBinder = listener.asBinder();synchronized (mLock) {Client client = mClientMap.get(listenerBinder);if (client == null) {client = new Client(listener);}if (client.isDead()) {Slogf.w(TAG, "the ICarPropertyEventListener is already dead");return;}mClientMap.put(listenerBinder, client);updateSetOperationRecorderLocked(propId, prop.getAreaId(), client);}}...
}
继续分发到 VehicleHal 侧。
public class PropertyHalService extends HalServiceBase {public void setProperty(CarPropertyValue prop)throws IllegalArgumentException, ServiceSpecificException {int halPropId = managerToHalPropId(prop.getPropertyId());...HalPropValue halPropValue = mPropValueBuilder.build(prop, halPropId, propConfig);// CarPropertyManager catches and rethrows exception, no need to handle here.mVehicleHal.set(halPropValue);}...
}
后续一样的是通过 VehicleHal 到 HalClient,再到 VehicleStub,最后抵达 HAL。
public class VehicleHal implements HalClientCallback {...public void set(HalPropValue propValue)throws IllegalArgumentException, ServiceSpecificException {mHalClient.setValue(propValue);}
}final class HalClient {...public void setValue(HalPropValue propValue)throws IllegalArgumentException, ServiceSpecificException {ObjectWrapper<String> errorMsgWrapper = new ObjectWrapper<>();errorMsgWrapper.object = new String();int status = invokeRetriable(() -> {try {mVehicle.set(propValue);errorMsgWrapper.object = new String();return StatusCode.OK;}...}, mWaitCapMs, mSleepMs);...}...
}public abstract class VehicleStub {...public abstract void set(HalPropValue propValue)throws RemoteException, ServiceSpecificException;...
}
结语

结合一张图回顾下整个过程:
- App 先通过 Car lib 拿到
CarService的Car实例,CarService 会初始化所有 Car 相关的实现,比如其中车辆属性的化,会初始化CarPropertyService和PropertyHalService等 - 接着,App 会从 Car 实例获取车辆某个接口的实例,比如控制车辆属性的话,需要获取
CarPropertyManager,CarService 则会从初始化完成的 map 里返回已准备好的对应对象 - App 的属性读写会通过 AIDL 接口抵达直接负责的 CarPropertyService,然后到与 HAL 中车辆属性模块交互的
PropertyHalService,再到综合的VehicleHal,最后通过 HIDL 接口抵达以及更下面的Hal,并按照定义的数据类型更改 ECU 的相关属性
希望本文能言简意赅地带你了解车辆属性的大体全貌,感谢阅读。
推荐阅读
- 一文了解 Android 车机如何处理中控的旋钮输入
- 从实体按键看 Android 车机的自定义事件机制
- 深度入门 Android 车机核心 CarService 的构成和链路
- Android 车机初体验:Auto,Automotive 傻傻分不清楚?
参考资料
- CarPropertyManager
相关文章:
从车窗升降一探 Android 车机的重要 API:车辆属性 CarProperty
前言 前面我们介绍过 Android 车机 Automotive OS 的几块重要内容: 一文了解 Android 车机如何处理中控的旋钮输入从实体按键看 Android 车机的自定义事件机制深度入门 Android 车机核心 CarService 的构成和链路 本篇文章我们聚焦 Android 车机上最重要、最常用…...
Unity读取写入Excel
1.在Plugins中放入dll,118开头的dll在Unity安装目录下(C:\Program Files\Unity\Editor\Data\Mono\lib\mono\unity) 2.写Excel public void WriteExcel(){//文件地址FileInfo newFile new FileInfo(Application.dataPath "/test.xlsx…...
手搭手Ajax经典基础案例省市联动
环境介绍 技术栈 springbootmybatis-plusmysql 软件 版本 mysql 8 IDEA IntelliJ IDEA 2022.2.1 JDK 1.8 Spring Boot 2.7.13 mybatis-plus 3.5.3.2 pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http:/…...
分类预测 | MATLAB实现SSA-CNN-BiLSTM-Attention数据分类预测(SE注意力机制)
分类预测 | MATLAB实现SSA-CNN-BiLSTM-Attention数据分类预测(SE注意力机制) 目录 分类预测 | MATLAB实现SSA-CNN-BiLSTM-Attention数据分类预测(SE注意力机制)分类效果基本描述模型描述程序设计参考资料 分类效果 基本描述 1.MAT…...
Springboot后端开发_日志
SpringBoot_日志 简介1、日志框架2、SLF4j使用1、如何在系统中使用SLF4j https://www.slf4j.org2、遗留问题 3、SpringBoot日志关系4、日志使用1、默认配置2、指定配置 5、切换日志框架拓展:日志分组 简介 6 种日志级别 TRACE: designates finer-grained informat…...
Unable to connect to the server: x509: certificate is valid for问题解决
文章目录 环境描述问题描述问题原因解决方案额外问题问题描述问题解决方案新问题 环境描述 Kubernetes版本1.15测试客户端centos7 问题描述 将构建于内网网络环境上的kubernetes集群的/etc/kubernetes/admin.conf文件拷贝到外网的一台装有kubernetes客户端的设备上ÿ…...
使用vite搭建前端项目
1、在vscode 终端那里执行创建前端工程项目,其中shop-admin为项目名称: npm init vite-app shop-admin 提示如需安装其他依赖执行npm install ....,否则忽略(第三步再讲)。 2、执行npm run dev 命令直接运行创建好的项目,在浏览器打开链接…...
leetcode1658. 将 x 减到 0 的最小操作数
题目链接:1658. 将 x 减到 0 的最小操作数 - 力扣(LeetCode) 知道滑动窗口,代码却写不出来 #define MIN(a ,b) ((a) < (b) ? (a) : (b))int minOperations(int* nums, int numsSize, int x) {int ans INT_MAX;int sum 0;f…...
Jenkins 重新定义 pom 内容,打包
文章目录 源码管理构建 源码管理 添加仓库地址,拉取凭证,选择需要的分支 构建 勾选 构建环境 下删除原始 build 配置,防止文件错误 Pre Steps 构建前处理 pom.xml ,例如我是需要删除该模块的所有子模块配置,我这里…...
自然语言处理---Transformer构建语言模型
语言模型概述 以一个符合语言规律的序列为输入,模型将利用序列间关系等特征,输出一个在所有词汇上的概率分布,这样的模型称为语言模型。 # 语言模型的训练语料一般来自于文章,对应的源文本和目标文本形如: src1 "I can do&…...
【WPF】对Image元素进行缩放平移等操作
元素布局 <Border Grid.Row"1" Name"border" ClipToBounds"True" Margin"10,10,10,10"><Image Name"image" Visibility"Visible" Margin"3,3,3,3" Grid.Column"1" Source"{Bin…...
JavaScript中Bom节点和表单的获取值
Bom节点 代表浏览器对象模型(Browser Object Model),它是浏览器提供的 JavaScript API,用于与浏览器窗口和浏览器本身进行交互 获取当前网页的URL: const currentURL window.location.href; console.log(currentURL…...
RDB.js:适用于 Node.js 和 Typescript 的终极对象关系映射器
RDB.js 是适用于 Node.js 和 Typescript 的终极对象关系映射器,可与 Postgres、MS SQL、MySQL、Sybase SAP 和 SQLite 等流行数据库无缝集成。无论您是使用 TypeScript 还是 JavaScript(包括 CommonJS 和 ECMAScript)构建应用程序,…...
ROI的投入产出比是什么?
ROI的投入产出比是什么? 投入产出比(Return on Investment, ROI)是一种评估投资效益的财务指标,用于衡量投资带来的回报与投入成本之间的关系。它的计算公式如下: 投资收益:指的是投资带来的净收入&#x…...
Linux打包发布常用命令
1、先下载一个FileZilla Client远程连接工具,并连接我们需要连接的服务器 2、进入xshell连接对应的服务器,连接后若不知道项目位置,可使用此命令查看 ps -ef | grep java 此时会出现一大串代码,找到以我这为例:root…...
Docker Swarm 节点维护
Docker Swarm Mode Docker Swarm 集群搭建 Docker Swarm 节点维护 Docker Service 创建 1.角色转换 Swarm 集群中节点的角色只有 manager 与 worker,所以其角色也只是在 manager 与worker 间的转换。即 worker 升级为 manager,或 manager 降级为 worke…...
AS/NZS 1859.3:2017 木基装饰板检测
木基装饰板是指以木质材料为基材,比如刨花板,胶合板等木质人造板,表面贴有PVC膜,三聚氰胺纸,木饰面等装饰层压制而成的木质复合材料,主要用于墙面装饰,家具等领域。 AS/NZS 1859.3:…...
深入理解算法:从基础到实践
深入理解算法:从基础到实践 1. 算法的定义2. 算法的特性3. 算法的分类按解决问题的性质分类:按算法的设计思路分类: 4. 算法分析5. 算法示例a. 搜索算法示例:二分搜索b. 排序算法示例:快速排序c. 动态规划示例…...
华为OD 机智的外卖员(100分)【java】A卷+B卷
华为OD统一考试A卷+B卷 新题库说明 你收到的链接上面会标注A卷还是B卷。目前大部分收到的都是B卷。 B卷对应20022部分考题以及新出的题目,A卷对应的是新出的题目。 我将持续更新最新题目 获取更多免费题目可前往夸克网盘下载,请点击以下链接进入: 我用夸克网盘分享了「华为O…...
Node编写用户登录接口
目录 前言 服务器 编写登录接口API 使用sql语句查询数据库中是否有该用户 判断密码是否正确 生成JWT的Token字符串 配置解析token的中间件 配置捕获错误中间件 完整的登录接口代码 前言 本文介绍如何使用node编写登录接口以及解密生成token,如何编写注册接…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...
聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...
ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...
html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...
SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)
上一章用到了V2 的概念,其实 Fiori当中还有 V4,咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务),代理中间件(ui5-middleware-simpleproxy)-CSDN博客…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...
Chrome 浏览器前端与客户端双向通信实战
Chrome 前端(即页面 JS / Web UI)与客户端(C 后端)的交互机制,是 Chromium 架构中非常核心的一环。下面我将按常见场景,从通道、流程、技术栈几个角度做一套完整的分析,特别适合你这种在分析和改…...
