• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 众包
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

RomanLin

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

【Android】应用电池使用情况(App battery usage)逻辑简要分析

前言

简介

在 "Settings -> App info -> App battery usage" 界面,可以设置应用电池使用情况,通常有以下三种设置选项:

  1. 不限制应用电池使用
  2. 电池优化
  3. 限制应用电池使用

正文

Qcom

下面分析高通机型(Android 13)如何默认开启电池优化(不限制电池使用):

定位到packages/app/Settings/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java

public class BatteryOptimizeUtils {
    private static final String TAG = "BatteryOptimizeUtils";
    private static final String UNKNOWN_PACKAGE = "unknown";

    @VisibleForTesting AppOpsManager mAppOpsManager;
    @VisibleForTesting BatteryUtils mBatteryUtils;
    @VisibleForTesting PowerAllowlistBackend mPowerAllowListBackend;
    @VisibleForTesting int mMode;
    @VisibleForTesting boolean mAllowListed;

    private final String mPackageName;
    private final int mUid;

    // If current user is admin, match apps from all users. Otherwise, only match the currect user.
    private static final int RETRIEVE_FLAG_ADMIN =
            PackageManager.MATCH_ANY_USER
                | PackageManager.MATCH_DISABLED_COMPONENTS
                | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
    private static final int RETRIEVE_FLAG =
            PackageManager.MATCH_DISABLED_COMPONENTS
                | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;

    // Optimization modes.
    static final int MODE_UNKNOWN = 0;
    static final int MODE_RESTRICTED = 1;
    static final int MODE_UNRESTRICTED = 2;
    static final int MODE_OPTIMIZED = 3;

    @IntDef(prefix = {"MODE_"}, value = {
        MODE_UNKNOWN,
        MODE_RESTRICTED,
        MODE_UNRESTRICTED,
        MODE_OPTIMIZED,
    })
    @Retention(RetentionPolicy.SOURCE)
    static @interface OptimizationMode {}

    public BatteryOptimizeUtils(Context context, int uid, String packageName) {
        mUid = uid;
        mPackageName = packageName;
        mAppOpsManager = context.getSystemService(AppOpsManager.class);
        mBatteryUtils = BatteryUtils.getInstance(context);
        mPowerAllowListBackend = PowerAllowlistBackend.getInstance(context);
        mMode = mAppOpsManager
                .checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mPackageName);
        mAllowListed = mPowerAllowListBackend.isAllowlisted(mPackageName);
    }

    /** Gets the {@link OptimizationMode} based on mode and allowed list. */
    @OptimizationMode
    public static int getAppOptimizationMode(int mode, boolean isAllowListed) {
        if (!isAllowListed && mode == AppOpsManager.MODE_IGNORED) {
            return MODE_RESTRICTED;
        } else if (isAllowListed && mode == AppOpsManager.MODE_ALLOWED) {
            return MODE_UNRESTRICTED;
        } else if (!isAllowListed && mode == AppOpsManager.MODE_ALLOWED) {
            return MODE_OPTIMIZED;
        } else {
            return MODE_UNKNOWN;
        }
    }

    /** Gets the {@link OptimizationMode} for associated app. */
    @OptimizationMode
    public int getAppOptimizationMode() {
        refreshState();
        return getAppOptimizationMode(mMode, mAllowListed);
    }

    /** Resets optimization mode for all applications. */
    public static void resetAppOptimizationMode(
            Context context, IPackageManager ipm, AppOpsManager aom) {
        resetAppOptimizationMode(context, ipm, aom,
                PowerAllowlistBackend.getInstance(context), BatteryUtils.getInstance(context));
    }

    /** Sets the {@link OptimizationMode} for associated app. */
    public void setAppUsageState(@OptimizationMode int mode) {
        if (getAppOptimizationMode(mMode, mAllowListed) == mode) {
            Log.w(TAG, "set the same optimization mode for: " + mPackageName);
            return;
        }
        setAppUsageStateInternal(mode, mUid, mPackageName, mBatteryUtils, mPowerAllowListBackend);
    }

    /**
     * Return {@code true} if package name is valid (can get an uid).
     */
    public boolean isValidPackageName() {
        return mBatteryUtils.getPackageUid(mPackageName) != BatteryUtils.UID_NULL;
    }

    /**
     * Return {@code true} if this package is system or default active app.
     */
    public boolean isSystemOrDefaultApp() {
        mPowerAllowListBackend.refreshList();
        return isSystemOrDefaultApp(mPowerAllowListBackend, mPackageName);
    }

    /**
      * Gets the list of installed applications.
      */
    public static ArraySet<ApplicationInfo> getInstalledApplications(
            Context context, IPackageManager ipm) {
        final ArraySet<ApplicationInfo> applications = new ArraySet<>();
        final UserManager um = context.getSystemService(UserManager.class);
        for (UserInfo userInfo : um.getProfiles(UserHandle.myUserId())) {
            try {
                @SuppressWarnings("unchecked")
                final ParceledListSlice<ApplicationInfo> infoList = ipm.getInstalledApplications(
                        userInfo.isAdmin() ? RETRIEVE_FLAG_ADMIN : RETRIEVE_FLAG,
                        userInfo.id);
                if (infoList != null) {
                    applications.addAll(infoList.getList());
                }
            } catch (Exception e) {
                Log.e(TAG, "getInstalledApplications() is failed", e);
                return null;
            }
        }
        // Removes the application which is disabled by the system.
        applications.removeIf(
                info -> info.enabledSetting != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER
                    && !info.enabled);
        return applications;
    }

    @VisibleForTesting
    static void resetAppOptimizationMode(
            Context context, IPackageManager ipm, AppOpsManager aom,
            PowerAllowlistBackend allowlistBackend, BatteryUtils batteryUtils) {
        final ArraySet<ApplicationInfo> applications = getInstalledApplications(context, ipm);
        if (applications == null || applications.isEmpty()) {
            Log.w(TAG, "no data found in the getInstalledApplications()");
            return;
        }

        allowlistBackend.refreshList();
        // Resets optimization mode for each application.
        for (ApplicationInfo info : applications) {
            final int mode = aom.checkOpNoThrow(
                    AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, info.uid, info.packageName);
            @OptimizationMode
            final int optimizationMode = getAppOptimizationMode(
                    mode, allowlistBackend.isAllowlisted(info.packageName));
            // Ignores default optimized/unknown state or system/default apps.
            if (optimizationMode == MODE_OPTIMIZED
                    || optimizationMode == MODE_UNKNOWN
                    || isSystemOrDefaultApp(allowlistBackend, info.packageName)) {
                continue;
            }

            // Resets to the default mode: MODE_OPTIMIZED.
            setAppUsageStateInternal(MODE_OPTIMIZED, info.uid, info.packageName, batteryUtils,
                    allowlistBackend);
        }
    }

    String getPackageName() {
        return mPackageName == null ? UNKNOWN_PACKAGE : mPackageName;
    }

    private static boolean isSystemOrDefaultApp(
            PowerAllowlistBackend powerAllowlistBackend, String packageName) {
        return powerAllowlistBackend.isSysAllowlisted(packageName)
                || powerAllowlistBackend.isDefaultActiveApp(packageName);
    }

    private static void setAppUsageStateInternal(
            @OptimizationMode int mode, int uid, String packageName, BatteryUtils batteryUtils,
            PowerAllowlistBackend powerAllowlistBackend) {
        if (mode == MODE_UNKNOWN) {
            Log.d(TAG, "set unknown app optimization mode.");
            return;
        }

        // MODE_RESTRICTED = AppOpsManager.MODE_IGNORED + !allowListed
        // MODE_UNRESTRICTED = AppOpsManager.MODE_ALLOWED + allowListed
        // MODE_OPTIMIZED = AppOpsManager.MODE_ALLOWED + !allowListed
        final int appOpsManagerMode =
                mode == MODE_RESTRICTED ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED;
        final boolean allowListed = mode == MODE_UNRESTRICTED;

        setAppOptimizationModeInternal(appOpsManagerMode, allowListed, uid, packageName,
                    batteryUtils, powerAllowlistBackend);
    }

    private static void setAppOptimizationModeInternal(
            int appStandbyMode, boolean allowListed, int uid, String packageName,
            BatteryUtils batteryUtils, PowerAllowlistBackend powerAllowlistBackend) {
        try {
            batteryUtils.setForceAppStandby(uid, packageName, appStandbyMode);
            if (allowListed) {
                powerAllowlistBackend.addApp(packageName);
            } else {
                powerAllowlistBackend.removeApp(packageName);
            }
        } catch (Exception e) {
            Log.e(TAG, "set OPTIMIZATION MODE failed for " + packageName, e);
        }
    }

    private void refreshState() {
        mPowerAllowListBackend.refreshList();
        mAllowListed = mPowerAllowListBackend.isAllowlisted(mPackageName);
        mMode = mAppOpsManager
                .checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mPackageName);
        Log.d(TAG, String.format("refresh %s state, allowlisted = %s, mode = %d",
                mPackageName, mAllowListed, mMode));
    }
}

通过分析 BatteryOptimizeUtils.java 的 refreshState() 方法,可以知道电池使用情况主要是由以下两种方式判断:

  1. 使用 mPowerAllowListBackend.isAllowlisted(mPackageName) 判断
  2. 判断应用是否拥有 AppOpsManager.OP_RUN_ANY_IN_BACKGROUND 权限

对于方式 1,定位到路径frameworks/base/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java:

public static PowerAllowlistBackend getInstance(Context context) {
    if (sInstance == null) {
        sInstance = new PowerAllowlistBackend(context);
    }
    return sInstance;
}

/**
  * Check if target package is in System allow list
*/
public boolean isSysAllowlisted(String pkg) {
    return mSysAllowlistedApps.contains(pkg);// 若是系统应用,默认允许不限制
}

/**
 * Check if target package is in allow list
 */
public boolean isAllowlisted(String pkg) {
    if (mAllowlistedApps.contains(pkg)) {// 判断普通应用是否在白名单中
        return true;
    }

    if (isDefaultActiveApp(pkg)) {// 判断是否是设备所有者应用
        return true;
    }

    return false;
}

/**
 * Check if it is default active app in multiple area(i.e. SMS, Dialer, Device admin..)
 */
public boolean isDefaultActiveApp(String pkg) {
    // Additionally, check if pkg is default dialer/sms. They are considered essential apps and
    // should be automatically allowlisted (otherwise user may be able to set restriction on
    // them, leading to bad device behavior.)

    if (mDefaultActiveApps.contains(pkg)) {
        return true;
    }

    final DevicePolicyManager devicePolicyManager = mAppContext.getSystemService(DevicePolicyManager.class);
    if (devicePolicyManager.packageHasActiveAdmins(pkg)) {
        return true;
    }

    return false;
}

/**
 * Add app into power save allow list.
 * @param pkg packageName
 */
public void addApp(String pkg) {
    try {
        mDeviceIdleService.addPowerSaveWhitelistApp(pkg);
        mAllowlistedApps.add(pkg);
    } catch (RemoteException e) {
        Log.w(TAG, "Unable to reach IDeviceIdleController", e);
    }
}

/**
 * Remove package from power save allow list.
 * @param pkg
 */
public void removeApp(String pkg) {
    try {
        mDeviceIdleService.removePowerSaveWhitelistApp(pkg);
        mAllowlistedApps.remove(pkg);
    } catch (RemoteException e) {
        Log.w(TAG, "Unable to reach IDeviceIdleController", e);
    }
}

针对上述代码的分析,易知只需要调用 PowerAllowlistBackend.java 的 addApp(String pkg) 方法,即可将应用添加到不限制电池使用白名单中。可以使用以下代码进行调用:

private static final String BATTERY_OPTIMIZE_UTILS_CLASS_NAME =
            "com.android.settingslib.fuelgauge.BatteryOptimizeUtils";
private static final String ADD_APP_METHOD_NAME = "addApp";

private enum PowerAllowPkgName {
    PKG1("包名1"),
    PKG2("包名2");

    private final String pkgName;

    PowerAllowPkgName(String pkgName) {
        this.pkgName = pkgName;
    }

    @NonNull
    @Override
    public String toString() {
        return pkgName;
    }
}

public static boolean judgeApplicationInstalled(Context context, String packageName) {
    boolean isInstalled = false;
    try {
        ApplicationInfo ai = context.getPackageManager().getApplicationInfo(packageName, 0);
        isInstalled = (ai != null);
    } catch (PackageManager.NameNotFoundException e) {
        isInstalled = false;
    }
    return isInstalled;
}

private void addVendorAppToPowerAllowList() {
    for (PowerAllowPkgName pkgName: PowerAllowPkgName.values()) {
        if (!judgeApplicationInstalled(mContext, pkgName.toString())) {
            continue;
        }
        LogUtil.d("Add " + pkgName.toString() + " to power allow list.");
        addAppToPowerAllowList(pkgName.toString());
    }
}

private void addAppToPowerAllowList(String packageName) {
    try {
        ClassLoader classLoader = mContext.getClassLoader();
        @SuppressLint("PrivateApi") Class<?> pwerAllowlistBackendClass = Class.forName(
                BATTERY_OPTIMIZE_UTILS_CLASS_NAME,
                true,
                classLoader);
        LogUtil.d("Load PwerAllowlistBackend successfully: "
                + pwerAllowlistBackendClass.getName());

        Method getInstanceMethod = pwerAllowlistBackendClass.getMethod(
                "getInstance", Context.class);
        LogUtil.d("Find getInstance method: " + getInstanceMethod);

        Object instance = getInstanceMethod.invoke(null, getAppContext());
        LogUtil.d("instance = " + instance);

        Method addAppMethod = pwerAllowlistBackendClass.getDeclaredMethod(
                ADD_APP_METHOD_NAME, String.class);
        addAppMethod.setAccessible(true);

        addAppMethod.invoke(instance, packageName);
        LogUtil.d("Add app " + packageName + " to power allow list successfully.");
    } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException |
             IllegalAccessException e) {
        e.printStackTrace();
    }
}

对于方式 2,可以使用以下方法授予权限:

 private void grantRunAnyInBackgroundPermission() {
    for (PowerAllowPkgName pkgName: PowerAllowPkgName.values()) {
        setMode(pkgName.toString(), AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, true);
    }
}

public boolean setMode(String packageName, int code, boolean newState) {
    try {
        PackageManager pm = getPackageManager();
        ApplicationInfo ai = pm.getApplicationInfo(packageName, PackageManager.GET_ACTIVITIES);
        AppOpsManager mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
        mAppOpsManager.setMode(code, ai.uid, packageName, newState
                ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_ERRORED);
        return true;
    } catch (PackageManager.NameNotFoundException e) {
        e.printStackTrace();
        return false;
    }
}

但是在高通 Android 13 的设备上,会弹出询问 "Let app always run in background?" 的对话框,定位到src/com/android/settings/fuelgauge/RequestIgnoreBatteryOptimizations.java

// 这是一个继承自 AlertActivity 的界面,所以在检测到新安装的应用有授予电池优化权限时
// 核心逻辑是询问是否允许总是允许在后台(Let app always run in background),即不限制电池使用
@Override
public void onClick(DialogInterface dialog, int which) {
    switch (which) {
        case BUTTON_POSITIVE:
            mPowerWhitelistManager.addToWhitelist(mPackageName);
            break;
        case BUTTON_NEGATIVE:
            break;
    }
}

对于点击确定按钮的逻辑,定位到路径 frameworks/base/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java:

/**
 * Add the specified package to the permanent power save whitelist.
 *
 * @deprecated Use {@link PowerExemptionManager#addToPermanentAllowList(String)} instead
 */
@Deprecated
@RequiresPermission(android.Manifest.permission.DEVICE_POWER)
public void addToWhitelist(@NonNull String packageName) {
    mPowerExemptionManager.addToPermanentAllowList(packageName);
}

/**
 * Add the specified packages to the permanent power save whitelist.
 *
 * @deprecated Use {@link PowerExemptionManager#addToPermanentAllowList(List)} instead
 */
@Deprecated
@RequiresPermission(android.Manifest.permission.DEVICE_POWER)
public void addToWhitelist(@NonNull List<String> packageNames) {
    mPowerExemptionManager.addToPermanentAllowList(packageNames);
}

/**
 * Get a list of app IDs of app that are whitelisted. This does not include temporarily
 * whitelisted apps.
 *
 * @param includingIdle Set to true if the app should be whitelisted from device idle as well
 *                      as other power save restrictions
 * @deprecated Use {@link PowerExemptionManager#getAllowListedAppIds(boolean)} instead
 * @hide
 */
@Deprecated
@NonNull
public int[] getWhitelistedAppIds(boolean includingIdle) {
    return mPowerExemptionManager.getAllowListedAppIds(includingIdle);
}

/**
 * Returns true if the app is whitelisted from power save restrictions. This does not include
 * temporarily whitelisted apps.
 *
 * @param includingIdle Set to true if the app should be whitelisted from device
 *                      idle as well as other power save restrictions
 * @deprecated Use {@link PowerExemptionManager#isAllowListed(String, boolean)} instead
 * @hide
 */
@Deprecated
public boolean isWhitelisted(@NonNull String packageName, boolean includingIdle) {
    return mPowerExemptionManager.isAllowListed(packageName, includingIdle);
}

/**
 * Remove an app from the permanent power save whitelist. Only apps that were added via
 * {@link #addToWhitelist(String)} or {@link #addToWhitelist(List)} will be removed. Apps
 * whitelisted by default by the system cannot be removed.
 *
 * @param packageName The app to remove from the whitelist
 * @deprecated Use {@link PowerExemptionManager#removeFromPermanentAllowList(String)} instead
 */
@Deprecated
@RequiresPermission(android.Manifest.permission.DEVICE_POWER)
public void removeFromWhitelist(@NonNull String packageName) {
    mPowerExemptionManager.removeFromPermanentAllowList(packageName);
}

由注释信息,可知这个类的方法已经过时了,本文仍以 PowerWhitelistManager.java 进行讲解,可以使用 PowerExemptionManager.java 进行替代,读者可自行翻阅,此不赘述。

如果想不弹窗默认授予,可以修改以下代码解决:

private void addVendorAppToPowerAllowList() {
    for (PowerAllowPkgName pkgName: PowerAllowPkgName.values()) {
        if (!judgeApplicationInstalled(mContext, pkgName.toString())) {
            continue;
        }
        LogUtil.d("Add " + pkgName.toString() + " to power allow list.");
        if (Build.VERSION.SDK_INT <= 33) {
            PowerWhitelistManager powerWhitelistManager = mContext.getSystemService(PowerWhitelistManager.class);
            powerWhitelistManager.addToWhitelist(pkgName.toString());
        } else {
            addAppToPowerAllowList(pkgName.toString());
        }
    }
}

MTK

定位到\vendor\mediatek\proprietary\packages\apps\MtkSettings\src\com\android\settings\fuelgauge\BatteryOptimizeUtils.java:

// 判断是否为不受限制的应用
static boolean isSystemOrDefaultApp(
        Context context,
        PowerAllowlistBackend powerAllowlistBackend,
        String packageName,
        int uid) {
    return powerAllowlistBackend.isSysAllowlisted(packageName)
            // Always forced unrestricted apps are one type of system important apps.
            || getForceBatteryUnrestrictModeList(context).contains(packageName)// 通过属性设置默认配置名单
            || powerAllowlistBackend.isDefaultActiveApp(packageName, uid);
}

// 获取强制开启“电池优化”的应用名单
static List<String> getForceBatteryOptimizeModeList(Context context) {
    if (sBatteryOptimizeModeList == null) {
        sBatteryOptimizeModeList =
                Arrays.asList(
                        context.getResources()
                                .getStringArray(
                                        R.array.config_force_battery_optimize_mode_apps));// 通过该配置项进行静态修改
        // Begin: 通过以下代码,可以修改系统属性 persist.sys.customize.battery.optimize_apps 的值动态定制“电池优化”的应用名单
        String extraAppsProp = SystemProperties.get("persist.sys.customize.battery.optimize_apps", "");
        sBatteryOptimizeModeList = Stream.concat(
            sBatteryOptimizeModeList.stream(),
            Arrays.stream(extraAppsProp.split(","))
                .filter(app -> !TextUtils.isEmpty(app.trim()))
                .map(String::trim)
        ).collect(Collectors.toList());
        // End
    }
    return sBatteryOptimizeModeList;
}

// 获取强制开启“电池不受限”的应用名单
static List<String> getForceBatteryUnrestrictModeList(Context context) {
    if (sBatteryUnrestrictModeList == null) {
        sBatteryUnrestrictModeList =
                Arrays.asList(
                        context.getResources()
                                .getStringArray(
                                        R.array.config_force_battery_unrestrict_mode_apps));//通过该配置项可以进行静态修改
        // Begin: 通过以下代码,可以通过修改系统属性 persist.sys.customize.battery.unrestricted_apps 的值动态定制“电池不受限”的应用名单
        String extraAppsProp = SystemProperties.get("persist.sys.customize.battery.unrestricted_apps", "");
        sBatteryUnrestrictModeList = Stream.concat(
            sBatteryUnrestrictModeList.stream(),
            Arrays.stream(extraAppsProp.split(","))
                .filter(app -> !TextUtils.isEmpty(app.trim()))
                .map(String::trim)
        ).collect(Collectors.toList());
        // End
    }
    return sBatteryUnrestrictModeList;
}

// 设置应用属于哪种模式
private static void setAppUsageStateInternal(
        Context context,
        @OptimizationMode int mode,
        int uid,
        String packageName,
        BatteryUtils batteryUtils,
        PowerAllowlistBackend powerAllowlistBackend,
        Action action) {
    if (mode == MODE_UNKNOWN) {
        Log.d(TAG, "set unknown app optimization mode.");
        return;
    }

    // MODE_RESTRICTED = AppOpsManager.MODE_IGNORED + !allowListed
    // MODE_UNRESTRICTED = AppOpsManager.MODE_ALLOWED + allowListed
    // MODE_OPTIMIZED = AppOpsManager.MODE_ALLOWED + !allowListed
    final int appOpsManagerMode =
            mode == MODE_RESTRICTED ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED;
    final boolean allowListed = mode == MODE_UNRESTRICTED;

    setAppOptimizationModeInternal(
            context,
            appOpsManagerMode,
            allowListed,
            uid,
            packageName,
            batteryUtils,
            powerAllowlistBackend,
            action);
}

private void refreshState() {
    mPowerAllowListBackend.refreshList();
    mAllowListed = mPowerAllowListBackend.isAllowlisted(mPackageName);
    mMode = mAppOpsManager
            .checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mPackageName);
    Log.d(TAG, String.format("refresh %s state, allowlisted = %s, mode = %d",
            mPackageName, mAllowListed, mMode));
}

定位到路径/packages/apps/MtkSettings/res/values/config.xml,可以对配置项进行静态修改:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- 覆盖电池无限制模式应用列表 -->
    <string-array name="config_force_battery_unrestrict_mode_apps" translatable="false">
        <item>packageName1</item>
        <item>packageName2</item>
    </string-array>
</resources>

posted on 2026-01-03 16:18  RomanLin  阅读(15)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3