文章目录

  • 前言
  • HIDL 简介
    • 启动流程
    • HIDL 使用
    • Jni 流程: HIDL 客户端使用
    • HIDL 服务端
  • 相关文件
    • 0.【Java 安卓LED服务类】LightsService.java
    • 1.【JNI 客户端实现】com_android_server_lights_LightsService.cpp
    • 2.【HIDL 客户】ILight.与 HIDL 服务库通信.hal
    • 3.【HIDL 客户】LightAll.cpp.调用 HIDL 服务库
    • 4.【HIDL 服务】android.hardware.light@2.0-service.启动注册 HIDL 服务库
    • 4.【HIDL 服务】service.cpp.向上注册 HIDL 服务库
    • 5.【HIDL 服务】Light.h
    • 6.【HIDL 服务】Light.cpp.加载调用传统库
    • 7.【HAL 实现】lights.c.传统 HAL 库
    • 8.【内核实现】leds-qpnp.c

前言

学习笔记,简单介绍了 light 在 Android 8.0 上的整个调用流程
更新上添加下调用流程线,将各个文件串起来

HIDL 简介

一张图开场

 其实主要是就是将以前那种 Java-> Jni -> Hal -> Kernel 的结构变成了Java -> Jni -> Binder 客户端 ====== Binder 通信 ======> Binder 服务端 -> Hal -> Kernel 将 framework 与 Hal 之间的交互变成了 CS 结构了。

插播下 Java Binder 服务编写框图:

C++ Binder 服务编写框图:

启动流程

 // SystemServer.java (frameworks\base\services\java\com\android\server)import com.android.server.lights.LightsService;startBootstrapServices()  # 此函数可参考 BatteryService 流程分析mSystemServiceManager.startService(LightsService.class);// SystemServiceManager.java (frameworks\base\services\core\java\com\android\server)startService(Class<T> serviceClass)# 获取类名final String name = serviceClass.getName();# 获得构造函数: public LightsService(Context context)Constructor<T> constructor = serviceClass.getConstructor(Context.class);# 调用构造函数,创建类对象service = constructor.newInstance(mContext);// LightsService.java (frameworks\base\services\core\java\com\android\server\lights)// 构造函数:LightsService(Context context)super(context);for (int i = 0; i < LightsManager.LIGHT_ID_COUNT; i++) {mLights[i] = new LightImpl(i);}# 添加到 SystemService 链表中管理,并启动服务startService(service);mServices.add(service);service.onStart();// LightsService.java (frameworks\base\services\core\java\com\android\server\lights)onStart()// 这是向 systemservice.java 注册了一些回调,只让其调用使用?// Publish the service so it is only accessible to the system process// 让其只能在 system 进程中访问?//      ./base/services/core/java/com/android/server/power/PowerManagerService.java:736:            //          mBatteryManagerInternal = getLocalService(BatteryManagerInternal.class);publishLocalService(LightsManager.class, mService);

HIDL 使用

// 接口文件:
//  Z:\work\A306_eng\src\hardware\interfaces\light\2.0\ILight.halpackage android.hardware.light@2.0;interface ILight {/*** Set the provided lights to the provided values.** @param type logical light to set* @param state describes what the light should look like.* @return status result of applying state transformation.*/setLight(Type type, LightState state) generates (Status status);/*** Discover what indicator lights are available.** @return types list of available lights*/getSupportedTypes() generates (vec<Type> types);};// 编译文件:
// Z:\work\A306_eng\src\hardware\interfaces\light\2.0\Android.bpfilegroup {name: "android.hardware.light@2.0_hal",srcs: ["types.hal","ILight.hal",],}genrule {name: "android.hardware.light@2.0_genc++",tools: ["hidl-gen"],cmd: "$(location hidl-gen) -o $(genDir) -Lc++-sources -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport android.hardware.light@2.0",srcs: [":android.hardware.light@2.0_hal",],out: ["android/hardware/light/2.0/types.cpp","android/hardware/light/2.0/LightAll.cpp",],}// 编译工具: out/host/linux-x86/bin/hidl-gen // 生成文件://      Z:\work\A306_eng\src\out\soong\.intermediates\hardware\interfaces\light\2.0\android.hardware.light@2.0_genc++\gen\android\hardware\light\2.0\LightAll.cppgetService(): 获取 servicemanager 中的对应 binder 服务 BnHwLight::onTransact()# 服务器端,调用具体子类提供服务操作硬件_hidl_mImpl->setLight(type, *state);BpHwLight::setLight(Type type, const LightState& state): # 客户端,提供远程调用服务端接口1. 填充 Parcel 数据2. 调用 remote() 通过 binder 调用到服务器端执行_hidl_err = remote()->transact(1 /* setLight */, _hidl_data, &_hidl_reply);

Jni 流程: HIDL 客户端使用

// com_android_server_lights_LightsService.cpp (frameworks\base\services\core\jni)
static void setLight_native( JNIEnv* /* env */, jobject /* clazz */, jint light, jint colorARGB, jint flashMode, jint onMS, jint offMS,jint brightnessMode) # HAL 服务获取: HIDL 接口,从 servicemanager 中获取 Light 服务sp<ILight> hal = LightHal::associate();// will return the hal if it exists the first time.sLight = ILight::getService();// Z:\work\A306_eng\src\out\soong\.intermediates\hardware\interfaces\light\2.0\android.hardware.light@2.0_genc++\gen\android\hardware\light\2.0\LightAll.cpp::android::sp<ILight> ILight::getService(const std::string &serviceName, const bool getStub) const sp<::android::hidl::manager::V1_0::IServiceManager> sm = defaultServiceManager();Return<Transport> transportRet = sm->getTransport(ILight::descriptor, serviceName);if (getStub || vintfPassthru || vintfLegacy) {# 获取 PassthroughServiceManager 服务 const sp<::android::hidl::manager::V1_0::IServiceManager> pm = getPassthroughServiceManager();Return<sp<::android::hidl::base::V1_0::IBase>> ret = pm->get(ILight::descriptor, serviceName);// ServiceManagement.cpp (system\libhidl\transport)struct PassthroughServiceManager : IServiceManager {Return<sp<IBase>> get(const hidl_string& fqName,const hidl_string& name){# PassthroughServiceManager 的 get(const hidl_string& fqName, const hidl_string& name)# 函数根据传入的 fqName=(android.hardware.cameraProvider@2.4::ICameraProvider"),# 获取当前的接口名ICameraProvider,#   拼接出后面需要载入的函数名:HIDL_FETCH_ICameraProvider 和库名字:#           android.hardware.camera.provider@2.4-impl.sostd::string stdFqName(fqName.c_str());//fqName looks like android.hardware.foo@1.0::IFoosize_t idx = stdFqName.find("::");if (idx == std::string::npos ||idx + strlen("::") + 1 >= stdFqName.size()) {LOG(ERROR) << "Invalid interface name passthrough lookup: " << fqName;return nullptr;}std::string packageAndVersion = stdFqName.substr(0, idx);std::string ifaceName = stdFqName.substr(idx + strlen("::"));const std::string prefix = packageAndVersion + "-impl";const std::string sym = "HIDL_FETCH_" + ifaceName;# 接着通过 dlopen 载入 /vendor/lib/hw/android.hardware.light@2.0-impl.so;#   通过 dlsym 载入 HIDL_FETCH_ICameraProvider 函数# 至此正式进入cameraprovider的implementstd::vector<std::string> libs = search(path, prefix, ".so");handle = dlopen(fullPath.c_str(), dlMode);IBase* (*generator)(const char* name);*(void **)(&generator) = dlsym(handle, sym.c_str());IBase *interface = (*generator)(name.c_str());################################################################## 调用 HIDL 服务入口函数#################################################################// Z:\work\A306_eng\src\hardware\interfaces\light\2.0\default\Light.cppILight* HIDL_FETCH_ILight(const char* /* name */) {std::map<Type, light_device_t*> lights;for(auto const &pair : kLogicalLights) {Type type = pair.first;const char* name = pair.second;light_device_t* light = getLightDevice(name);############################################## 老 HAL 库加载#############################################light_device_t* getLightDevice(const char* name)int ret = hw_get_module (LIGHTS_HARDWARE_MODULE_ID, &hwModule);// Lights.c (hardware\qcom\display\liblight)struct hw_module_t HAL_MODULE_INFO_SYM = {.tag = HARDWARE_MODULE_TAG,.version_major = 1,.version_minor = 0,.id = LIGHTS_HARDWARE_MODULE_ID,.name = "lights Module",.author = "Google, Inc.",.methods = &lights_module_methods,};static struct hw_module_methods_t lights_module_methods = {.open =  open_lights,};ret = hwModule->methods->open(hwModule, name,reinterpret_cast<hw_device_t**>(&lightDevice));// Light.cpp (vendor\qcom\proprietary\fastmmi\module\light)open_lights(const struct hw_module_t* module, char const* name,struct hw_device_t** device)set_light = set_light_notifications;handle_speaker_battery_locked(dev);set_speaker_light_locked(dev, &g_notification);// wangjun@wind-mobi.com 20180111 begein// 闪烁之前清灯状态write_int(RED_LED_FILE, 0);write_int(GREEN_LED_FILE, 0);write_int(BLUE_LED_FILE, 0);# 往 /sys/class/leds/green/brightness 这类的结点写亮度值fd = open(path, O_RDWR);int bytes = snprintf(buffer, sizeof(buffer), "%d\n", value);ssize_t amt = write(fd, buffer, (size_t)bytes);close(fd);// wangjun@wind-mobi.com 20180111 endif (light != nullptr) {lights[type] = light;}}if (lights.size() == 0) {// Log information, but still return new Light.// Some devices may not have any lights.ALOGI("Could not open any lights.");}return new Light(std::move(lights));}Type type = static_cast<Type>(light);// 构造 LightState 状态类 LightState state = constructState( colorARGB, flashMode, onMS, offMS, brightnessMode);ALOGD_IF_SLOW(50, "Excessive delay setting light");# HIDL 接口向 HAL 下发灯状态给他处理Return<Status> ret = hal->setLight(type, state);# 处理通信返回值processReturn(ret, type, state);

HIDL 服务端

// Z:\work\A306_eng\src\hardware\interfaces\light\2.0\default\Android.mkinclude $(CLEAR_VARS)LOCAL_MODULE_RELATIVE_PATH := hwLOCAL_PROPRIETARY_MODULE := trueLOCAL_MODULE := android.hardware.light@2.0-serviceLOCAL_INIT_RC := android.hardware.light@2.0-service.rcLOCAL_SRC_FILES := \service.cpp \LOCAL_SHARED_LIBRARIES := \liblog \libcutils \libdl \libbase \libutils \libhardware \LOCAL_SHARED_LIBRARIES += \libhidlbase \libhidltransport \android.hardware.light@2.0 \include $(BUILD_EXECUTABLE)// 服务启动:// Z:\work\A306_eng\src\hardware\interfaces\light\2.0\default\android.hardware.light@2.0-service.rcservice light-hal-2-0 /vendor/bin/hw/android.hardware.light@2.0-serviceclass haluser systemgroup system    // Z:\work\A306_eng\src\hardware\interfaces\light\2.0\default\service.cppint main() {return defaultPassthroughServiceImplementation<ILight>();/// LegacySupport.h (system\libhidl\transport\include\hidl)defaultPassthroughServiceImplementation(std::string name, size_t maxThreads = 1)configureRpcThreadpool(maxThreads, true);configureBinderRpcThreadpool(maxThreads, callerWillJoin);ProcessState::self()->setThreadPoolConfiguration(maxThreads, callerWillJoin /*callerJoinsPool*/);status_t result = registerPassthroughServiceImplementation<Interface>(name);                                    registerPassthroughServiceImplementation( std::string name = "default")sp<Interface> service = Interface::getService(name, true /* getStub */);status_t status = service->registerAsService(name);joinRpcThreadpool();joinBinderRpcThreadpool();IPCThreadState::self()->joinThreadPool();}

相关文件

下面将从上到下介绍:

0.【Java 安卓LED服务类】LightsService.java

public class LightsService extends SystemService {// 下一级 Jni 接口static native void setLight_native(int light, int color, int mode,int onMS, int offMS, int brightnessMode);
}

1.【JNI 客户端实现】com_android_server_lights_LightsService.cpp

 static const JNINativeMethod method_table[] = {{ "setLight_native", "(IIIIII)V", (void*)setLight_native },};static void setLight_native(JNIEnv* /* env */,jobject /* clazz */,jint light,jint colorARGB,jint flashMode,jint onMS,jint offMS,jint brightnessMode) {if (!validate(light, flashMode, brightnessMode)) {return;}sp<ILight> hal = LightHal::associate();if (hal == nullptr) {return;}Type type = static_cast<Type>(light);LightState state = constructState(colorARGB, flashMode, onMS, offMS, brightnessMode);{ALOGD_IF_SLOW(50, "Excessive delay setting light");// 下一级入口 Return<Status> ret = hal->setLight(type, state);processReturn(ret, type, state);}}

2.【HIDL 客户】ILight.与 HIDL 服务库通信.hal

 定义了 HIDL 接口 package android.hardware.light@2.0;interface ILight {/*** Set the provided lights to the provided values.** @param type logical light to set* @param state describes what the light should look like.* @return status result of applying state transformation.*/setLight(Type type, LightState state) generates (Status status);/*** Discover what indicator lights are available.** @return types list of available lights*/getSupportedTypes() generates (vec<Type> types);};

3.【HIDL 客户】LightAll.cpp.调用 HIDL 服务库

::android::status_t BnHwLight::onTransact(uint32_t _hidl_code,const ::android::hardware::Parcel &_hidl_data,::android::hardware::Parcel *_hidl_reply,uint32_t _hidl_flags,TransactCallback _hidl_cb)// 下一级接口Status _hidl_out_status = _hidl_mImpl->setLight(type, *state);

4.【HIDL 服务】android.hardware.light@2.0-service.启动注册 HIDL 服务库

 service light-hal-2-0 /vendor/bin/hw/android.hardware.light@2.0-serviceclass haluser systemgroup system

4.【HIDL 服务】service.cpp.向上注册 HIDL 服务库

 int main() {return defaultPassthroughServiceImplementation<ILight>();    /// LegacySupport.h (system\libhidl\transport\include\hidl)defaultPassthroughServiceImplementation(std::string name, size_t maxThreads = 1)configureRpcThreadpool(maxThreads, true);configureBinderRpcThreadpool(maxThreads, callerWillJoin);ProcessState::self()->setThreadPoolConfiguration(maxThreads, callerWillJoin /*callerJoinsPool*/);status_t result = registerPassthroughServiceImplementation<Interface>(name);                                    registerPassthroughServiceImplementation( std::string name = "default")sp<Interface> service = Interface::getService(name, true /* getStub */);status_t status = service->registerAsService(name);joinRpcThreadpool();joinBinderRpcThreadpool();IPCThreadState::self()->joinThreadPool();

5.【HIDL 服务】Light.h

 namespace android {namespace hardware {namespace light {namespace V2_0 {namespace implementation {using ::android::hardware::light::V2_0::ILight;using ::android::hardware::light::V2_0::LightState;using ::android::hardware::light::V2_0::Status;using ::android::hardware::light::V2_0::Type;using ::android::hardware::Return;using ::android::hardware::Void;using ::android::hardware::hidl_vec;using ::android::hardware::hidl_string;using ::android::sp;struct Light : public ILight {Light(std::map<Type, light_device_t*> &&lights);// Methods from ::android::hardware::light::V2_0::ILight follow.Return<Status> setLight(Type type, const LightState& state)  override;Return<void> getSupportedTypes(getSupportedTypes_cb _hidl_cb)  override;private:std::map<Type, light_device_t*> mLights;};extern "C" ILight* HIDL_FETCH_ILight(const char* name);}  // namespace implementation}  // namespace V2_0}  // namespace light}  // namespace hardware}  // namespace android

6.【HIDL 服务】Light.cpp.加载调用传统库

 ILight* HIDL_FETCH_ILight(const char* /* name */) {std::map<Type, light_device_t*> lights;for(auto const &pair : kLogicalLights) {Type type = pair.first;const char* name = pair.second;// 加载传统 HAL 库 light_device_t* light = getLightDevice(name);if (light != nullptr) {lights[type] = light;}}if (lights.size() == 0) {// Log information, but still return new Light.// Some devices may not have any lights.ALOGI("Could not open any lights.");}return new Light(std::move(lights));}Light::Light(std::map<Type, light_device_t*> &&lights): mLights(std::move(lights)) {}// Methods from ::android::hardware::light::V2_0::ILight follow.Return<Status> Light::setLight(Type type, const LightState& state)  {auto it = mLights.find(type);if (it == mLights.end()) {return Status::LIGHT_NOT_SUPPORTED;}light_device_t* hwLight = it->second;light_state_t legacyState {.color = state.color,.flashMode = static_cast<int>(state.flashMode),.flashOnMS = state.flashOnMs,.flashOffMS = state.flashOffMs,.brightnessMode = static_cast<int>(state.brightnessMode),};int ret = hwLight->set_light(hwLight, &legacyState);switch (ret) {case -ENOSYS:return Status::BRIGHTNESS_NOT_SUPPORTED;case 0:return Status::SUCCESS;default:return Status::UNKNOWN;}}

7.【HAL 实现】lights.c.传统 HAL 库

 /** The lights Module*/struct hw_module_t HAL_MODULE_INFO_SYM = {.tag = HARDWARE_MODULE_TAG,.version_major = 1,.version_minor = 0,.id = LIGHTS_HARDWARE_MODULE_ID,.name = "lights Module",.author = "Google, Inc.",.methods = &lights_module_methods,};/*** module methods*//** Open a new instance of a lights device using name */
static int open_lights(const struct hw_module_t* module, char const* name,struct hw_device_t** device)
{int (*set_light)(struct light_device_t* dev,struct light_state_t const* state);if (0 == strcmp(LIGHT_ID_BACKLIGHT, name)) {char property[PROPERTY_VALUE_MAX];property_get("persist.extend.brightness", property, "0");if(!(strncmp(property, "1", PROPERTY_VALUE_MAX)) ||!(strncmp(property, "true", PROPERTY_VALUE_MAX))) {property_get("persist.display.max_brightness", property, "255");g_brightness_max = atoi(property);set_brightness_ext_init();set_light = set_light_backlight_ext;} else// 调用接口 set_light , 上层会调用到这里来set_light = set_light_backlight;} else if (0 == strcmp(LIGHT_ID_BATTERY, name))set_light = set_light_battery;else if (0 == strcmp(LIGHT_ID_NOTIFICATIONS, name))set_light = set_light_notifications;else if (0 == strcmp(LIGHT_ID_BUTTONS, name)) {if (!access(BUTTON_FILE, F_OK)) {// enable light button when the file is present              set_light = set_light_buttons;} else {return -EINVAL;}}else if (0 == strcmp(LIGHT_ID_ATTENTION, name))set_light = set_light_attention;elsereturn -EINVAL;pthread_once(&g_init, init_globals);struct light_device_t *dev = malloc(sizeof(struct light_device_t));if(!dev)return -ENOMEM;memset(dev, 0, sizeof(*dev));dev->common.tag = HARDWARE_DEVICE_TAG;dev->common.version = LIGHTS_DEVICE_API_VERSION_2_0;dev->common.module = (struct hw_module_t*)module;dev->common.close = (int (*)(struct hw_device_t*))close_lights;dev->set_light = set_light;*device = (struct hw_device_t*)dev;return 0;
}# 更新灯则通过 sys/class/led/xxx/bright 来操作的
// char const*const RED_LED_FILE    = "/sys/class/leds/red/brightness";
set_speaker_light_locked(struct light_device_t* dev,struct light_state_t const* state)write_int(RED_LED_FILE, 0);int bytes = snprintf(buffer, sizeof(buffer), "%d\n", value);ssize_t amt = write(fd, buffer, (size_t)bytes);

8.【内核实现】leds-qpnp.c

【充电 CHG_LED 可用红灯配置流程】:// Msm8937-pmi8940-mtp.dtsi (kernel\msm-3.18\arch\arm64\boot\dts\qcom)&pmi8940_charger {qcom,battery-data = <&mtp_batterydata>;qcom,chg-led-sw-controls;qcom,chg-led-support;};解析使用位置:Qpnp-smbcharger.c (kernel\msm-3.18\drivers\power)static int __init smbchg_init(void)// static struct spmi_driver smbchg_driver = {//     .driver       = {//         .name        = "qpnp-smbcharger",//         .owner        = THIS_MODULE,//         .of_match_table   = smbchg_match_table,//         .pm        = &smbchg_pm_ops,//     },//     .probe        = smbchg_probe,//     .remove      = smbchg_remove,//     .shutdown   = smbchg_shutdown,// };return spmi_driver_register(&smbchg_driver);smbchg_probe(struct spmi_device *spmi)smb_parse_dt(struct smbchg_chip *chip)chip->cfg_chg_led_support = of_property_read_bool(node, "qcom,chg-led-support");。。。    if (chip->cfg_chg_led_support && chip->schg_version == QPNP_SCHG_LITE)    rc = smbchg_register_chg_led(chip);chip->led_cdev.name = "red";chip->led_cdev.brightness_set = smbchg_chg_led_brightness_set;chip->led_cdev.brightness_get = smbchg_chg_led_brightness_get;rc = led_classdev_register(chip->dev, &chip->led_cdev);

基于 Light 介绍安卓 8.0 HAL 变化相关推荐

  1. 安卓系统刷机怎么刷机_安卓5.0系统如何刷机 安卓5.0系统刷机步骤介绍【教程】...

    安卓5.0系统怎么刷机?android5.0刷机包安装步骤!现就为大家介绍安卓5.0系统刷机包安装步骤. 安卓5.0刷机包下载地址将在下文提供哦~很多朋友在找安卓5.0系统刷机包官方下载链接,有了刷机 ...

  2. android10.0版本下载,安卓10.0系统

    零度软件园提供安卓10系统更新包下载,安卓10.0系统是一款安卓系统的最新版本,该系统版本不仅加入了5G技术的支持并且修复了以往一些系统bug,这样就能够为安卓手机用户带来不错的使用体验. 安卓10. ...

  3. htcm7刷linux,HTC ONE M7刷机包 Flyme6 安卓6.0插桩适配 顺畅体验 完整root 6.7.6.5R

    基于lineage os安卓6.0.1底包插桩适配;;本期更新:通讯 优化:通讯信号 修复:信号无法满格问题 修复:无SIM卡联系人入口问题 修复:部分双卡机型插单卡拨号仍然显示双卡问题 修复:部分机 ...

  4. 联想android正在升级,联想ZUK Z1暂停系统升级维护:好在已升安卓7.0

    感谢IT之家网友你以墨深祭流年 的投稿 IT之家4月23日消息 4月21日,联想ZUK Z1官方论坛发布暂停维护公告,公告表示,ZUI2.5.330版本将是Z1内测的最后一个版本,之后Z1将暂停维护. ...

  5. 荣耀v8升级android 8,圈粉1亿!荣耀最后四款老机型不限量升级安卓8.0

    7月25日,荣耀手机官方宣布,荣耀8.荣耀V8.荣耀Note 8.荣耀畅玩6X四款发布于2016年的老机型,正式开启EMUI 8.0新系统不限量升级. 官方强调,本次系统升级基于最新的安卓8.0内核, ...

  6. 小米8 android8.0,最经典的小米手机怒升安卓8.0,流畅度要起飞了!

    小米近两年最经典的手机是哪一部?可能很多人都会认为是小米5,虽然去年发布的小米6也非常经典,但在很多人心中,小米5是不可替代的经典机型.原因就是,小米5相比之前的小米手机,属于脱胎换骨的一代,而小米6 ...

  7. nubia基于android深度定制的ui,基于安卓5.0,nubia UI 2.8抢鲜体验

    IT之家讯 3月13日消息,虽然距离Android 5.0正式发布已经过去了近半年的时间,但是迟迟不见国内手机厂商为自家产品提供系统更新,结果导致目前为止,国内可以体验Android5.0的手机品牌寥 ...

  8. zuk z2 android 7.0,Lenovo 联想 ZUK Z2 PRO 尊享版ZUI 升级 2.5(基于安卓7.0)体验

    Lenovo 联想 ZUK Z2 PRO 尊享版ZUI 升级 2.5(基于安卓7.0)体验 2017-02-14 20:39:35 37点赞 41收藏 71评论 还是先唠叨几句入手zuk的心路历程.原 ...

  9. 鸿蒙就是安卓10,紧跟鸿蒙,华为宣布基于安卓10.0全新系统:8月9日见

    原标题:紧跟鸿蒙,华为宣布基于安卓10.0全新系统:8月9日见 华为将会在开发者大会上展示自主系统鸿蒙,而目前他们正在调试当中,以此希望新的系统能够吸引开发者,并为它建立起来一个良性的生态系统环境. ...

最新文章

  1. Maptree-层级结构数据展示的绝佳尝试
  2. 牛津书虫系列双语读物
  3. 如何自行分析定位SAP BSP错误
  4. filenotfoundexception是什么异常_Java异常处理:给程序罩一层保险
  5. centos7 rpm 安装 rabbitMQ 最新版
  6. 杭州趣链张帅:区块链应用落地,融合产业高速发展
  7. C++ Primer 5th 第14章 重载运算与类型转换
  8. ArcGIS Server瓦片下载推荐|全能电子地图下载器
  9. 【SQL练习】经典SQL练习题
  10. 线上事故复盘报告模板
  11. 怎样用Python自制好看的指数估值图
  12. 美团O2O广告营销中的机器学习技术
  13. SaaS(软件即服务)的魅力
  14. CTF-实验吧-图片里的动漫
  15. Whitestorm.js入门
  16. 获取键盘按下的键位对应ask码
  17. v-rep仿真之键盘控制机械臂末端移动
  18. 高等数学一:函数与极限二:收敛数列的保号性以及其推论的理解
  19. 打造 Material 颜色主题 | 实现篇
  20. 安卓学习日志 Day11 — JSON 解析

热门文章

  1. 古月居ros课件_古月居ROS入门21讲学习笔记(基础概述1-5课)
  2. 第5章 运算符和表达式
  3. requests+re-爬取豆瓣电影top250,看看都有哪些电影上榜
  4. 联想thinkbook14p最新测评
  5. 杨建允:椰树直播带货是翻车了还是一次成功的营销?
  6. 蓝桥杯2018省赛——猴子分香蕉(Java)
  7. linux .sig文件,SIG 文件扩展名: 它是什么以及如何打开它?
  8. 《精益软件开发艺术》译序
  9. 研报精选230312
  10. 30个实用的网页设计工具