android11关机安卓充电的UI定制化

news2025/4/1 22:35:23

引言

首先上一张安卓充电的图片:
安卓关机状态下有两种充电模式:uboot-charge和android-charge,可通过dts配置使用哪一种充电模式。
dts配置中uboot-charge和android-charge是互斥的,如下配置的是开启android-charge:

kernel/arch/arm64/boot/dts/rockchip/rk3566_xxproject.dts
在这里插入图片描述

本片主要讲解安卓充电的定制化。

安卓充电流程讲解

InitAnimation解析animation.txt配置文件

实现安卓充电的是一个名为charger的文件,源码位置:system/core/healthd
程序启动时首先会通过InitAnimation函数根据animation.txt解析得到图片和字体文件的路径。animation.txt内容如下:

#动画循环次数 首帧显示次数 动画压缩文件名(charge_scale是多张图片合成的一张图片)
animation: 3 1 charge_scale
#fail文件名
fail: fail_scale
#c c r g b a 字体文件名
clock_display: c c 255 255 255 255 font 
percent_display: c c 255 255 255 255 font 
#电量20以下显示的图片
frame: 500 0 19
#电量40以下显示的图片
frame: 600 0 39
frame: 700 0 59
frame: 750 0 79
frame: 750 0 89
frame: 750 0 100

Charger实现在system/core/healthd/healthd_mode_charger.cpp

static constexpr const char* product_animation_desc_path = "/product/etc/res/values/charger/animation.txt";
static constexpr const char* product_animation_root = "/product/etc/res/images/";

void Charger::InitAnimation() {
    bool parse_success;

    std::string content;
    if (base::ReadFileToString(product_animation_desc_path, &content)) {
        parse_success = parse_animation_desc(content, &batt_anim_);
        batt_anim_.set_resource_root(product_animation_root);
    } else if (base::ReadFileToString(animation_desc_path, &content)) {
        parse_success = parse_animation_desc(content, &batt_anim_);
    } else {
        LOGW("Could not open animation description at %s\n", animation_desc_path);
        parse_success = false;
    }
//parse_animation_desc实现在system/core/healthd/AnimationParser.cpp
bool parse_animation_desc(const std::string& content, animation* anim) {
    static constexpr const char* animation_prefix = "animation: ";
    static constexpr const char* fail_prefix = "fail: ";
    static constexpr const char* clock_prefix = "clock_display: ";
    static constexpr const char* percent_prefix = "percent_display: ";

    std::vector<animation::frame> frames;

    for (const auto& line : base::Split(content, "\n")) {
        animation::frame frame;
        const char* rest;

        if (can_ignore_line(line.c_str())) {
            continue;
        } else if (remove_prefix(line, animation_prefix, &rest)) {
            int start = 0, end = 0;
            if (sscanf(rest, "%d %d %n%*s%n", &anim->num_cycles, &anim->first_frame_repeats,
                    &start, &end) != 2 ||
                end == 0) {
                LOGE("Bad animation format: %s\n", line.c_str());
                return false;
            } else {
                anim->animation_file.assign(&rest[start], end - start);
            }
        } else if (remove_prefix(line, fail_prefix, &rest)) {
            anim->fail_file.assign(rest);
        } else if (remove_prefix(line, clock_prefix, &rest)) {
            if (!parse_text_field(rest, &anim->text_clock)) {
                LOGE("Bad clock_display format: %s\n", line.c_str());
                return false;
            }
        } else if (remove_prefix(line, percent_prefix, &rest)) {
            if (!parse_text_field(rest, &anim->text_percent)) {
                LOGE("Bad percent_display format: %s\n", line.c_str());
                return false;
            }
        } else if (sscanf(line.c_str(), " frame: %d %d %d",
                &frame.disp_time, &frame.min_level, &frame.max_level) == 3) {
            frames.push_back(std::move(frame));
        } else {
            LOGE("Malformed animation description line: %s\n", line.c_str());
            return false;
        }
    }

    if (anim->animation_file.empty() || frames.empty()) {
        LOGE("Bad animation description. Provide the 'animation: ' line and at least one 'frame: ' "
             "line.\n");
        return false;
    }

    anim->num_frames = frames.size();
    anim->frames = new animation::frame[frames.size()];
    std::copy(frames.begin(), frames.end(), anim->frames);

    return true;
}
//parse_text_field实现在system/core/healthd/AnimationParser.cpp
bool parse_text_field(const char* in, animation::text_field* field) {
    int* x = &field->pos_x;
    int* y = &field->pos_y;
    int* r = &field->color_r;
    int* g = &field->color_g;
    int* b = &field->color_b;
    int* a = &field->color_a;

    int start = 0, end = 0;

    if (sscanf(in, "c c %d %d %d %d %n%*s%n", r, g, b, a, &start, &end) == 4) {
        *x = CENTER_VAL;
        *y = CENTER_VAL;
    } else if (sscanf(in, "c %d %d %d %d %d %n%*s%n", y, r, g, b, a, &start, &end) == 5) {
        *x = CENTER_VAL;
    } else if (sscanf(in, "%d c %d %d %d %d %n%*s%n", x, r, g, b, a, &start, &end) == 5) {
        *y = CENTER_VAL;
    } else if (sscanf(in, "%d %d %d %d %d %d %n%*s%n", x, y, r, g, b, a, &start, &end) != 6) {
        return false;
    }
    if (end == 0) return false;
    field->font_file.assign(&in[start], end - start);
    return true;
}

初始化GRSurface

根据上一步解析得到的图片的路径转化成绘图表面

void Charger::Init(struct healthd_config* config) {
	...
    InitAnimation();

    ret = res_create_display_surface(batt_anim_.fail_file.c_str(), &surf_unknown_);
    if (ret < 0) {
        LOGE("Cannot load custom battery_fail image. Reverting to built in: %d\n", ret);
        ret = res_create_display_surface("charger/battery_fail", &surf_unknown_);
        if (ret < 0) {
            LOGE("Cannot load built in battery_fail image\n");
            surf_unknown_ = NULL;
        }
    }

    GRSurface** scale_frames;
    int scale_count;
    int scale_fps;  // Not in use (charger/battery_scale doesn't have FPS text
                    // chunk). We are using hard-coded frame.disp_time instead.
    ret = res_create_multi_display_surface(batt_anim_.animation_file.c_str(), &scale_count,
                                           &scale_fps, &scale_frames);
    if (ret < 0) {
        LOGE("Cannot load battery_scale image\n");
        batt_anim_.num_frames = 0;
        batt_anim_.num_cycles = 1;
    } else if (scale_count != batt_anim_.num_frames) {
        LOGE("battery_scale image has unexpected frame count (%d, expected %d)\n", scale_count,
             batt_anim_.num_frames);
        batt_anim_.num_frames = 0;
        batt_anim_.num_cycles = 1;
    } else {
        for (i = 0; i < batt_anim_.num_frames; i++) {
            batt_anim_.frames[i].surface = scale_frames[i];
        }
    }

绘制电量图片、电量百分比和时间文字,用的minui框架

void Charger::UpdateScreenState(int64_t now) {
    ...
    if (healthd_draw_ == nullptr) {
        ...
		//初始化healthd_draw_
        healthd_draw_.reset(new HealthdDraw(&batt_anim_));
        if (android::sysprop::ChargerProperties::disable_init_blank().value_or(false)) {
            healthd_draw_->blank_screen(true);
            screen_blanked_ = true;
        }
    }
    
    //执行具体的绘制
    healthd_draw_->redraw_screen(&batt_anim_, surf_unknown_);
	...
}

HealthdDraw::HealthdDraw(animation* anim)
    : kSplitScreen(get_split_screen()), kSplitOffset(get_split_offset()) {
    int ret = gr_init();

    if (ret < 0) {
        LOGE("gr_init failed\n");
        graphics_available = false;
        return;
    }

    graphics_available = true;
    sys_font = gr_sys_font();
    if (sys_font == nullptr) {
        LOGW("No system font, screen fallback text not available\n");
    } else {
        gr_font_size(sys_font, &char_width_, &char_height_);
    }

    screen_width_ = gr_fb_width() / (kSplitScreen ? 2 : 1);
    screen_height_ = gr_fb_height();

    int res;
    if (!anim->text_clock.font_file.empty() &&
        (res = gr_init_font(anim->text_clock.font_file.c_str(), &anim->text_clock.font)) < 0) {
        LOGE("Could not load time font (%d)\n", res);
    }
    if (!anim->text_percent.font_file.empty() &&
        (res = gr_init_font(anim->text_percent.font_file.c_str(), &anim->text_percent.font)) < 0) {
        LOGE("Could not load percent font (%d)\n", res);
    }
}

void HealthdDraw::redraw_screen(const animation* batt_anim, GRSurface* surf_unknown) {
    if (!graphics_available) return;
    clear_screen();

    /* try to display *something* */
    if (batt_anim->cur_status == BATTERY_STATUS_UNKNOWN || batt_anim->cur_level < 0 ||
        batt_anim->num_frames == 0)
        draw_unknown(surf_unknown);
    else
        draw_battery(batt_anim);
    gr_flip();
}

void HealthdDraw::draw_battery(const animation* anim) {
    if (!graphics_available) return;
    const animation::frame& frame = anim->frames[anim->cur_frame];

    if (anim->num_frames != 0) {
    	//绘制电量图片
        draw_surface_centered(frame.surface);
        LOGV("drawing frame #%d min_cap=%d time=%d\n", anim->cur_frame, frame.min_level,
             frame.disp_time);
    }
    //绘制时间和电量百分比文字
    draw_clock(anim);
    draw_percent(anim);
}

void HealthdDraw::draw_percent(const animation* anim) {
    if (!graphics_available) return;
    int cur_level = anim->cur_level;
    if (anim->cur_status == BATTERY_STATUS_FULL) {
        cur_level = 100;
    }
    if (cur_level < 0) return;
    const animation::text_field& field = anim->text_percent;
    if (field.font == nullptr || field.font->char_width == 0 || field.font->char_height == 0) {
        return;
    }

    std::string str = base::StringPrintf("%d%%", cur_level);
    int x, y;
    determine_xy(field, str.size(), &x, &y);

    LOGV("drawing percent %s %d %d\n", str.c_str(), x, y);
    gr_color(field.color_r, field.color_g, field.color_b, field.color_a);
    draw_text(field.font, x, y, str.c_str());
}

HealthdDraw实现在system/core/healthd/healthd_draw.cpp

电量刷新和事件响应

充电状体下会监听power按键、充电器插拔事件和电量更新事件。
监听power按键实现在HandleInputState函数,按下power键会触发重新显示充电动画。

void Charger::HandleInputState(int64_t now) {
	//监听power按键
    ProcessKey(KEY_POWER, now);
    if (next_key_check_ != -1 && now > next_key_check_) next_key_check_ = -1;
}

void Charger::ProcessKey(int code, int64_t now) {
    key_state* key = &keys_[code];

    if (code == KEY_POWER) {
        if (key->down) {
            int64_t reboot_timeout = key->timestamp + POWER_ON_KEY_TIME;
            if (now >= reboot_timeout) {
                /* We do not currently support booting from charger mode on
                   all devices. Check the property and continue booting or reboot
                   accordingly. */
                if (property_get_bool("ro.enable_boot_charger_mode", false)) {
                    LOGW("[%" PRId64 "] booting from charger mode\n", now);
                    property_set("sys.boot_from_charger_mode", "1");
                } else {
                    if (batt_anim_.cur_level >= boot_min_cap_) {
                        LOGW("[%" PRId64 "] rebooting\n", now);
                        reboot(RB_AUTOBOOT);
                    } else {
                        LOGV("[%" PRId64
                             "] ignore power-button press, battery level "
                             "less than minimum\n",
                             now);
                    }
                }
            } else {
                /* if the key is pressed but timeout hasn't expired,
                 * make sure we wake up at the right-ish time to check
                 */
                SetNextKeyCheck(key, POWER_ON_KEY_TIME);

                /* Turn on the display and kick animation on power-key press
                 * rather than on key release
                 */
                kick_animation(&batt_anim_);
                request_suspend(false);
            }
        } else {
            /* if the power key got released, force screen state cycle */
            if (key->pending) {
                kick_animation(&batt_anim_);
                request_suspend(false);
            }
        }
    }
    key->pending = false;
}

如果想要监听更多的按键事件,只需要在HandleInputState函数中新增ProcessKey(KEY_xxx, now),然后在ProcessKey实现对应键值的逻辑即可。

充电器插拔回调到HandlePowerSupplyState函数

void Charger::HandlePowerSupplyState(int64_t now) {
    int timer_shutdown = UNPLUGGED_SHUTDOWN_TIME;
    if (!have_battery_state_) return;
    if (!charger_online()) {
    	//断开充电器
        ...
    } else {
	    //插入充电器
        ...
    }
}

电量刷新会回调到OnHealthInfoChanged函数

void Charger::OnHealthInfoChanged(const HealthInfo_2_1& health_info) {
    set_charger_online(health_info);

    if (!have_battery_state_) {
        have_battery_state_ = true;
        next_screen_transition_ = curr_time_ms() - 1;
        request_suspend(false);
        reset_animation(&batt_anim_);
        kick_animation(&batt_anim_);
    }
    health_info_ = health_info.legacy.legacy;
    
    AdjustWakealarmPeriods(charger_online());
}

在rk3566 android11中动画执行完后,如果电量刷新了不会触发界面的刷新。如要实现电量实时更新到界面,在此方法中新增逻辑即可,下面贴下我实现电量实时刷新的patch

diff --git a/healthd/healthd_mode_charger.cpp b/healthd/healthd_mode_charger.cpp
--- a/healthd/healthd_mode_charger.cpp	(revision 6ae575fc403d2504435366ac34ff233e537e78bd)
+++ b/healthd/healthd_mode_charger.cpp	(revision 1122ab003e599072fa194f23b593fbd4ad84205e)
@@ -617,6 +617,15 @@
         reset_animation(&batt_anim_);
         kick_animation(&batt_anim_);
     }
+    //huanghp add: refresh screen when batteryLevel changed
+    if (health_info_.batteryLevel != health_info.legacy.legacy.batteryLevel){
+        LOGV("batteryLevel changed : %d\n",health_info.legacy.legacy.batteryLevel);
+        request_suspend(false);
+        reset_animation(&batt_anim_);
+        kick_animation(&batt_anim_);
+    }
+    //huanghp end;
     health_info_ = health_info.legacy.legacy;
 
     AdjustWakealarmPeriods(charger_online());

源码更新了后可以单编charger,ado root && adb remount后替换charger文件重启机器就能看到效果,不需要刷机。对于下面的充电图标和字体也是找到对应目录直接替换后重启就可以看效果。

充电图标替换

修改默认关机充电图标实际上要替换battery_scale.png,charge_scale.png实际是由多张图片合成的一张图片。
在这里插入图片描述
对应c源码配置

void Charger::InitDefaultAnimationFrames() {
    owned_frames_ = {
            {
                    .disp_time = 750,
                    .min_level = 0,
                    .max_level = 19,
                    .surface = NULL,
            },
            {
                    .disp_time = 750,
                    .min_level = 0,
                    .max_level = 39,
                    .surface = NULL,
            },
            {
                    .disp_time = 750,
                    .min_level = 0,
                    .max_level = 59,
                    .surface = NULL,
            },
            {
                    .disp_time = 750,
                    .min_level = 0,
                    .max_level = 79,
                    .surface = NULL,
            },
            {
                    .disp_time = 750,
                    .min_level = 80,
                    .max_level = 95,
                    .surface = NULL,
            },
            {
                    .disp_time = 750,
                    .min_level = 0,
                    .max_level = 100,
                    .surface = NULL,
            },
    };
}

合成和拆分charge_scale用到的脚本:bootable/recovery/interlace-frames.py

#合成命令
python interlace-frames.py -o battery_scale.png oem/battery00.png oem/battery01.png oem/battery02.png oem/battery03.png oem/battery04.png oem/battery05.png
#拆分命令
python interlace-frames.py -d battery_scale.png -o battery.png

font.png字体文件替换

在这里插入图片描述
在这里插入图片描述
bootable/recovery/fonts目录下默认有些不同大小的字体文件,官方的说法是字体都是用font
Inconsolata自动生成的。

The images in this directory were generated using the font
Inconsolata, which is released under the OFL license and was obtained
from:
https://code.google.com/p/googlefontdirectory/source/browse/ofl/inconsolata/

打开链接发现内容不在了,没有找到制作字体的工具。
因此如果要使用更大字号的字体,就需要自己想办法制作字体,这里我从stackoverflow找到个
可以自动生成的python脚本,试了生成的字体可以使用。

'auto generate font png'
from PIL import Image, ImageDraw, ImageFont
import os
def draw_png(name, font_size = 40):
    font_reg  = ImageFont.truetype(name + '-Regular' + '.ttf', font_size)
    font_bold = ImageFont.truetype(name + '-Bold' + '.ttf', font_size)
    text=r''' !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'''
    text_width, text_height = font_bold.getsize(text)

    max_w = 0
    max_h = 0
    for c in text:
        w, h = font_bold.getsize(c)
        if w > max_w:
            max_w = w
            
        if h > max_h:
            max_h = h
        
    print max_w, max_h

    image = Image.new(mode='L', size=(max_w*96, max_h*2))
    draw_table = ImageDraw.Draw(im=image)
    i = 0
    for c in text:
        text_width, text_height = font_bold.getsize(c)
        print c , text_width, text_height
        draw_table.text(xy=(max_w*i, 0), text=c, fill='#ffffff', font=font_reg, anchor="mm", align="center")
        draw_table.text(xy=(max_w*i, max_h), text=c, fill='#ffffff', font=font_bold, anchor="mm",align="center")
        i = i + 1

    image.show()
    image.save( name + '.png', 'PNG')
    image.close()


if __name__ == "__main__":
    print('running:')
    try:
        draw_png('Roboto',100)
    except Exception as e:
        print( ' ERR: ', e)

字体文件直接在aosp源码目录查找find ./ -name *.ttf |grep Roboto

参考:

  • https://blog.csdn.net/lmpt90/article/details/103390395
  • https://stackoverflow.com/questions/65180151/how-to-generate-a-font-image-used-in-android-power-off-charging-animation

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

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

相关文章

Web前端之JavaScript的DOM操作冷门API

MENU 前言1、Element.checkVisibility()2、TreeWalker3、Node.compareDocumentPosition()4、scrollIntoViewIfNeeded()5、insertAdjacentElement()6、Range.surroundContents()7、Node.isEqualNode()8、document.createExpression()小结 前言 作为前端开发者&#xff0c;我们每…

集成开发环境革新:IntelliJ IDEA与Cursor AI的智能演进

集成开发环境革新&#xff1a;IntelliJ IDEA 与 Cursor AI 的智能演进 集成开发环境&#xff08;IDE&#xff09; 是软件开发者必不可少的工具。一个优秀的 IDE 不仅能够帮助编写和调试代码&#xff0c;还能集成版本控制和代码优化等多种功能。如今&#xff0c;随着人工智能&a…

EXCEL报错:无法共享此工作薄,因表包含excel表或xml映射的解决方法

在分享工作薄是&#xff0c;如果出现了“无法共享此工作薄&#xff0c;因表包含excel表或xml映射”的报错&#xff0c;那么有两个原因&#xff1a; 1.包含Excel表格&#xff0c;这个也是相对比较常见的原因。 首先选中表格。如果你不知道表的位置在哪&#xff0c;那么在Excel左…

《Linux运维实战:Ubuntu 22.04配置pam实现密码复杂度策略》

总结&#xff1a;整理不易&#xff0c;如果对你有帮助&#xff0c;可否点赞关注一下&#xff1f; 更多详细内容请参考&#xff1a;Linux运维实战总结 一、背景信息 由于安全方面的考虑&#xff0c;先要求Ubuntu 22.04系统需配置密码复杂度策略&#xff0c;先要求如下&#xff1…

从代码学习深度学习 - 使用块的网络(VGG)PyTorch版

文章目录 前言一、VGG网络简介1.1 VGG的核心特点1.2 VGG的典型结构1.3 优点与局限性1.4 本文的实现目标二、搭建VGG网络2.1 数据准备2.2 定义VGG块2.3 构建VGG网络2.4 辅助工具2.4.1 计时器和累加器2.4.2 准确率计算2.4.3 可视化工具2.5 训练模型2.6 运行实验总结前言 深度学习…

Windows 安装多用户和其它一些问题 VMware Onedrive打不开

以下以win10家庭版为例&#xff0c;win11、专业版类似。 Onedrive相关问题参看我的其他文章&#xff1a; Windows如何同时登录两个OneDrive个人版账号_onedrive登录两个账号-CSDN博客 win10 win11 设置文件权限以解决Onedrive不能同步问题_onedrive没有同步权限-CSDN博客 O…

java基础自用笔记:异常、泛型、集合框架(List、Set、Map)、Stream流

异常 异常体系 编译时异常代表程序觉得你可能会出错。 运行时异常代表已经出错 异常基本处理 异常的作用 可以在可能出现的异常的地方用返回异常来代替return&#xff0c;这样提醒程序出现异常简洁清晰 自定义异常 最好用运行时异常&#xff0c;不会像编译时异常那样烦人&a…

第六届 蓝桥杯 嵌入式 省赛

参考 第六届蓝桥杯嵌入式省赛程序设计题解析&#xff08;基于HAL库&#xff09;_蓝桥杯嵌入式第六届真题-CSDN博客 一、分析功能 RTC 定时 1&#xff09;时间初始化 2&#xff09;定时上报电压时间 ADC测量 采集电位器的输出电压信号。 串行功能 1&#xff09;传送要设置…

爱普生FC-135晶振5G手机的极端温度性能守护者

在5G时代&#xff0c;智能手机不仅需要高速率与低延迟&#xff0c;更需在严寒、酷暑、振动等复杂环境中保持稳定运行。作为 5G 手机的核心时钟源&#xff0c;爱普生32.768kHz晶振FC-135凭借其宽温适应性、高精度稳定性与微型化设计&#xff0c;成为5G手机核心时钟源的理想选择&…

如何备份你的 Postman 所有 Collection?

团队合作需要、备份&#xff0c;还是迁移到其他平台&#xff0c;我们都需要在 Postman 中将这些珍贵的集合数据导出。 如何从 Postman 中导出所有集合(Collection)教程

MinGW下编译ffmpeg源码时生成compile_commands.json

在前面的博文MinGW下编译nginx源码中&#xff0c;有介绍到使用compiledb工具在MinGW环境中生成compile_commands.json&#xff0c;以为compiledb是捕获的make时的输出&#xff0c;而nginx生成时控制台是有输出编译时的命令行信息的&#xff0c;笔者之前编译过ffmpeg的源码&…

【数据结构】树与森林

目录 树的存储方法 双亲表示法 孩子表示法 孩子兄弟表示法 树、森林与二叉树的转换 树转换成二叉树 森林转换成二叉树 二叉树转换成森林 树与森林的遍历 树的遍历 森林的遍历 树的存储方法 双亲表示法 这种存储结构采用一组连续空间来存储每个结点&#xff0c;同时…

跟着StatQuest学知识08-RNN与LSTM

一、RNN &#xff08;一&#xff09;简介 整个过程权重和偏置共享。 &#xff08;二&#xff09;梯度爆炸问题 在这个例子中w2大于1&#xff0c;会出现梯度爆炸问题。 当我们循环的次数越来越多的时候&#xff0c;这个巨大的数字会进入某些梯度&#xff0c;步长就会大幅增加&…

【SpringCloud】Eureka的使用

3. Eureka 3.1 Eureka 介绍 Eureka主要分为两个部分&#xff1a; EurekaServer: 作为注册中心Server端&#xff0c;向微服务应用程序提供服务注册&#xff0c;发现&#xff0c;健康检查等能力。 EurekaClient: 服务提供者&#xff0c;服务启动时&#xff0c;会向 EurekaS…

初识MySQL · 数据类型

目录 前言&#xff1a; 数值类型 文本、二进制数据类型 时间类型 String类型 前言&#xff1a; 对于MySQL来说&#xff0c;是一门编程语言&#xff0c;可能定义不是那么的严格&#xff0c;但是对于MySQL来说也是拥有自己的数据类型的&#xff0c;比如tinyint&#xff0c;…

QT图片轮播器(QT实操学习2)

1.项目架构 1.UI界面 2.widget.h​ #ifndef WIDGET_H #define WIDGET_H#include <QWidget>#define TIMEOUT 1 * 1000 QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent n…

深度解析衡石科技HENGSHI SENSE嵌入式分析能力:如何实现3天快速集成

嵌入式分析成为现代SaaS的核心竞争力 在当今SaaS市场竞争中&#xff0c;数据分析能力已成为产品差异化的关键因素。根据Bessemer Venture Partners的最新调研&#xff0c;拥有深度嵌入式分析功能的SaaS产品&#xff0c;其客户留存率比行业平均水平高出23%&#xff0c;ARR增长速…

杂草YOLO系列数据集4000张

一份开源数据集——杂草YOLO数据集&#xff0c;该数据集适用于农业智能化、植物识别等计算机视觉应用场景。 数据集详情 ​训练集&#xff1a;3,664张高清标注图像​测试集&#xff1a;180张多样性场景样本​验证集&#xff1a;359张严格筛选数据 下载链接 杂草YOLO数据集分…

Vue 2 探秘:visible 和 append-to-body 是谁的小秘密?

&#x1f680; Vue 2 探秘&#xff1a;visible 和 append-to-body 是谁的小秘密&#xff1f;&#x1f914; 父组件&#xff1a;identify-list.vue子组件&#xff1a;fake-clue-list.vue 嘿&#xff0c;各位前端探险家&#xff01;&#x1f44b; 今天我们要在 Vue 2 的代码丛林…

机器学习的一百个概念(1)单位归一化

前言 本文隶属于专栏《机器学习的一百个概念》&#xff0c;该专栏为笔者原创&#xff0c;引用请注明来源&#xff0c;不足和错误之处请在评论区帮忙指出&#xff0c;谢谢&#xff01; 本专栏目录结构和参考文献请见[《机器学习的一百个概念》 ima 知识库 知识库广场搜索&…