health@1.0:android.hardware.health@1.0 的缩写。指的是 Android 8.0 中发布的运行状况 HIDL 的 1.0 版 HAL。

Android 8.x 中的“运行状况”服务

在 Android 8.x 中,运行状况组件的工作原理详情如下图所示:


在此图中:

  1. 框架通过一次 Binder 调用和一次 hwbinder 调用与硬件进行通信。
  2. healthd 静态关联到 libhealthd_android、libbatterymonitor 和 libbatteryservice。
  3. health@1.0-impl 静态关联到 libhealthd.BOARD。

每个开发板都可以自定义不同的 libhealthd.BOARD;charger、health@1.0-impl 和 recovery 关联到哪个开发板是在构建时确定的。

对于其他模式:


charger 静态关联到 libhealthd.BOARD、libhealthd_charger 和 libbatterymonitor。

recovery 静态关联到 libhealthd.BOARD 和 libbatterymonitor。

health@1.0 HAL 中的定义:

hardware/interfaces/health/1.0/types.hal

package android.hardware.health@1.0;/*** Possible return values for optional HAL method(s) like* IHealth::energyCounter()* 可选HAL方法的可能返回值*/
enum Result : int32_t {SUCCESS,NOT_SUPPORTED,UNKNOWN,
};/*** Possible values for Battery Status.* “电池状态”的取值范围。* Note: These are currently in sync with BatteryManager and must not* be extended / altered.* 注意:这些目前与 BatteryManager 同步,不能扩展/更改。*/
@export(name="", value_prefix="BATTERY_STATUS_")
enum BatteryStatus : int32_t {UNKNOWN = 1,CHARGING = 2,DISCHARGING = 3,/*** Battery is *not* charging - special case when charger is present* but battery isn't charging*/NOT_CHARGING = 4,FULL = 5,
};/*** Possible values for Battery Health.* 电池运行状况的可能值。* Note: These are currently in sync with BatteryManager and must not* be extended / altered.* 注意:这些目前与 BatteryManager 同步,不能扩展/更改。*/
@export(name="", value_prefix="BATTERY_HEALTH_")
enum BatteryHealth : int32_t {UNKNOWN = 1,GOOD = 2,OVERHEAT = 3,DEAD = 4,OVER_VOLTAGE = 5,/*** Battery experienced an unknown/unspecifid failure.*/UNSPECIFIED_FAILURE = 6,COLD = 7,
};struct HealthConfig {/*** periodicChoresIntervalFast is used while the device is not in* suspend, or in suspend and connected to a charger (to watch for battery* overheat due to charging)* 当设备不处于挂起状态或挂起并连接到充电器时使用 periodicChoresIntervalFast* (观察电池因充电而过热)*/int32_t periodicChoresIntervalFast;/*** periodicChoresIntervalSlow is used when the device is in suspend and* not connected to a charger (to watch for a battery drained to zero* remaining capacity).* 当设备处于挂起状态且未连接充电器时,* 使用 periodicChoresIntervalSlow (观察电池耗尽至剩余容量为零)。*/int32_t periodicChoresIntervalSlow;/*** power_supply sysfs attribute file paths. Set these to specific paths* to use for the associated battery parameters. Clients must search* for appropriate power_supply attribute files to use, for any paths* left empty after the HAL is initialized.*//*** batteryStatusPath - file path to read battery charging status.* 读取电池充电状态的文件路径。* (POWER_SUPPLY_PROP_STATUS)*/string batteryStatusPath;/*** batteryHealthPath - file path to read battery health.* 读取电池运行状况的文件路径。* (POWER_SUPPLY_PROP_HEALTH)*/string batteryHealthPath;/*** batteryPresentPath - file path to read battery present status.* 读取电池当前状态的文件路径。* (POWER_SUPPLY_PROP_PRESENT)*/string batteryPresentPath;/*** batteryCapacityPath - file path to read remaining battery capacity.* 读取剩余电池容量的文件路径。* (POWER_SUPPLY_PROP_CAPACITY)*/string batteryCapacityPath;/*** batteryVoltagePath - file path to read battery voltage.* 读取电池电压的文件路径。* (POWER_SUPPLY_PROP_VOLTAGE_NOW)*/string batteryVoltagePath;/*** batteryTemperaturePath - file path to read battery temperature in tenths* of degree celcius. (POWER_SUPPLY_PROP_TEMP)* 读取电池温度摄氏度乘以10后的文件路径。*/string batteryTemperaturePath;/*** batteryTechnologyPath - file path to read battery technology.* 读取电池技术的文件路径。* (POWER_SUPPLY_PROP_TECHNOLOGY)*/string batteryTechnologyPath;/*** batteryCurrentNowPath - file path to read battery instantaneous current.* 读取电池瞬时电流的文件路径。* (POWER_SUPPLY_PROP_CURRENT_NOW)*/string batteryCurrentNowPath;/*** batteryCurrentAvgPath - file path to read battery average current.* 读取电池平均电流的文件路径。* (POWER_SUPPLY_PROP_CURRENT_AVG)*/string batteryCurrentAvgPath;/*** batteryChargeCounterPath - file path to read battery accumulated charge.* 读取电池累计充电电量的文件路径。* (POWER_SUPPLY_PROP_CHARGE_COUNTER)*/string batteryChargeCounterPath;/*** batteryFullChargerPath - file path to read battery charge value when it* is considered to be full. (POWER_SUPPLY_PROP_CHARGE_FULL)* 读取电池充满时的对应值文件路径。*/string batteryFullChargePath;/*** batteryCycleCountPath - file path to read battery charge cycle count.* 读取电池充电周期计数文件路径。* (POWER_SUPPLY_PROP_CYCLE_COUNT)*/string batteryCycleCountPath;
};/*** The parameter to healthd mainloop update calls* healthd 主循环更新调用的参数*/
struct HealthInfo {/** AC charger state - 'true' if online */ /** 交流充电状态 */bool chargerAcOnline;/** USB charger state - 'true' if online */ /** USB 充电状态 */bool chargerUsbOnline;/** Wireless charger state - 'true' if online */ /** 无线充电状态 */bool chargerWirelessOnline;/** Maximum charging current supported by charger in uA *//** 充电器支持的最大充电电流(uA) */int32_t maxChargingCurrent;/** Maximum charging voltage supported by charger in uV *//** 充电器支持的最大充电电压(uV) */int32_t maxChargingVoltage;BatteryStatus batteryStatus;BatteryHealth batteryHealth;/** 'true' if battery is present *//** 如果有电池,则为 true */bool batteryPresent;/** Remaining battery capacity in percent *//** 剩余电池容量百分比 */int32_t batteryLevel;/*** Instantaneous battery voltage in millivolts (mV).* 瞬间电池电压(毫伏)。** Historically, the unit of this field is microvolts (uV), but all* clients and implementations uses millivolts in practice, making it* the de-facto standard.*/int32_t batteryVoltage;/** Instantaneous battery temperature in tenths of degree celcius *//** 瞬间电池温度 */int32_t batteryTemperature;/** Instantaneous battery current in uA *//** 瞬时电池电流(uA) */int32_t batteryCurrent;/** Battery charge cycle count *//** 电池充电周期计数 */int32_t batteryCycleCount;/** Battery charge value when it is considered to be "full" in uA-h *//** 当认为电池“满”时的电池充电值(uA-h) */int32_t batteryFullCharge;/** Instantaneous battery capacity in uA-h *//** 瞬时电池容量(uA-h) */int32_t batteryChargeCounter;/** Battery technology, e.g. "Li-ion, Li-Poly" etc. *//** 电池技术 */string batteryTechnology;
};

hardware/interfaces/health/1.0/IHealth.hal

package android.hardware.health@1.0;interface IHealth {/*** This function lets you change healthd configuration from default if* desired. It must be called exactly once at startup time.* 如果需要,这个功能可以让你改变默认的 healthd 配置。它必须在启动时调用一次。** The configuration values are described in 'struct HealthConfig'.* To use default configuration, simply return without modifying the* fields of the config parameter.* 配置值在“struct HealthConfig”中描述。* 要使用默认配置,只需返回而不修改 config 参数的字段。** @param default healthd configuration.*/init(HealthConfig config) generates (HealthConfig configOut);/*** This function is a hook to update/change device's HealthInfo (as described* in 'struct HealthInfo').* 这个函数是一个 hook 来更新/改变设备的 HealthInfo** 'HealthInfo' describes device's battery and charging status, typically* read from kernel. These values may be modified in this call.* “HealthInfo”描述设备的电池和充电状态,通常从内核读取。* 这些值可以在这个调用中修改。** @param   Device Health info as described in 'struct HealthInfo'.* @return  skipLogging Indication to the caller to add 'or' skip logging the health*          information. Return 'true' to skip logging the update.* @return  infoOut HealthInfo to be sent to client code. (May or may*          not be modified).*/update(HealthInfo info) generates (bool skipLogging, HealthInfo infoOut);/*** This function is called by healthd when framework queries for remaining* energy in the Battery through BatteryManager APIs.* 当框架通过 BatteryManager API 查询电池中的剩余电量时,* healthd 会调用这个函数。** @return  result Result of querying enery counter for the battery.* @return  energy Battery remaining energy in nanowatt-hours.*          Must be '0' if result is anything other than Result::SUCCESS.*/energyCounter() generates (Result result, int64_t energy);
};

现在重点来分析 hardware/interfaces/health/1.0/default/ 下的实现。Android.mk 中编译了两个模块,分别为 android.hardware.health@1.0-impl 和 android.hardware.health@1.0-service(可执行文件)。android.hardware.health@1.0-impl 主要使用了 Health.cpp 源文件,android.hardware.health@1.0-service 则使用了 HealthService.cpp 源文件。

hardware/interfaces/health/1.0/default/Android.mk

LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)
LOCAL_MODULE := android.hardware.health@1.0-impl
LOCAL_PROPRIETARY_MODULE := true
LOCAL_MODULE_RELATIVE_PATH := hw
LOCAL_C_INCLUDES := system/core/base/include
LOCAL_SRC_FILES := \Health.cpp \LOCAL_HEADER_LIBRARIES := libhealthd_headersLOCAL_SHARED_LIBRARIES := \libcutils \libhidlbase \libhidltransport \liblog \libutils \android.hardware.health@1.0 \LOCAL_STATIC_LIBRARIES := android.hardware.health@1.0-convertLOCAL_HAL_STATIC_LIBRARIES := libhealthdinclude $(BUILD_SHARED_LIBRARY)include $(CLEAR_VARS)
LOCAL_PROPRIETARY_MODULE := true
LOCAL_MODULE_RELATIVE_PATH := hw
LOCAL_MODULE := android.hardware.health@1.0-service
LOCAL_INIT_RC := android.hardware.health@1.0-service.rc
LOCAL_SRC_FILES := \HealthService.cpp \LOCAL_SHARED_LIBRARIES := \liblog \libcutils \libdl \libbase \libutils \libhidlbase \libhidltransport \android.hardware.health@1.0 \include $(BUILD_EXECUTABLE)include $(call first-makefiles-under,$(LOCAL_PATH))

android.hardware.health@1.0-impl 包含了 HAL 的实现以及一个导出函数 HIDL_FETCH_IHealth(…) ,HIDL_FETCH_IHealth(…) 获取 HAL 实现的对象,只是 new 了一个 Health 结构体返回。

Health::init(…) 实现主要流程:

  1. 创建一个 healthd_config struct;
  2. 为了使得 healthd 静态 HAL 工作正常,调用 convertFromHealthConfig(…) 将 HealthConfig 转换为 healthd_config;
  3. 调用 healthd_board_init(…) 进一步板级初始化;
  4. 给 mGetEnergyCounter 字段赋值;
  5. 调用 convertToHealthConfig(…) 再将 healthd_config 转换为 HealthConfig 结构,并返回。

Health::update(…) 实现主要流程:

  1. 创建 android::BatteryProperties 结构;
  2. 调用 convertFromHealthInfo(…) 将 HealthInfo 转为 android::Batteryproperties;
  3. 调用板级更新 healthd_board_battery_update(…);
  4. 调用 convertToHealthInfo(…) 将 android::Batteryproperties 转为 HealthInfo。

Health::energyCounter(…) 实现非常简单主要调用了 mGetEnergyCounter 指向的函数指针获取剩余电量。

hardware/interfaces/health/1.0/default/Health.cpp

using ::android::hardware::health::V1_0::hal_conversion::convertToHealthConfig;
using ::android::hardware::health::V1_0::hal_conversion::convertFromHealthConfig;
using ::android::hardware::health::V1_0::hal_conversion::convertToHealthInfo;
using ::android::hardware::health::V1_0::hal_conversion::convertFromHealthInfo;// Methods from ::android::hardware::health::V1_0::IHealth follow.
Return<void> Health::init(const HealthConfig& config, init_cb _hidl_cb)  {struct healthd_config healthd_config = {};HealthConfig configOut;// To keep working with existing healthd static HALs,// convert the new HealthConfig to the old healthd_config// and back.convertFromHealthConfig(config, &healthd_config);healthd_board_init(&healthd_config);mGetEnergyCounter = healthd_config.energyCounter;convertToHealthConfig(&healthd_config, configOut);_hidl_cb(configOut);return Void();
}Return<void> Health::update(const HealthInfo& info, update_cb _hidl_cb)  {struct android::BatteryProperties p = {};HealthInfo infoOut;// To keep working with existing healthd static HALs,// convert the new HealthInfo to android::Batteryproperties// and back.convertFromHealthInfo(info, &p);int skipLogging = healthd_board_battery_update(&p);convertToHealthInfo(&p, infoOut);_hidl_cb(!!skipLogging, infoOut);return Void();
}Return<void> Health::energyCounter(energyCounter_cb _hidl_cb) {int64_t energy = 0;Result result = Result::NOT_SUPPORTED;if (mGetEnergyCounter) {int status = mGetEnergyCounter(&energy);if (status == 0) {result = Result::SUCCESS;}}_hidl_cb(result, energy);return Void();
}IHealth* HIDL_FETCH_IHealth(const char* /* name */) {return new Health();
}

上面提到的转换适配函数定义在 convert.cpp 中,实现了 healthd_config 和 HealthConfig 互转,
BatteryProperties 和 HealthInfo 互转。

hardware/interfaces/health/1.0/default/convert.cpp

void convertToHealthConfig(const struct healthd_config *hc, HealthConfig& config) {config.periodicChoresIntervalFast = hc->periodic_chores_interval_fast;config.periodicChoresIntervalSlow = hc->periodic_chores_interval_slow;config.batteryStatusPath        = hc->batteryStatusPath.string();config.batteryHealthPath        = hc->batteryHealthPath.string();config.batteryPresentPath       = hc->batteryPresentPath.string();config.batteryCapacityPath      = hc->batteryCapacityPath.string();config.batteryVoltagePath       = hc->batteryVoltagePath.string();config.batteryTemperaturePath   = hc->batteryTemperaturePath.string();config.batteryTechnologyPath    = hc->batteryTechnologyPath.string();config.batteryCurrentNowPath    = hc->batteryCurrentNowPath.string();config.batteryCurrentAvgPath    = hc->batteryCurrentAvgPath.string();config.batteryChargeCounterPath = hc->batteryChargeCounterPath.string();config.batteryFullChargePath    = hc->batteryFullChargePath.string();config.batteryCycleCountPath    = hc->batteryCycleCountPath.string();}void convertFromHealthConfig(const HealthConfig& c, struct healthd_config *hc) {hc->periodic_chores_interval_fast = c.periodicChoresIntervalFast;hc->periodic_chores_interval_slow = c.periodicChoresIntervalSlow;hc->batteryStatusPath =android::String8(c.batteryStatusPath.c_str(),c.batteryStatusPath.size());hc->batteryHealthPath =android::String8(c.batteryHealthPath.c_str(),c.batteryHealthPath.size());hc->batteryPresentPath =android::String8(c.batteryPresentPath.c_str(),c.batteryPresentPath.size());hc->batteryCapacityPath =android::String8(c.batteryCapacityPath.c_str(),c.batteryCapacityPath.size());hc->batteryVoltagePath =android::String8(c.batteryVoltagePath.c_str(),c.batteryVoltagePath.size());hc->batteryTemperaturePath =android::String8(c.batteryTemperaturePath.c_str(),c.batteryTemperaturePath.size());hc->batteryTechnologyPath =android::String8(c.batteryTechnologyPath.c_str(),c.batteryTechnologyPath.size());hc->batteryCurrentNowPath =android::String8(c.batteryCurrentNowPath.c_str(),c.batteryCurrentNowPath.size());hc->batteryCurrentAvgPath =android::String8(c.batteryCurrentAvgPath.c_str(),c.batteryCurrentNowPath.size());hc->batteryChargeCounterPath =android::String8(c.batteryChargeCounterPath.c_str(),c.batteryChargeCounterPath.size());hc->batteryFullChargePath =android::String8(c.batteryFullChargePath.c_str(),c.batteryFullChargePath.size());hc->batteryCycleCountPath =android::String8(c.batteryCycleCountPath.c_str(),c.batteryCycleCountPath.size());// energyCounter is handled through special means so all calls to// the function go across the HALs// boot_min_cap - never used in Android (only in charger-mode).// screen_on - never used in Android (only in charger mode).
}void convertToHealthInfo(const struct android::BatteryProperties *p,HealthInfo& info) {info.chargerAcOnline        = p->chargerAcOnline;info.chargerUsbOnline       = p->chargerUsbOnline;info.chargerWirelessOnline  = p->chargerWirelessOnline;info.maxChargingCurrent     = p->maxChargingCurrent;info.maxChargingVoltage     = p->maxChargingVoltage;info.batteryStatus          = static_cast<BatteryStatus>(p->batteryStatus);info.batteryHealth          = static_cast<BatteryHealth>(p->batteryHealth);info.batteryPresent         = p->batteryPresent;info.batteryLevel           = p->batteryLevel;info.batteryVoltage         = p->batteryVoltage;info.batteryTemperature     = p->batteryTemperature;info.batteryCurrent         = p->batteryCurrent;info.batteryCycleCount      = p->batteryCycleCount;info.batteryFullCharge      = p->batteryFullCharge;info.batteryChargeCounter   = p->batteryChargeCounter;info.batteryTechnology      = p->batteryTechnology;
}void convertFromHealthInfo(const HealthInfo& info,struct android::BatteryProperties *p) {p->chargerAcOnline          = info.chargerAcOnline;p->chargerUsbOnline         = info.chargerUsbOnline;p->chargerWirelessOnline    = info.chargerWirelessOnline;p->maxChargingCurrent       = info.maxChargingCurrent;p->maxChargingVoltage       = info.maxChargingVoltage;p->batteryStatus            = static_cast<int>(info.batteryStatus);p->batteryHealth            = static_cast<int>(info.batteryHealth);p->batteryPresent           = info.batteryPresent;p->batteryLevel             = info.batteryLevel;p->batteryVoltage           = info.batteryVoltage;p->batteryTemperature       = info.batteryTemperature;p->batteryCurrent           = info.batteryCurrent;p->batteryCycleCount        = info.batteryCycleCount;p->batteryFullCharge        = info.batteryFullCharge;p->batteryChargeCounter     = info.batteryChargeCounter;p->batteryTechnology        = android::String8(info.batteryTechnology.c_str());
}

libhealthd 模块 Android.bp 编译主要使用了 healthd_board_default.cpp。

hardware/interfaces/health/1.0/default/libhealthd/Android.bp

cc_library_static {srcs: ["healthd_board_default.cpp"],name: "libhealthd.default",vendor_available: true,recovery_available: true,cflags: ["-Werror"],include_dirs: ["system/core/base/include"],header_libs: ["libhealthd_headers"],
}

healthd_board_init(…) 和 healthd_board_battery_update(…) 仅仅都是空实现。

hardware/interfaces/health/1.0/default/libhealthd/healthd_board_default.cpp

void healthd_board_init(struct healthd_config*)
{// use defaults
}int healthd_board_battery_update(struct android::BatteryProperties*)
{// return 0 to log periodic polled battery status to kernel logreturn 0;
}

android.hardware.health@1.0-service 实现了 HAL service。主要使用了 HealthService.cpp 源文件和,关联了 android.hardware.health@1.0-service.rc 文件。

Binderized 绑定式可以用两种方式来绑定服务,第一种通过 defaultPassthroughServiceImplementation 调用来注册服务,另外一种是直接调用 RegisterAsService 来注册服务。此处显然是通过第一种。

hardware/interfaces/health/1.0/default/HealthService.cpp

using android::hardware::health::V1_0::IHealth;
using android::hardware::defaultPassthroughServiceImplementation;int main() {return defaultPassthroughServiceImplementation<IHealth>();
}

rc 文件中,class 分类为 hal,user 用户是 system,group 组也是 system,capabilities 能力则是 WAKE_ALARM,表明可以闹钟唤醒。系统启动后 vendor.health-hal-1-0 Service 将会注册到系统。

hardware/interfaces/health/1.0/default/android.hardware.health@1.0-service.rc

service vendor.health-hal-1-0 /vendor/bin/hw/android.hardware.health@1.0-serviceclass haluser systemgroup systemcapabilities WAKE_ALARM

Android.bp 文件中编译了 android.hardware.health@1.0-convert 静态库,它主要使用了 convert.cpp 文件。

hardware/interfaces/health/1.0/default/Android.bp

cc_library_static {name: "android.hardware.health@1.0-convert",vendor_available: true,recovery_available: true,srcs: ["convert.cpp"],include_dirs: ["system/core/base/include",],header_libs: ["libhealthd_headers"],export_header_lib_headers: ["libhealthd_headers"],export_include_dirs: ["include"],shared_libs: ["libcutils","libhidlbase","libhidltransport","libutils","android.hardware.health@1.0",],}

【Android 10 源码】healthd 模块 HAL 1.0 分析相关推荐

  1. 【Android 10 源码】healthd 模块 HAL 2.0 分析

    Android 9 引入了从 health@1.0 HAL 升级的主要版本 android.hardware.health HAL 2.0.这一新 HAL 具有以下优势: 框架代码和供应商代码之间的区 ...

  2. Ubuntu18.04 编译Android 10源码 并烧录源码到pixel3的避坑指南

    Ubuntu18.04 编译Android 10源码 并烧录源码到pixel3的避坑指南 实验环境 下载Android源码树 在pixel3上安装手机驱动版本 编译Android源码 Android ...

  3. 【Android 10 源码】深入理解 software Codec2 服务启动

    MediaCodec 系列文章: [Android 10 源码]深入理解 MediaCodec 硬解码初始化 [Android 10 源码]深入理解 Omx 初始化 [Android 10 源码]深入 ...

  4. 【Android 10 源码】深入理解 MediaCodec 组件分配

    MediaCodec 系列文章: [Android 10 源码]深入理解 MediaCodec 硬解码初始化 [Android 10 源码]深入理解 Omx 初始化 [Android 10 源码]深入 ...

  5. 【Android 10 源码】深入理解 Omx 初始化

    MediaCodec 系列文章: [Android 10 源码]深入理解 MediaCodec 硬解码初始化 [Android 10 源码]深入理解 Omx 初始化 [Android 10 源码]深入 ...

  6. 【Android 10 源码】MediaRecorder 录像流程:MediaRecorder 初始化

    MediaRecorder 用于录制音频和视频,录制控制基于一个简单的状态机.下面是典型的使用 camera2 API 录制,需要使用到的 MediaRecorder API. MediaRecord ...

  7. 【Android 10 源码】MediaRecorder 录像流程:MediaRecorder 配置

    MediaRecorder 录像配置主要涉及输出文件路径.音频来源.视频来源.输出格式.音频编码格式.视频编码格式.比特率.帧率和视频尺寸等. 我们假设视频输入源来自 Camera,Camera2 A ...

  8. 【Android 10 源码】Camera v1 startPreview 流程

    Camera v1 startPreview 起点位于 android.hardware 包下的 Camera 类中,这是老版本的 Camera 预览的起点. 上面这张相机架构图左边就是关于 Came ...

  9. 【Android 10 源码】healthd 模块 BatteryService 初始化

    BatteryService 是在 SystemServer 中启动的,BatteryService 监控设备电池的充电状态和充电水平.当这些值改变时,这个服务会将这些新值广播给所有正在监听 ACTI ...

最新文章

  1. SecureRandom
  2. python服务端多进程压测工具
  3. 软件工程结对作业 四则运算界面设计
  4. QTP的Action之间传递参数
  5. 如何处理CRM_ORGMAN 300 error message
  6. P1616 疯狂的采药(python3实现)--80分
  7. 各类 HTTP 返回状态代码详解
  8. 计算两个经纬度的距离_HTML5 地理定位+地图 API:计算用户到商家的距离
  9. 机器阅读理解(MRC)和问答(QA)在信息抽取中的应用
  10. Android完美适配dimens.xml脚本
  11. python异常类型大全
  12. winows+Eclipse下Mahout配置
  13. CoreOS为Kubernetes量身打造分布式存储方案Torus
  14. Python图像处理笔记——形态学处理(skimage.morphology)
  15. 多开分身苹果版_苹果手机如何同时登陆两个微信 iPhone微信多开教程
  16. markdown贴gif图片
  17. 升级win11后,觉得不好用想重装win10系统?教你重装win10“精简版”
  18. 求3000以内的全部亲密数。
  19. 关于eclipse中js文件没有提示代码的解决
  20. 王守仁——走对路才有出路

热门文章

  1. 华东师范计算机科学与技术 导师,钱莹 - 华东师范大学 - 计算机科学与技术学院...
  2. [UER #6]逃跑
  3. autoexec.bat文件的所在位置
  4. 前端面试题 Doctype作用是什么?严格模式与混杂模式如何区分?他们之间有何意义?
  5. 钉钉小程序的坑 么有开启通讯录权限,导致后台报错“没有调用该接口的权限”
  6. Luogu P1144 最短路计数
  7. python 字典计数_python怎么用字典计数
  8. 2018 Arab Collegiate Programming Contest (ACPC 2018) L.Looking for Taste(按位或)
  9. 普通用户强制修改root密码
  10. navicat 连接 oracle (最全解读)