Android11添加AIDL系统服务及HIDL接口服务

软件平台:Android11

硬件平台:QCS6125

    直接上需求:首先添加自定义系统服务并且开机自启动;其次该服务要调用HW层service的方法。

    前半部分之前添加过N次,不是难点,后半部分的实现最开始也是想借助binder通信,直接去调用接口,遇到了一大堆selinux权限问题,这是google针对android8.0之后新添加的策略,使用了三个binder,分别是标准/dev/binder该节点主要用于System分区的系统服务去注册使用,/dev/hwbinder该节点主要用于运行于HW的服务注册使用,/dev/vndbinder该节点主要用于运行在vendor分区的服务注册使用,而需求中所说的HW层的服务注册的是vndbinder,自定义的服务是binder,因此系统层面是隔离的,无法通过binder直接通信,只能通过hwbinder去迂回,因为binder可以跟hwbinder通信,而hwbinder可以和vndbinder交互,从而达到binder服务和vndbinder服务通信的目的。

前半部分(系统服务)的实现:

1、创建frameworks/base/core/java/android/app/yfd/目录,建立如下文件:

HandWriteManager.java

/*************************************************************************
	* File Name: HandWriteManager.java
	* Author: kongbo 
	* Mail: shenyoutianji@126.com 
	* Created Time: 2022年11月21日 星期一 17时17分35秒
 ************************************************************************/
package android.app.yfd;

import android.content.Context;

import android.os.RemoteException;

import android.util.Slog;

public class HandWriteManager {

    Context mContext;

    IHandWriteManager mService;

    static final String TAG = "HandWriteManager";

    public HandWriteManager(Context context, IHandWriteManager service) {

        mContext = context;

        mService = service;

        if (mService != null)

            Slog.d(TAG, " mService not null");

    }
		
	public void setRefreshMode(int mode) {

        if (mService != null) {
            Slog.d(TAG, " Will setRefreshMode");
            try {
                mService.setRefreshMode(mode);

            } catch (RemoteException e) {

                Slog.d(TAG, "RemoteException " + e);

            }

        }

        //return null;
    }

    public void commitRefreshArea(int x, int y, int h, int w) {
        if (mService != null) {
            Slog.d(TAG, " Will commitRefreshArea");
            try {
                mService.commitRefreshArea(x, y, h, w);

            } catch (RemoteException e) {

                Slog.d(TAG, "RemoteException " + e);

            }

        }
    }

}

IHandWriteManager.aidl

/**

* Hand Writes Service

*/

package android.app.yfd;

interface IHandWriteManager {

    void setRefreshMode(int mode);

    void commitRefreshArea(int x, int y, int h, int w);

}

frameworks/base/services/core/java/com/android/server目录添加HandWriteManagerService.java

/*************************************************************************
	* File Name: HandWriteManagerService.java
	* Author: kongbo
	* Mail:nubility@gmail.com 
	* Created Time: 2022年11月21日 星期一 17时36分04秒
 ************************************************************************/
package com.android.server;

import android.app.yfd.IHandWriteManager;

import android.content.Context;

import android.os.Build;

import android.util.Slog;

import java.io.BufferedReader;

import java.io.BufferedWriter;

import java.io.FileWriter;

import java.io.FileReader;

import java.io.IOException;

public class HandWriteManagerService extends IHandWriteManager.Stub {

    private Context mContext;

    static final String TAG = "HandWriteManagerService";

    private boolean Debug = true;

    private final int LEISURE_MODE = 1;

    private final int WRITE_MODE = 2;

    private final int NETWORK_MODE = 3;

    private native void HandWriteServiceNative_setRefreshMode(int mode);
    private native void HandWriteServiceNative_Init();
    private native void HandWriteServiceNative_commitRefreshArea(int x, int y, int h, int w);

	public HandWriteManagerService(Context context) {
        //Slog.e(TAG," ====== call native method HandWriteServiceNative_Init");
        HandWriteServiceNative_Init();
        mContext = context;
		/*try {
            Slog.e(TAG," ====== Runtime will exec ======");
            Runtime.getRuntime().exec("su");
            Runtime.getRuntime().exec("vndservice call display.qservice 47 i32 2");
            Slog.e(TAG," ====== Runtime exec end ======");
		} catch (IOException e) {
            Slog.e(TAG," ====== Runtime exec has exception ======");
            e.printStackTrace();
		}*/
    }

    @Override
    public void setRefreshMode(int mode) {
        BufferedWriter bufWriter = null;

        try {

            //bufWriter = new BufferedWriter(new FileWriter(LMAP_BRIGHTNESS_PATH));

            if (Debug)
                Slog.e(TAG," ====== write brightness:");

            HandWriteServiceNative_setRefreshMode(mode);

            //bufWriter.write(String.valueOf(brightness));

            //bufWriter.close();

        } catch (Exception e) {

            e.printStackTrace();

        }

    }

    @Override
    public void commitRefreshArea(int x, int y, int h, int w) {
        HandWriteServiceNative_commitRefreshArea(x, y, h, w);
    }
}

向SystemServer完成注册:

diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 3d8328bd193..1279df76dd5 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -89,6 +89,7 @@ import com.android.internal.util.ConcurrentUtils;
 import com.android.internal.util.EmergencyAffordanceManager;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.widget.ILockSettings;
+import com.android.server.HandWriteManagerService;
 import com.android.server.am.ActivityManagerService;
 import com.android.server.appbinding.AppBindingService;
 import com.android.server.attention.AttentionManagerService;
@@ -1614,6 +1615,16 @@ public final class SystemServer {
             }
             t.traceEnd();
 
+            t.traceBegin("StartHandWriteManagerService");
+            try {
+                Slog.i(TAG, "HandWriteManagerService is create");
+                ServiceManager.addService(Context.HAND_WRITE_SERVICE,
+                        new HandWriteManagerService(context));
+            } catch (Throwable e) {
+                reportWtf("starting HandWriteManagerService", e);
+            }
+            t.traceEnd();
+
             t.traceBegin("StartNotificationManager");
             mSystemServiceManager.startService(NotificationManagerService.class);
             SystemNotificationChannels.removeDeprecated(context);
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index e599a5ce81e..38473ee5e6e 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -43,6 +43,8 @@ import android.app.usage.IUsageStatsManager;
 import android.app.usage.NetworkStatsManager;
 import android.app.usage.StorageStatsManager;
 import android.app.usage.UsageStatsManager;
+import android.app.yfd.HandWriteManager;
+import android.app.yfd.IHandWriteManager;
 import android.appwidget.AppWidgetManager;
 import android.bluetooth.BluetoothManager;
 import android.companion.CompanionDeviceManager;
@@ -1189,6 +1191,17 @@ public final class SystemServiceRegistry {
                     }
                 });
 
+        registerService(Context.HAND_WRITE_SERVICE, HandWriteManager.class,
+                new CachedServiceFetcher<HandWriteManager>() {
+                    @Override
+                    public HandWriteManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        IBinder b = ServiceManager.getServiceOrThrow(
+                                Context.HAND_WRITE_SERVICE);
+                        return new HandWriteManager(ctx, IHandWriteManager.Stub.asInterface(b));
+                    }
+                });
+
         registerService(Context.SLICE_SERVICE, SliceManager.class,
                 new CachedServiceFetcher<SliceManager>() {
                     @Override

在Context.java添加服务ID定义:

diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 8472144a92c..d9ee340cd55 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5024,6 +5024,15 @@ public abstract class Context {
      */
     public static final String CROSS_PROFILE_APPS_SERVICE = "crossprofileapps";
 
+    /**
+     * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.app.yfd.HandWriteManager} for handwriting
+     *
+     * @see #getSystemService(String)
+     * @see android.app.yfd.HandWriteManager
+     */
+    public static final String HAND_WRITE_SERVICE  = "hand_write";
+
     /**
      * Use with {@link #getSystemService} to retrieve a
      * {@link android.se.omapi.ISecureElementService}

此时增量编译代码,会提示api需要更新还有一个lint检测的报错,API更新执行make update-api指令,去掉lint检查需要修改framework/base/Android.bp如下:

diff --git a/Android.bp b/Android.bp
index bf6c99d0cf2..bb4a6a9a4e7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1219,7 +1219,8 @@ metalava_framework_docs_args = "--manifest $(location core/res/AndroidManifest.x
     "--api-lint-ignore-prefix android.icu. " +
     "--api-lint-ignore-prefix java. " +
     "--api-lint-ignore-prefix junit. " +
-    "--api-lint-ignore-prefix org. "
+    "--api-lint-ignore-prefix org. " +
+    "--api-lint-ignore-prefix android.app.yfd. "
 
 build = [
     "StubLibraries.bp",

在执行完make update-api之后,系统会自动更新api文件:

diff --git a/api/current.txt b/api/current.txt
index 952ccdad992..f91544dde68 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8106,6 +8106,37 @@ package android.app.usage {
 
 }
 
+package android.app.yfd {
+
+  public class HandWriteManager {
+    ctor public HandWriteManager(android.content.Context, android.app.yfd.IHandWriteManager);
+    method public void commitRefreshArea(int, int, int, int);
+    method public void setRefreshMode(int);
+  }
+
+  public interface IHandWriteManager extends android.os.IInterface {
+    method public void commitRefreshArea(int, int, int, int) throws android.os.RemoteException;
+    method public void setRefreshMode(int) throws android.os.RemoteException;
+  }
+
+  public static class IHandWriteManager.Default implements android.app.yfd.IHandWriteManager {
+    ctor public IHandWriteManager.Default();
+    method public android.os.IBinder asBinder();
+    method public void commitRefreshArea(int, int, int, int) throws android.os.RemoteException;
+    method public void setRefreshMode(int) throws android.os.RemoteException;
+  }
+
+  public abstract static class IHandWriteManager.Stub extends android.os.Binder implements android.app.yfd.IHandWriteManager {
+    ctor public IHandWriteManager.Stub();
+    method public android.os.IBinder asBinder();
+    method public static android.app.yfd.IHandWriteManager asInterface(android.os.IBinder);
+    method public static android.app.yfd.IHandWriteManager getDefaultImpl();
+    method public boolean onTransact(int, android.os.Parcel, android.os.Parcel, int) throws android.os.RemoteException;
+    method public static boolean setDefaultImpl(android.app.yfd.IHandWriteManager);
+  }
+
+}
+
 package android.appwidget {
 
   public class AppWidgetHost {
@@ -10190,6 +10221,7 @@ package android.content {
     field public static final String EUICC_SERVICE = "euicc";
     field public static final String FILE_INTEGRITY_SERVICE = "file_integrity";
     field public static final String FINGERPRINT_SERVICE = "fingerprint";
+    field public static final String HAND_WRITE_SERVICE = "hand_write";
     field public static final String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
     field public static final String INPUT_METHOD_SERVICE = "input_method";
     field public static final String INPUT_SERVICE = "input";
diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt
index 5f15216e840..c6047ab6ff4 100644
--- a/non-updatable-api/current.txt
+++ b/non-updatable-api/current.txt
@@ -8106,6 +8106,37 @@ package android.app.usage {
 
 }
 
+package android.app.yfd {
+
+  public class HandWriteManager {
+    ctor public HandWriteManager(android.content.Context, android.app.yfd.IHandWriteManager);
+    method public void commitRefreshArea(int, int, int, int);
+    method public void setRefreshMode(int);
+  }
+
+  public interface IHandWriteManager extends android.os.IInterface {
+    method public void commitRefreshArea(int, int, int, int) throws android.os.RemoteException;
+    method public void setRefreshMode(int) throws android.os.RemoteException;
+  }
+
+  public static class IHandWriteManager.Default implements android.app.yfd.IHandWriteManager {
+    ctor public IHandWriteManager.Default();
+    method public android.os.IBinder asBinder();
+    method public void commitRefreshArea(int, int, int, int) throws android.os.RemoteException;
+    method public void setRefreshMode(int) throws android.os.RemoteException;
+  }
+
+  public abstract static class IHandWriteManager.Stub extends android.os.Binder implements android.app.yfd.IHandWriteManager {
+    ctor public IHandWriteManager.Stub();
+    method public android.os.IBinder asBinder();
+    method public static android.app.yfd.IHandWriteManager asInterface(android.os.IBinder);
+    method public static android.app.yfd.IHandWriteManager getDefaultImpl();
+    method public boolean onTransact(int, android.os.Parcel, android.os.Parcel, int) throws android.os.RemoteException;
+    method public static boolean setDefaultImpl(android.app.yfd.IHandWriteManager);
+  }
+
+}
+
 package android.appwidget {
 
   public class AppWidgetHost {
@@ -10190,6 +10221,7 @@ package android.content
<think>我们正在处理一个Android系统开发问题。用户希望在Android 14中让HAL层的broadcastradio AIDL不参与编译,以便使用HIDL服务接口。根据引用内容,我们需要考虑以下几点: 1. 从引用[1]中,我们知道在Android P(9.0)开始,system和vendor分区之间的调用受到限制,违反CTS。因此,我们需要确保HAL实现位于vendor分区,而framework位于system分区,它们通过合法的接口通信。 2. 引用[2]展示了收音机服务的调用流程,涉及到的AIDL接口(如IRadioManager.aidl, IRadioService.aidl)和实现类(如RadioService.java, BroadcastRadioService.java)。这表明在当前的实现中,广播收音机服务使用了AIDL。 3. 引用[3]提到了hidl-gen工具,它可以生成不同语言类型的文件,包括Java和VTS等。 用户的需求是:在Android 14中,让HAL层的broadcastradio AIDL不参与编译,从而使用HIDL服务接口。这意味着用户希望避免使用AIDL,而继续使用HIDL来实现广播收音机的HAL。 在Android系统中,HAL层可以使用HIDLAIDL。从Android 11开始,Google鼓励使用AIDL作为新的HAL接口。但是,用户可能因为兼容性或特定需求希望继续使用HIDL。 因此,我们需要做的是: - 移除或禁用与broadcastradio HAL相关的AIDL接口的编译,并确保使用HIDL接口。 具体步骤可能包括: 1. **修改构建配置(Android.bp或Android.mk)**:找到编译AIDL接口的模块,并将其从编译中移除。通常,AIDL接口会通过`aidl_interface`模块定义,并在HAL实现中引用。 2. **确保HIDL接口被正确编译**:检查HIDL接口的定义(通常位于`hardware/interfaces/broadcastradio`目录下)是否被正确编译和包含。 3. **调整HAL实现**:确保HAL实现使用HIDL接口,而不是AIDL。这意味着HAL实现应该继承自HIDL接口类(如`IBroadcastRadio`)并实现其方法。 4. **调整Framework层的服务**:Framework层需要调用HIDL接口而不是AIDL接口。这可能需要修改`RadioManager`和相关的服务(如`BroadcastRadioService`)以使用HIDL。 然而,由于用户要求“让HAL层的broadcastradio aidl不参与编译”,我们重点关注构建配置的修改。 ### 步骤: 1. **定位broadcastradio AIDL的构建定义**: 在HAL层,AIDL接口通常定义在`hardware/interfaces`或`vendor`目录下。具体到broadcastradio,可能在`hardware/interfaces/broadcastradio/aidl`目录下。 在对应的模块定义文件(如`aidl_interface`的Android.bp)中,我们可以通过设置`enabled: false`来禁用该模块的编译。但是,由于系统可能依赖它,我们需要确保其他模块不依赖它,并用HIDL替代。 2. **修改构建配置**: 假设在`hardware/interfaces/broadcastradio/aidl/Android.bp`中有一个`aidl_interface`模块,我们可以通过添加`enabled`属性来控制是否编译。 例如,在Android.bp中: ``` aidl_interface { name: "android.hardware.broadcastradio", ..., enabled: false, // 添加这一行来禁用 } ``` 但是,在Android 14中,可能没有这样的模块,或者广播收音机已经默认使用AIDL。因此,我们需要查看具体代码位置。 另一种方法是,在设备特定的makefile中覆盖变量,禁止编译AIDL HAL。例如,在设备`device.mk`中,设置: ``` PRODUCT_USE_AIDL_BROADCASTRADIO_HAL := false ``` 然后在相关的Android.bp或Android.mk中,根据这个变量来决定是否编译AIDL接口。 3. **使用环境变量或产品变量控制**: 在Android构建系统中,我们可以通过设置环境变量或产品变量来控制模块的编译。 在广播收音机的HAL实现中,可能同时支持HIDLAIDL,并通过变量选择。例如,在`Android.bp`中可能有条件判断: ``` // 根据变量决定使用哪种接口 if (broadcastradio_use_hidl) { // 编译HIDL实现 } else { // 编译AIDL实现 } ``` 因此,我们需要定义变量`broadcastradio_use_hidl`为true。 4. **设备特定配置**: 在设备的产品配置文件(如`device/<vendor>/<product>/product.mk`)中,添加: ``` PRODUCT_PACKAGES += \ android.hardware.broadcastradio@<version>-impl ``` 同时,确保没有包含AIDL实现的包。 并且设置变量: ``` PRODUCT_PACKAGES += \ android.hardware.broadcastradio@<version>-service ``` 这里使用HIDL服务。 5. **处理依赖**: 由于framework层的服务可能默认使用AIDL,我们需要修改framework使其调用HIDL接口。这可能需要修改framework的代码(如`RadioManager`)和服务的实现(如`BroadcastRadioService`)。但是,用户的问题只提到HAL层,所以可能framework层已经同时支持两种方式?或者需要进行适配。 ### 注意事项: - 在禁用AIDL HAL后,需要确保HIDL HAL实现被正确编译和部署。 - 需要检查selinux策略,确保vendor分区中的HIDL服务可以被system分区的服务访问(通过binder调用)。 ### 具体操作: 1. 首先,确认HAL层使用HIDL实现。查看`hardware/interfaces/broadcastradio`目录下的HIDL接口定义(例如`types.hal`,`IBroadcastRadio.hal`等)和相应的实现(通常在`default`子目录下)。 2. 禁用AIDL HAL的编译。找到AIDL接口的构建定义(可能在`hardware/interfaces/broadcastradio/aidl`目录下),并在其Android.bp中添加`enabled: false`。或者,如果上层模块通过`override_android_filesystem_config`等机制控制,则需要在产品配置中禁用。 3. 在设备的产品配置中,确保只包含HIDL实现: ``` # 例如,在device.mk或类似文件中 PRODUCT_PACKAGES += android.hardware.broadcastradio@<version>-impl ``` 4. 如果系统服务(在`frameworks/base`中)默认使用AIDL,那么我们需要修改服务以使用HIDL。这可能涉及到修改`RadioManager`和`BroadcastRadioService`,将其中的AIDL调用改为HIDL调用。但是,这可能超出了用户当前问题的范围(用户只要求HAL层不编译AIDL)。因此,我们需要确认:如果HAL层提供了HIDL接口,那么系统服务是否已经支持调用HIDL?或者是否有一个适配层? 从引用[2]中,我们看到有一个`RadioManagerExt.java`,这可能是扩展和适配。因此,我们可能需要修改`RadioManagerExt`,使其通过HIDL来访问HAL。 5. 另外,注意引用[2]中提到的`RadioService.java`(在packages/apps/Car/Radio中)和`BroadcastRadioService.java`(在frameworks/base/services/core中)。这些服务可能也需要调整。 ### 总结步骤: 1. 修改构建配置,禁止编译broadcastradio AIDL HAL模块。 2. 确保HIDL HAL实现被正确编译并包含在设备映像中。 3. 调整系统服务(如果必要)以使用HIDL接口。 由于用户的要求集中在构建配置,我们主要关注前两步。 ### 修改构建配置的示例: 假设广播收音机的AIDL接口定义在`hardware/interfaces/broadcastradio/aidl`,我们可以通过以下方式禁用: 在`hardware/interfaces/broadcastradio/aidl/Android.bp`中: ``` aidl_interface { name: "android.hardware.broadcastradio", // ... 其他设置 enabled: false, // 添加此行禁用 } ``` 或者在产品的`BoardConfig.mk`中使用: ``` TARGET_USES_AIDL_BROADCASTRADIO := false ``` 然后在构建规则中根据该变量判断。 ### 如果系统默认使用AIDL,而我们强制使用HIDL: 可能需要同时修改设备配置,确保framework加载的是HIDL服务。这可能需要修改`init.rc`文件,启动HIDL服务而不是AIDL服务。 最后,由于Android 14可能已经默认使用AIDL,因此这种方式可能需要更多的修改。用户需要权衡是否值得,或者考虑迁移到AIDL。 ### 生成相关问题: §§相关问题§§ 1. 如何检查Android构建系统中某个模块是否被正确禁用? 2.Android中,如何切换HAL实现(HIDLAIDL之间)? 3. Android 14中,广播收音机的HAL默认使用AIDL还是HIDL? 4. 如何为广播收音机HAL实现HIDL接口? 5. 在禁用AIDL HAL后,如何解决SELinux权限问题? </think>### 在Android 14中禁用BroadcastRadio AIDL HAL并启用HIDL接口的方法 #### 1. **修改构建配置** 在设备特定配置(`device.mk`或`product.mk`)中覆盖AIDL HAL编译: ```makefile # 禁用BroadcastRadio AIDL HAL PRODUCT_PACKAGES := $(filter-out android.hardware.broadcastradio-service.example, $(PRODUCT_PACKAGES)) PRODUCT_PACKAGES := $(filter-out android.hardware.broadcastradio.aidl, $(PRODUCT_PACKAGES)) # 启用HIDL实现 PRODUCT_PACKAGES += android.hardware.broadcastradio@<版本号>-impl PRODUCT_PACKAGES += android.hardware.broadcastradio@<版本号>-service ``` 需将`<版本号>`替换为实际HIDL接口版本(如`2.0`)[^3]。 #### 2. **调整HAL接口选择** 在`RadioManagerExt.java`中强制使用HIDL接口: ```java // /packages/apps/Car/Radio/src/com/android/car/radio/platform/RadioManagerExt.java public void initialize() { // 跳过AIDL初始化 // mRadioManagerAidl = null; // 直接绑定HIDL服务 mRadioManagerHidl = IBroadcastRadio.getService(); // ... HIDL接口初始化逻辑 } ``` #### 3. **修改SELinux策略** 在`hal_broadcastradio_default.te`中允许vendor与system通信: ```sepolicy # 允许system访问vendor的HIDL接口 allow system hal_broadcastradio_default:dir { search }; allow system hal_broadcastradio_default:file { read open }; allow system hal_broadcastradio_hwservice:hwservice_manager { find }; ``` #### 4. **更新服务绑定逻辑** 在`BroadcastRadioService.java`中修改: ```java // /frameworks/base/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java public void onStart() { // 注释AIDL服务注册 // publishBinderService(Context.RADIO_SERVICE, new ServiceImpl()); // 注册HIDL服务 try { IBroadcastRadio.getService().linkToDeath(/*...*/); } catch (RemoteException e) { ... } } ``` #### 5. **验证模块配置** 在`Android.bp`中确保HIDL接口优先级高于AIDL: ```python hidl_interface { name: "android.hardware.broadcastradio@2.0", priority: 1, // 高于AIDL默认优先级 // ... } ``` ### 关键注意事项 1. **HIDL接口兼容性**:需确保HIDL接口版本与Android 14框架兼容(建议≥2.0版本)[^3] 2. **CTS/VTS测试**:禁用AIDL后需验证: ```sh atest VtsHalBroadcastradioV2_0TargetTest ``` 3. **系统分区隔离**:需确保所有HIDL调用符合`system`→`vendor`单向通信规则[^1] 4. **资源清理**:移除`packages/apps/Car/Radio`中所有AIDL相关实现文件[^2] > **编译验证命令**: > ```sh > m android.hardware.broadcastradio@<版本号>-service && > mmma -j16 hardware/interfaces/broadcastradio/<版本号>/ > ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值