IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> MTK 关机充电时充电IC正常,电池正常充电,但是充电动画一直显示0% -> 正文阅读

[移动开发]MTK 关机充电时充电IC正常,电池正常充电,但是充电动画一直显示0%

MTK 关机充电时充电IC正常,电池正常充电,但是充电动画一直显示0%

平台

mt8168+mt6357+chargerIC

问题

MTK 关机充电时充电IC正常,电池正常充电,但是充电动画一直显示0%

相关说明

只截取部分记录一下思路
on charger 触发
工程中的一些on charger配置用于开机时(bootmode is charger)充电触发。
如:device\mediatek\mt8168\init.mt8168.rc

on charger
    wait /sys/bus/platform/devices/mt6357-charger-type-detection 10
    symlink /dev/block/platform/soc/11230000.mmc /dev/block/platform/bootdevice
    # mount ext4 /dev/block/platform/bootdevice/by-name/system /system ro wait

    # Permissions for System Server and daemons.
    chown system system /sys/power/autosleep
    chown system system /sys/power/state
    chown system system /sys/power/wakeup_count
    chown radio wakelock /sys/power/wake_lock
    chown radio wakelock /sys/power/wake_unlock
    chmod 0660 /sys/power/state
    chmod 0660 /sys/power/wake_lock
    chmod 0660 /sys/power/wake_unlock
    chmod 0660 /sys/power/wakeup_count
    start kpoc_charger

    chmod 0666 /dev/kmsg
    chmod 0775 /mnt/vendor
    mkdir /mnt/vendor/nvcfg
    mount ext4 /dev/block/platform/bootdevice/by-name/nvcfg /mnt/vendor/nvcfg rw wait
    chown system system /mnt/vendor/nvcfg
    chmod 0771 /mnt/vendor/nvcfg
    restorecon_recursive /mnt/vendor/nvcfg
    write /sys/devices/platform/battery_meter/FG_daemon_log_level 7
    write /sys/bus/platform/devices/battery/FG_daemon_log_level 7
    start fuelgauged
    start fuelgauged_nvram

    chown system system /sys/class/leds/lcd-backlight/brightness
    chown system system /sys/class/leds/red/brightness
    chown system system /sys/class/leds/green/brightness

#    start vendor.light-default

    # disable USB
    write /sys/devices/platform/mt_usb/cmode 0

hardware\interfaces\health\2.1\default\android.hardware.health@2.1-service.rc

service health-hal-2-1 /vendor/bin/hw/android.hardware.health@2.1-service
    class hal charger
    user system
    group system
    capabilities WAKE_ALARM BLOCK_SUSPEND
    file /dev/kmsg w

vendor\mediatek\proprietary\external\charger 关机充电时调用的服务。
kpoc_charger.rc内容:

on charger
    start kpoc_charger

service kpoc_charger /system/bin/kpoc_charger
    class charger
    group system wakelock
    capabilities BLOCK_SUSPEND SYS_ADMIN SYS_BOOT

启动流程(\system\core\init\init.cpp)中判断是否充电模式开机判断如下:

int SecondStageMain(int argc, char** argv) {
。。。

    // Don't mount filesystems or start core system services in charger mode.
    std::string bootmode = GetProperty("ro.bootmode", "");
	LOG(INFO) << "init : xmhxj debug : bootmode is " << bootmode;
    if (bootmode == "charger") {
        am.QueueEventTrigger("charger");
    } else {
        am.QueueEventTrigger("late-init");
    }
。。。
}

hardware\interfaces\health 提供vendor和system的接口调用。
如电量获取getCapacity:
hardware\interfaces\health\2.0\default\Health.cpp

Return<void> Health::getCapacity(getCapacity_cb _hidl_cb) {
    getProperty<int32_t>(battery_monitor_, BATTERY_PROP_CAPACITY, 0, _hidl_cb);
    return Void();
}

关联调用的是:
system\core\healthd\BatteryMonitor.cpp

status_t BatteryMonitor::getProperty(int id, struct BatteryProperty *val) {
    status_t ret = BAD_VALUE;
    std::string buf;

    val->valueInt64 = LONG_MIN;

    switch(id) {
。。。
    case BATTERY_PROP_CAPACITY:
        if (!mHealthdConfig->batteryCapacityPath.isEmpty()) {
            val->valueInt64 =
                getIntField(mHealthdConfig->batteryCapacityPath);
            ret = OK;
        } else {
            ret = NAME_NOT_FOUND;
        }
        break;
。。。
    return ret;
}

healthd服务相关(system\core\healthd ),用于关机充电时获取电池信息的地方。
void BatteryMonitor::updateValues函数,更新底层上报的电池信息

void BatteryMonitor::updateValues(void) {
    initHealthInfo(mHealthInfo.get());

    HealthInfo_1_0& props = mHealthInfo->legacy.legacy;
	KLOG_WARNING(LOG_TAG, "BM xmhxj debug :mHealthdConfig->batteryPresentPath.isEmpty() %d\n", mHealthdConfig->batteryPresentPath.isEmpty());
	KLOG_WARNING(LOG_TAG, "BM xmhxj debug :mHealthdConfig->batteryPresentPath %s\n",mHealthdConfig->batteryPresentPath.string());

    if (!mHealthdConfig->batteryPresentPath.isEmpty()){
    	KLOG_WARNING(LOG_TAG, "BM xmhxj debug :!mHealthdConfig->batteryPresentPath.isEmpty() %s\n", mHealthdConfig->batteryPresentPath.string());

		props.batteryPresent = getBooleanField(mHealthdConfig->batteryPresentPath);
    } else {

		props.batteryPresent = mBatteryDevicePresent;
	KLOG_WARNING(LOG_TAG, "BM xmhxj debug : mHealthdConfig->batteryPresentPath.isEmpty() mBatteryDevicePresent %d\n", mBatteryDevicePresent);

    }
	KLOG_WARNING(LOG_TAG, "BM xmhxj debug : props.batteryPresent %d\n", props.batteryPresent);

	
    props.batteryLevel = mBatteryFixedCapacity ?
        mBatteryFixedCapacity :
        getIntField(mHealthdConfig->batteryCapacityPath);
    props.batteryVoltage = getIntField(mHealthdConfig->batteryVoltagePath) / 1000;

    if (!mHealthdConfig->batteryCurrentNowPath.isEmpty())
        props.batteryCurrent = getIntField(mHealthdConfig->batteryCurrentNowPath);

    if (!mHealthdConfig->batteryFullChargePath.isEmpty())
        props.batteryFullCharge = getIntField(mHealthdConfig->batteryFullChargePath);

    if (!mHealthdConfig->batteryCycleCountPath.isEmpty())
        props.batteryCycleCount = getIntField(mHealthdConfig->batteryCycleCountPath);

    if (!mHealthdConfig->batteryChargeCounterPath.isEmpty())
        props.batteryChargeCounter = getIntField(mHealthdConfig->batteryChargeCounterPath);

    if (!mHealthdConfig->batteryCurrentAvgPath.isEmpty())
        mHealthInfo->legacy.batteryCurrentAverage =
                getIntField(mHealthdConfig->batteryCurrentAvgPath);

    if (!mHealthdConfig->batteryChargeTimeToFullNowPath.isEmpty())
        mHealthInfo->batteryChargeTimeToFullNowSeconds =
                getIntField(mHealthdConfig->batteryChargeTimeToFullNowPath);

    if (!mHealthdConfig->batteryFullChargeDesignCapacityUahPath.isEmpty())
        mHealthInfo->batteryFullChargeDesignCapacityUah =
                getIntField(mHealthdConfig->batteryFullChargeDesignCapacityUahPath);

    props.batteryTemperature = mBatteryFixedTemperature ?
        mBatteryFixedTemperature :
        getIntField(mHealthdConfig->batteryTemperaturePath);

    std::string buf;

    if (readFromFile(mHealthdConfig->batteryCapacityLevelPath, &buf) > 0)
        mHealthInfo->batteryCapacityLevel = getBatteryCapacityLevel(buf.c_str());

    if (readFromFile(mHealthdConfig->batteryStatusPath, &buf) > 0)
        props.batteryStatus = getBatteryStatus(buf.c_str());

    if (readFromFile(mHealthdConfig->batteryHealthPath, &buf) > 0)
        props.batteryHealth = getBatteryHealth(buf.c_str());

    if (readFromFile(mHealthdConfig->batteryTechnologyPath, &buf) > 0)
        props.batteryTechnology = String8(buf.c_str());

    double MaxPower = 0;

    for (size_t i = 0; i < mChargerNames.size(); i++) {
        String8 path;
        path.appendFormat("%s/%s/online", POWER_SUPPLY_SYSFS_PATH,
                          mChargerNames[i].string());
        if (getIntField(path)) {
            path.clear();
            path.appendFormat("%s/%s/type", POWER_SUPPLY_SYSFS_PATH,
                              mChargerNames[i].string());
            switch(readPowerSupplyType(path)) {
            case ANDROID_POWER_SUPPLY_TYPE_AC:
                props.chargerAcOnline = true;
                break;
            case ANDROID_POWER_SUPPLY_TYPE_USB:
                props.chargerUsbOnline = true;
                break;
            case ANDROID_POWER_SUPPLY_TYPE_WIRELESS:
                props.chargerWirelessOnline = true;
                break;
            default:
                KLOG_WARNING(LOG_TAG, "%s: Unknown power supply type\n",
                             mChargerNames[i].string());
            }
            path.clear();
            path.appendFormat("%s/%s/current_max", POWER_SUPPLY_SYSFS_PATH,
                              mChargerNames[i].string());
            int ChargingCurrent =
                    (access(path.string(), R_OK) == 0) ? getIntField(path) : 0;

            path.clear();
            path.appendFormat("%s/%s/voltage_max", POWER_SUPPLY_SYSFS_PATH,
                              mChargerNames[i].string());

            int ChargingVoltage =
                (access(path.string(), R_OK) == 0) ? getIntField(path) :
                DEFAULT_VBUS_VOLTAGE;

            double power = ((double)ChargingCurrent / MILLION) *
                           ((double)ChargingVoltage / MILLION);
            if (MaxPower < power) {
                props.maxChargingCurrent = ChargingCurrent;
                props.maxChargingVoltage = ChargingVoltage;
                MaxPower = power;
            }
        }
    }
}

服务初始化函数void BatteryMonitor::init函数:

void BatteryMonitor::init(struct healthd_config *hc) {  
    String8 path;  
    char pval[PROPERTY_VALUE_MAX];  
...  
    mHealthdConfig = hc;  
    std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(POWER_SUPPLY_SYSFS_PATH), closedir);  
    if (dir == NULL) {  
        KLOG_ERROR(LOG_TAG, "Could not open %s\n", POWER_SUPPLY_SYSFS_PATH);  
    } else {  
        struct dirent* entry;  
  
        while ((entry = readdir(dir.get()))) {  
            const char* name = entry->d_name;  
            std::vector<String8>::iterator itIgnoreName;  
            KLOG_WARNING(LOG_TAG, "BM xmhxj debug :INIT 111 name %s\n",name);  
  
            if (!strcmp(name, ".") || !strcmp(name, ".."))  
                continue;  
  
            itIgnoreName = find(hc->ignorePowerSupplyNames.begin(),  
                                hc->ignorePowerSupplyNames.end(), String8(name));  
            if (itIgnoreName != hc->ignorePowerSupplyNames.end())  
                continue;  
            KLOG_WARNING(LOG_TAG, "BM xmhxj debug :INIT 222 name %s\n",name);  
  
            // Look for "type" file in each subdirectory  
            path.clear();  
            path.appendFormat("%s/%s/type", POWER_SUPPLY_SYSFS_PATH, name);  
            KLOG_WARNING(LOG_TAG, "BM xmhxj debug :INIT 333 readPowerSupplyType %d\n",readPowerSupplyType(path));  
  
            switch(readPowerSupplyType(path)) {  
   ...  
  
            case ANDROID_POWER_SUPPLY_TYPE_BATTERY:  
                KLOG_WARNING(LOG_TAG, "BM xmhxj debug :INIT 333 name %s\n",name);  
  
                // Some devices expose the battery status of sub-component like  
                // stylus. Such a device-scoped battery info needs to be skipped  
                // in BatteryMonitor, which is intended to report the status of  
                // the battery supplying the power to the whole system.  
                if (isScopedPowerSupply(name)) continue;  
                mBatteryDevicePresent = true;  
                KLOG_WARNING(LOG_TAG, "BM xmhxj debug :INIT 444 name %s\n",name);  
  
                if (mHealthdConfig->batteryStatusPath.isEmpty()) {  
                    path.clear();  
                    path.appendFormat("%s/%s/status", POWER_SUPPLY_SYSFS_PATH,  
                                      name);  
                    if (access(path, R_OK) == 0)  
                        mHealthdConfig->batteryStatusPath = path;  
                }  
  
                if (mHealthdConfig->batteryHealthPath.isEmpty()) {  
                    path.clear();  
                    path.appendFormat("%s/%s/health", POWER_SUPPLY_SYSFS_PATH,  
                                      name);  
                    if (access(path, R_OK) == 0)  
                        mHealthdConfig->batteryHealthPath = path;  
                }  
                KLOG_WARNING(LOG_TAG, "BM xmhxj debug :mHealthdConfig->batteryPresentPath %s\n",mHealthdConfig->batteryPresentPath.string());  
  
                if (mHealthdConfig->batteryPresentPath.isEmpty()) {  
                    path.clear();  
                    path.appendFormat("%s/%s/present", POWER_SUPPLY_SYSFS_PATH,  
                                      name);  
              
  
                    if (access(path, R_OK) == 0){  
                    mHealthdConfig->batteryPresentPath = path;  
                    KLOG_WARNING(LOG_TAG, "BM xmhxj debug : %s/%s/present ok\n",POWER_SUPPLY_SYSFS_PATH,name);  
  
                    } else {  
                    KLOG_WARNING(LOG_TAG, "BM xmhxj debug : %s/%s/present fail \n",POWER_SUPPLY_SYSFS_PATH,name);  
  
                    }  
  
                }  
                KLOG_WARNING(LOG_TAG, "BM xmhxj debug :init batteryPresentPath : %s \n",mHealthdConfig->batteryPresentPath.c_str());  
  
...  
  
    // Typically the case for devices which do not have a battery and  
    // and are always plugged into AC mains.  
    if (!mBatteryDevicePresent) {  
        KLOG_WARNING(LOG_TAG, "No battery devices found\n");  
        hc->periodic_chores_interval_fast = -1;  
        hc->periodic_chores_interval_slow = -1;  
    } else {  
...  
}  

vendor\mediatek\proprietary\external\libshowlogo 充电图片显示服务代码
最终调用void show_battery_capacity(unsigned int capacity)函数为电量显示时调用。

现象

关机充电显示

log分析

抓取的异常串口log分析:

[    4.746224][  T165] init: BOOTPROF:      4746.206241:INIT:init  
  
[    4.803653][    T1] init: init 6: [4801][0]processing action (charger) from (/system/etc/init/hw/init.rc:1093)  
[    4.805786][    T1] init: init 21: [4801][0]starting service 'kpoc_charger'...  
  
[    4.816035][    T1] init: init 21: [4801][0]starting service 'health-hal-2-1'...  
  
[    4.817246][    T1] init: init 21: [4801][0]Opened file '/dev/kmsg', flags 1  
  
[    4.829566][    T1] init: init 21: [4828][0]processing action (charger) from (/vendor/etc/init/hw/init.mt8168.rc:71)  
[    4.895068][    T1] init: init 21: [4892][0]Command 'write /sys/bus/platform/devices/battery/FG_daemon_log_level 7' action=charger (/vendor/etc/init/hw/init.mt8168.rc:96) took 1ms and failed: Unable to write to file '/sys/bus/platform/devices/battery/FG_daemon_log_level': open() failed: Permission denied  
[    4.900952][    T1] init: init 21: [4900][0]processing action (charger) from (/vendor/etc/init/hw/init.mt8168.usb.rc:73)  
[    4.925939][  T206] android.hardwar: healthd: BM xmhxj debug :INIT 111 name .  
[    4.926948][  T206] android.hardwar: healthd: BM xmhxj debug :INIT 111 name ..  
[    4.927952][  T206] android.hardwar: healthd: No battery devices found  
[    4.932247][    T1] init: init 21: [4931][0]processing action (charger) from (/vendor/etc/init/hw/init.project.rc:58)  
[    4.935972][    T1] init: init 21: [4934][0]starting service 'fuelgauged'...  
[    4.937344][  T206] health@2.1-serv: healthd: BM xmhxj debug :mHealthdConfig->batteryPresentPath.isEmpty() 1  
[    4.938631][  T206] health@2.1-serv: healthd: BM xmhxj debug :mHealthdConfig->batteryPresentPath   
[    4.939981][  T163] init: init 23: [4939][8]ReapLogT PropSet [sys.usb.configfs]=[0]4738 [sys.lmk.reportkills]=[1]4740 [init.svc.kpoc_charger]=[running]4812 [ro.boottime.kpoc_charger]=[4808788307]4813 [init.svc_debug_pid.kpoc_charger]=[204]4813 [init.svc.health-hal-2-1]=[running]4826 [ro.boottime.health-hal-2-1]=[4824270692]4827 [init.svc_debug_pid.health-hal-2-1]=[206]4828 [hwservicemanager.ready]=[true]4912 [sys.usb.configfs]=[1]4923 [sys.usb.controller]=[11201000.usb]4924 [vendor.usb.acm_cnt]=[0]4926 [vendor.usb.acm_port0]=[]4927 [vendor.usb.acm_port1]=[]4928 [vendor.usb.acm_enable]=[0]4928 [vendor.usb.clear]=[boot]4929 [sys.usb.config]=[hid]4930 Done  
[    4.944828][  T206] health@2.1-serv: healthd: BM xmhxj debug : mHealthdConfig->batteryPresentPath.isEmpty() mBatteryDevicePresent 0  
[    4.948799][  T206] health@2.1-serv: healthd: BM xmhxj debug : props.batteryPresent 0  
[    4.949877][  T206] health@2.1-serv: healthd: xmhxj debug : battery none chg=  
[    4.950707][    T1] init: init 6: [4948][0]starting service 'fuelgauged_nvram'...  
[    4.950816][  T206] health@2.1-serv: healthd: BM xmhxj debug :mHealthdConfig->batteryPresentPath.isEmpty() 1  

从log中可以知道初始化时获取power_supply节点下的信息为空

[    4.925939][  T206] android.hardwar: healthd: BM xmhxj debug :INIT 111 name .  
[    4.926948][  T206] android.hardwar: healthd: BM xmhxj debug :INIT 111 name ..  
[    4.927952][  T206] android.hardwar: healthd: No battery devices found  
[    4.937344][  T206] health@2.1-serv: healthd: BM xmhxj debug :mHealthdConfig->batteryPresentPath.isEmpty() 1  

导致

health@2.1-serv: healthd: xmhxj debug : battery none chg=

使得关机充电时充电信息的获取无法从healthd中得到。使得加载为0%

而比较正常的开机log为:

[   12.834990][    T1] init: init 23: [12681][0]starting service 'health-hal-2-1'...
...
[   13.094853][  T426] android.hardwar: healthd: BM xmhxj debug :INIT 111 name .
[   13.095843][  T426] android.hardwar: healthd: BM xmhxj debug :INIT 111 name ..
[   13.096817][  T426] android.hardwar: healthd: BM xmhxj debug :INIT 111 name mtk-gauge
[   13.123017][  T426] android.hardwar: healthd: BM xmhxj debug :INIT 222 name mtk-gauge
[   13.124642][  T426] android.hardwar: healthd: BM xmhxj debug :INIT 333 readPowerSupplyType 0
[   13.145360][  T426] android.hardwar: healthd: BM xmhxj debug :INIT 111 name battery
[   13.146437][  T426] android.hardwar: healthd: BM xmhxj debug :INIT 222 name battery
[   13.170693][  T426] android.hardwar: healthd: BM xmhxj debug :INIT 333 readPowerSupplyType 4
[   13.172061][  T426] android.hardwar: healthd: BM xmhxj debug :INIT 333 name battery
[   13.205482][  T426] android.hardwar: healthd: BM xmhxj debug :INIT 444 name battery

对策

在void BatteryMonitor::init(struct healthd_config hc)初始化函数中添加延迟
具体修改如下:

void BatteryMonitor::init(struct healthd_config *hc) {  
     String8 path;  
     char pval[PROPERTY_VALUE_MAX];  
+  
+    nsecs_t now;  
+    nsecs_t start_t;  
+    now = nanoseconds_to_seconds(systemTime());  
+    KLOG_WARNING(LOG_TAG, " BM xmhxj debug :now  %ld\n", (long)now);  
+  //获取系统时间,开机时间运行小于15s不断循环
+    while((nanoseconds_to_seconds(systemTime()) <= 15 )); 
+    start_t = nanoseconds_to_seconds(systemTime());  
+    KLOG_WARNING(LOG_TAG, " BM xmhxj debug : start_t  %ld\n", (long)start_t);  
+    KLOG_WARNING(LOG_TAG, " BM xmhxj debug : 222 now  %ld\n", (long)(start_t - now));      
 
    mHealthdConfig = hc;  
     std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(POWER_SUPPLY_SYSFS_PATH), closedir);  
     if (dir == NULL) {  
...
}

截取log如下:

    Line 2872: [    5.034645][  T195] android.hardwar: healthd:  BM xmhxj debug :now  5
	Line 5280: [   16.001043][  T195] android.hardwar: healthd:  BM xmhxj debug : start_t  16
	Line 5281: [   16.001955][  T195] android.hardwar: healthd:  BM xmhxj debug : 222 now  11
	Line 5282: [   16.003457][  T195] android.hardwar: healthd: BM xmhxj debug :INIT 111 name .
	Line 5283: [   16.004371][  T195] android.hardwar: healthd: BM xmhxj debug :INIT 111 name ..
	Line 5284: [   16.005314][  T195] android.hardwar: healthd: BM xmhxj debug :INIT 111 name mtk-gauge
	Line 5285: [   16.006321][  T195] android.hardwar: healthd: BM xmhxj debug :INIT 222 name mtk-gauge
	Line 5286: [   16.007688][  T195] android.hardwar: healthd: BM xmhxj debug :INIT 333 readPowerSupplyType 0
	Line 5287: [   16.008824][  T195] android.hardwar: healthd: BM xmhxj debug :INIT 111 name battery
	Line 5288: [   16.009910][  T195] android.hardwar: healthd: BM xmhxj debug :INIT 222 name battery
	Line 5289: [   16.011025][  T195] android.hardwar: healthd: BM xmhxj debug :INIT 333 readPowerSupplyType 4

总结

由于项目创建时的一些客制化修改导致关机充电服务过早的被调用,而底层kernel中的power_supply相关的节点设备(battery)等还未初始化注册,使得上层的初始化时读取的power_supply下的节点为空导致。解决方式为:在上层的初始化中添加延迟等待,模拟到正常开机时调用的时刻再执行节点设备获取。

本人有道笔记记录同步:
1:http://note.youdao.com/noteshare?id=418b40be3ff208d70cc80b9ff3f21b55&sub=8751EEB0DD0E47A28ADD474328B26DC6

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-08-06 10:55:38  更:2022-08-06 10:59:42 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 4:36:51-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码