AndroidO Treble架构下HIDL服务Java框架实现

前面介绍了HIDL服务在native层的实现过程,包括HIDL服务加载创建、服务注册、服务查询过程等,那么Java层是否也实现了相关的服务框架呢? 通常情况下,所有的Hal都实现在native层面,每个hal进程都是一个native进程,由init进程启动,在hal进程启动时会完成HIDL服务注册,Framework Server进程不一定完全是native进程,比如system_server进程,它运行在虚拟机环境中,由zygote进程fork而来,这时,Java层也需要请求HIDL服务,因此Android不仅在native层HIDL化了hal,在Java层同样也定义了相关的服务框架。

上图是Java层binder和hwbinder之间的类基础图对比。当我们定义一个.hal接口文件时,通过hidl-gen编译为Java文件后,将按上图中的类继承关系自动生成代码。

如上图所示,当我们定义IXXX.hal文件后,通过编译将在out/target/common/gen/JAVA_LIBRARIES目录下生成对应的IXXX.java,该文件按上述类继承关系自动生成相关代码,我们只需要定义一个XXXImp类,继承Stub并实现所有方法,然后在某个服务进程中创建一个XXXImp对象,并调用registerService()函数进行hidl服务注册,如下所示:

[java] view plain copy

  1. XXXImp mXXXImp = new XXXImp();  
  2. mXXXImp.registerAsService(”XXXImp”);  

这样就完成了一个Java层的hidl服务注册,当然在当前Android系统中,大部分还是native层的hidl服务,Java层的hidl服务还是比较少的。从上述可知,Java层的hidl服务包括2个步骤:

1. hidl服务对象创建;

2.hidl服务注册;

Java hidl服务创建过程

从上面的类继承图可知,hidl服务实现类继承于Stub,Stub又继承于HwBinder,因此创建一个XXXImp对象时,会调用HwBinder的构造函数。

frameworks\base\core\java\android\os\HwBinder.java

[java] view plain copy

  1. public HwBinder() {  
  2.     native_setup();  
  3.   
  4.     sNativeRegistry.registerNativeAllocation(  
  5.             this,  
  6.             mNativeContext);  
  7. }  

[java] view plain copy

  1. static {  
  2.     long freeFunction = native_init();  
  3.   
  4.     sNativeRegistry = new NativeAllocationRegistry(  
  5.             HwBinder.class.getClassLoader(),  
  6.             freeFunction,  
  7.             128 /* size */);  
  8. }  

创建HwBinder对象会首先执行native_init()函数,然后调用native_setup()函数。

frameworks\base\core\jni\android_os_HwBinder.cpp

[cpp] view plain copy

  1. static jlong JHwBinder_native_init(JNIEnv *env) {  
  2.     JHwBinder::InitClass(env);  
  3.   
  4.     return reinterpret_cast<jlong>(&releaseNativeContext);  
  5. }  
  6.   
  7. static void JHwBinder_native_setup(JNIEnv *env, jobject thiz) {  
  8.     sp<JHwBinderHolder> context = new JHwBinderHolder;  
  9.     JHwBinder::SetNativeContext(env, thiz, context);  
  10. }  

这里创建一个JHwBinderHolder 对象,并保存在HwBinder类的mNativeContext变量中。

[cpp] view plain copy

  1. sp<JHwBinderHolder> JHwBinder::SetNativeContext(  
  2.         JNIEnv *env, jobject thiz, const sp<JHwBinderHolder> &context) {  
  3.     sp<JHwBinderHolder> old =  
  4.         (JHwBinderHolder *)env->GetLongField(thiz, gFields.contextID);  
  5.   
  6.     if (context != NULL) {  
  7.         context->incStrong(NULL /* id */);  
  8.     }  
  9.   
  10.     if (old != NULL) {  
  11.         old->decStrong(NULL /* id */);  
  12.     }  
  13.   
  14.     env->SetLongField(thiz, gFields.contextID, (long)context.get());  
  15.   
  16.     return old;  
  17. }  

这里出现了多个binder类型:HwBinder、JHwBinderHolder、JHwBinder他们的类继承图如下:

红线标识了这3个类对象之间的关系,为了更加清晰地描述他们之间的关联关系,如下图所示:

 

Java hidl服务注册过程

当我们创建好了hidl服务类对象后,将调用mXXXImp.registerAsService(“XXXImp”);进行注册,注册过程如下:

frameworks\base\core\java\android\os\HwBinder.java

[java] view plain copy

  1. public native final void registerService(String serviceName)  
  2.         throws RemoteException;  

frameworks\base\core\jni\android_os_HwBinder.cpp

[cpp] view plain copy

  1. static void JHwBinder_native_registerService(  
  2.         JNIEnv *env,  
  3.         jobject thiz,  
  4.         jstring serviceNameObj) {  
  5.     if (serviceNameObj == NULL) {  
  6.         jniThrowException(env, ”java/lang/NullPointerException”, NULL);  
  7.         return;  
  8.     }  
  9.   
  10.     const char *serviceName = env->GetStringUTFChars(serviceNameObj, NULL);  
  11.     if (serviceName == NULL) {  
  12.         return;  // XXX exception already pending?  
  13.     }  
  14.   
  15.     sp<hardware::IBinder> binder = JHwBinder::GetNativeBinder(env, thiz);  
  16.   
  17.     /* TODO(b/33440494) this is not right */  
  18.     sp<hidl::base::V1_0::IBase> base = new hidl::base::V1_0::BpHwBase(binder);  
  19.   
  20.     auto manager = hardware::defaultServiceManager();  
  21.   
  22.     if (manager == nullptr) {  
  23.         LOG(ERROR) << ”Could not get hwservicemanager.”;  
  24.         signalExceptionForError(env, UNKNOWN_ERROR, true /* canThrowRemoteException */);  
  25.         return;  
  26.     }  
  27.   
  28.     Return<bool> ret = manager->add(serviceName, base);  
  29.   
  30.     env->ReleaseStringUTFChars(serviceNameObj, serviceName);  
  31.     serviceName = NULL;  
  32.   
  33.     bool ok = ret.isOk() && ret;  
  34.   
  35.     if (ok) {  
  36.         LOG(INFO) << ”Starting thread pool.”;  
  37.         ::android::hardware::ProcessState::self()->startThreadPool();  
  38.     }  
  39.   
  40.     signalExceptionForError(env, (ok ? OK : UNKNOWN_ERROR), true /* canThrowRemoteException */);  
  41. }  

首先通过GetNativeBinder函数得到JHwBinder对象,然后创建一个BpHwBase来包装JHwBinder,并将BpHwBase注册到hwservicemanager中。

[cpp] view plain copy

  1. sp<JHwBinder> JHwBinder::GetNativeBinder(  
  2.         JNIEnv *env, jobject thiz) {  
  3.     JHwBinderHolder *holder =  
  4.         reinterpret_cast<JHwBinderHolder *>(  
  5.                 env->GetLongField(thiz, gFields.contextID));  
  6.   
  7.     return holder->get(env, thiz);  
  8. }  

[cpp] view plain copy

  1. sp<JHwBinder> get(JNIEnv *env, jobject obj) {  
  2.     Mutex::Autolock autoLock(mLock);  
  3.   
  4.     sp<JHwBinder> binder = mBinder.promote();  
  5.   
  6.     if (binder == NULL) {  
  7.         binder = new JHwBinder(env, obj);  
  8.         mBinder = binder;  
  9.     }  
  10.   
  11.     return binder;  
  12. }  

从HwBinder的成员变量mNativeContext中得到JHwBinderHolder的对象指针,然后调用其get函数得到JHwBinder对象。然后将JHwBinder封装为BpHwBase对象。

android.hidl.base@1.0_genc++\gen\android\hidl\base\1.0\BaseAll.cpp

[cpp] view plain copy

  1. BpHwBase::BpHwBase(const ::android::sp<::android::hardware::IBinder> &_hidl_impl)  
  2.         : BpInterface<IBase>(_hidl_impl),  
  3.           ::android::hardware::details::HidlInstrumentor(”android.hidl.base@1.0”, “IBase”) {  
  4. }  

因此Java hidl服务向hwservicemanager注册的还是BpHwBase对象,BpHwBase的mRemote变量引用的是JHwBinder对象,JHwBinder的成员变量mObject又保存了Java层的HwBinder的引用。

从进程空间角度来看Java hidl服务注册,如下:

BpHwBase注册到hwservicemanager的详细过程在前面的文章中已经有详细的介绍,这里不再重复。

 

Java hidl服务查询过程

[java] view plain copy

  1.   
 

既然有注册,那么肯定存在服务查询,那么Client进程如何查询这些运行在Server进程端的Java hidl服务呢?

out/target/common/gen/JAVA_LIBRARIES/android.hardware.wifi-V1.1-java_intermediates/android/hardware/wifi/V1_1/IWifi.java

[java] view plain copy

 

  1. public static IWifi getService(String serviceName) throws android.os.RemoteException {  
  2.     return IWifi.asInterface(android.os.HwBinder.getService(“android.hardware.wifi@1.1::IWifi”,serviceName));  
  3. }  
  4.   
  5. public static IWifi getService() throws android.os.RemoteException {  
  6.     return IWifi.asInterface(android.os.HwBinder.getService(“android.hardware.wifi@1.1::IWifi”,“default”));  
  7. }  

这里首先调用android.os.HwBinder.getService(“android.hardware.wifi@1.1::IWifi”,”default”)来查询hidl服务,然后通过asInterface接口转换为与业务相关的接口对象。

服务查询过程

这里首先通过HwBinder.getService()接口从hwservicemanager进程中根据包名”android.hardware.wifi@1.1::IWifi”,”default”的hwBinder代理。

frameworks\base\core\java\android\os\HwBinder.java

[java] view plain copy

  1. public static native final IHwBinder getService(  
  2.         String iface,  
  3.         String serviceName)  
  4.     throws RemoteException, NoSuchElementException;  

frameworks\base\core\jni\android_os_HwBinder.cpp

[cpp] view plain copy

  1. static jobject JHwBinder_native_getService(  
  2.         JNIEnv *env,  
  3.         jclass /* clazzObj */,  
  4.         jstring ifaceNameObj,  
  5.         jstring serviceNameObj) {  
  6.   
  7.     using ::android::hidl::base::V1_0::IBase;  
  8.     using ::android::hidl::manager::V1_0::IServiceManager;  
  9.   
  10.     if (ifaceNameObj == NULL) {  
  11.         jniThrowException(env, ”java/lang/NullPointerException”, NULL);  
  12.         return NULL;  
  13.     }  
  14.     if (serviceNameObj == NULL) {  
  15.         jniThrowException(env, ”java/lang/NullPointerException”, NULL);  
  16.         return NULL;  
  17.     }  
  18.   
  19.     auto manager = hardware::defaultServiceManager();  
  20.   
  21.     if (manager == nullptr) {  
  22.         LOG(ERROR) << ”Could not get hwservicemanager.”;  
  23.         signalExceptionForError(env, UNKNOWN_ERROR, true /* canThrowRemoteException */);  
  24.         return NULL;  
  25.     }  
  26.   
  27.     const char *ifaceNameCStr = env->GetStringUTFChars(ifaceNameObj, NULL);  
  28.     if (ifaceNameCStr == NULL) {  
  29.         return NULL; // XXX exception already pending?  
  30.     }  
  31.     std::string ifaceName(ifaceNameCStr);  
  32.     env->ReleaseStringUTFChars(ifaceNameObj, ifaceNameCStr);  
  33.     ::android::hardware::hidl_string ifaceNameHStr;  
  34.     ifaceNameHStr.setToExternal(ifaceName.c_str(), ifaceName.size());  
  35.   
  36.     const char *serviceNameCStr = env->GetStringUTFChars(serviceNameObj, NULL);  
  37.     if (serviceNameCStr == NULL) {  
  38.         return NULL; // XXX exception already pending?  
  39.     }  
  40.     std::string serviceName(serviceNameCStr);  
  41.     env->ReleaseStringUTFChars(serviceNameObj, serviceNameCStr);  
  42.     ::android::hardware::hidl_string serviceNameHStr;  
  43.     serviceNameHStr.setToExternal(serviceName.c_str(), serviceName.size());  
  44.   
  45.     LOG(INFO) << ”Looking for service ”  
  46.               << ifaceName  
  47.               << ”/”  
  48.               << serviceName;  
  49.   
  50.     Return<IServiceManager::Transport> transportRet =  
  51.             manager->getTransport(ifaceNameHStr, serviceNameHStr);  
  52.   
  53.     if (!transportRet.isOk()) {  
  54.         signalExceptionForError(env, UNKNOWN_ERROR, true /* canThrowRemoteException */);  
  55.         return NULL;  
  56.     }  
  57.   
  58.     IServiceManager::Transport transport = transportRet;  
  59.   
  60. #ifdef __ANDROID_TREBLE__  
  61. #ifdef __ANDROID_DEBUGGABLE__  
  62.     const char* testingOverride = std::getenv(“TREBLE_TESTING_OVERRIDE”);  
  63.     const bool vintfLegacy = (transport == IServiceManager::Transport::EMPTY)  
  64.             && testingOverride && !strcmp(testingOverride, ”true”);  
  65. #else // __ANDROID_TREBLE__ but not __ANDROID_DEBUGGABLE__  
  66.     const bool vintfLegacy = false;  
  67. #endif // __ANDROID_DEBUGGABLE__  
  68. #else // not __ANDROID_TREBLE__  
  69.     const bool vintfLegacy = (transport == IServiceManager::Transport::EMPTY);  
  70. #endif // __ANDROID_TREBLE__”;  
  71.   
  72.     if (transport != IServiceManager::Transport::HWBINDER && !vintfLegacy) {  
  73.         LOG(ERROR) << ”service ” << ifaceName << “ declares transport method ”  
  74.                    << toString(transport) << ” but framework expects hwbinder.”;  
  75.         signalExceptionForError(env, UNKNOWN_ERROR, true /* canThrowRemoteException */);  
  76.         return NULL;  
  77.     }  
  78.   
  79.     Return<sp<hidl::base::V1_0::IBase>> ret = manager->get(ifaceNameHStr, serviceNameHStr);  
  80.   
  81.     if (!ret.isOk()) {  
  82.         signalExceptionForError(env, UNKNOWN_ERROR, true /* canThrowRemoteException */);  
  83.         return NULL;  
  84.     }  
  85.   
  86.     sp<hardware::IBinder> service = hardware::toBinder<  
  87.             hidl::base::V1_0::IBase, hidl::base::V1_0::BpHwBase>(ret);  
  88.   
  89.     if (service == NULL) {  
  90.         signalExceptionForError(env, NAME_NOT_FOUND);  
  91.         return NULL;  
  92.     }  
  93.   
  94.     LOG(INFO) << ”Starting thread pool.”;  
  95.     ::android::hardware::ProcessState::self()->startThreadPool();  
  96.   
  97.     return JHwRemoteBinder::NewObject(env, service);  
  98. }  

ifdef ANDROID_TREBLE

ifdef ANDROID_DEBUGGABLE

else // ANDROID_TREBLE but not ANDROID_DEBUGGABLE

const bool vintfLegacy = false;

endif // ANDROID_DEBUGGABLE

else // not ANDROID_TREBLE

const bool vintfLegacy = (transport == IServiceManager::Transport::EMPTY);

endif // ANDROID_TREBLE“;

if (transport != IServiceManager::Transport::HWBINDER && !vintfLegacy) {
    LOG(ERROR) << "service " << ifaceName << " declares transport method "
               << toString(transport) << " but framework expects hwbinder.";
    signalExceptionForError(env, UNKNOWN_ERROR, true /* canThrowRemoteException */);
    return NULL;
}

Return<sp<hidl::base::V1_0::IBase>> ret = manager->get(ifaceNameHStr, serviceNameHStr);

if (!ret.isOk()) {
    signalExceptionForError(env, UNKNOWN_ERROR, true /* canThrowRemoteException */);
    return NULL;
}

sp<hardware::IBinder> service = hardware::toBinder<
        hidl::base::V1_0::IBase, hidl::base::V1_0::BpHwBase>(ret);

if (service == NULL) {
    signalExceptionForError(env, NAME_NOT_FOUND);
    return NULL;
}

LOG(INFO) << "Starting thread pool.";
::android::hardware::ProcessState::self()->startThreadPool();

return JHwRemoteBinder::NewObject(env, service);

首先检查当前查询的hidl服务的Transport是否为hwbinder,然后通过hardware::defaultServiceManager()得到IServiceManager 在native层的业务代理对象BpHwServiceManager,接着通过BpHwServiceManager向hwservicemanager查询hidl服务,其实就是根据接口包名从hwservicemanager进程中的mServiceMap表中查找对应的HidlService对象,从而得到BpHwBase对象,通过前面文章对hidl服务查询过程分析可知,查询返回的IBase对象是BpHwBase对象。

这里接着通过hardware::toBinder接口将IBase对象转换为binder对象,其实就是从BpHwBase中拿到其成员变量mRemote中的BpHwBinder对象,最后在JNI层将调用JHwRemoteBinder::NewObject()函数来创建一个Java层HwRemoteBinder对象。

frameworks\base\core\jni\android_os_HwBinder.cpp

[cpp] view plain copy

  1. jobject JHwRemoteBinder::NewObject(  
  2.         JNIEnv *env, const sp<hardware::IBinder> &binder) {  
  3.     ScopedLocalRef<jclass> clazz(env, FindClassOrDie(env, CLASS_PATH));  
  4.   
  5.     // XXX Have to look up the constructor here because otherwise that static  
  6.     // class initializer isn’t called and gProxyOffsets.constructID is undefined :(  
  7.   
  8.     jmethodID constructID = GetMethodIDOrDie(env, clazz.get(), ”<init>”, “()V”);  
  9.   
  10.     jobject obj = env->NewObject(clazz.get(), constructID);  
  11.     JHwRemoteBinder::GetNativeContext(env, obj)->setBinder(binder);  
  12.   
  13.     return obj;  
  14. }  
     

首先调用HwRemoteBinder的构造函数创建一个HwRemoteBinder对象。

frameworks\base\core\java\android\os\HwRemoteBinder.java

[java] view plain copy

  1. static {  
  2.     long freeFunction = native_init();  
  3.   
  4.     sNativeRegistry = new NativeAllocationRegistry(  
  5.             HwRemoteBinder.class.getClassLoader(),  
  6.             freeFunction,  
  7.             128 / size /);  
  8. }  
  9.   
  10. public HwRemoteBinder() {  
  11.     native_setup_empty();  
  12.   
  13.     sNativeRegistry.registerNativeAllocation(  
  14.             this,  
  15.             mNativeContext);  
  16. }  
static { 

    long freeFunction = native_init();

sNativeRegistry = new NativeAllocationRegistry( HwRemoteBinder.class.getClassLoader(), freeFunction, 128 /* size */); 

public HwRemoteBinder() {
native_setup_empty();

  •  

首先将执行静态代码块,做必要的初始化,然后执行对象的构造函数。

frameworks\base\core\jni\android_os_HwRemoteBinder.cpp

[cpp] view plain copy

  1. static jlong JHwRemoteBinder_native_init(JNIEnv env) {  
  2.     JHwRemoteBinder::InitClass(env);  
  3.   
  4.     return reinterpret_cast<jlong>(&releaseNativeContext);  
  5. }  
  6.   
  7. static void JHwRemoteBinder_native_setup_empty(JNIEnv *env, jobject thiz) {  
  8.     sp<JHwRemoteBinder> context =  
  9.         new JHwRemoteBinder(env, thiz, NULL / service */);  
  10.   
  11.     JHwRemoteBinder::SetNativeContext(env, thiz, context);  
  12. }  
static jlong JHwRemoteBinder_native_init(JNIEnv *env) { 

    JHwRemoteBinder::InitClass(env);

return reinterpret_cast<jlong>(&releaseNativeContext); 

static void JHwRemoteBinder_native_setup_empty(JNIEnv *env, jobject thiz) {
sp<JHwRemoteBinder> context =
new JHwRemoteBinder(env, thiz, NULL /* service */);

JHwRemoteBinder::SetNativeContext(env, thiz, context);
  • }

[cpp] view plain copy

  1. JHwRemoteBinder::JHwRemoteBinder(  
  2.         JNIEnv *env, jobject thiz, const sp<hardware::IBinder> &binder)  
  3.     : mBinder(binder) {  
  4.     mDeathRecipientList = new HwBinderDeathRecipientList();  
  5.     jclass clazz = env->GetObjectClass(thiz);  
  6.     CHECK(clazz != NULL);  
  7.   
  8.     mObject = env->NewWeakGlobalRef(thiz);  
  9. }  
JHwRemoteBinder::JHwRemoteBinder( 

        JNIEnv *env, jobject thiz, const sp<hardware::IBinder> &binder) 

    : mBinder(binder) { 

    mDeathRecipientList = new HwBinderDeathRecipientList(); 

    jclass clazz = env->GetObjectClass(thiz); 

    CHECK(clazz != NULL);

mObject = env->NewWeakGlobalRef(thiz); 

这里在JNI层创建一个JHwRemoteBinder对象,并将其对象指针保存到Java层HwRemoteBinder的mNativeContext变量中。

[cpp] view plain copy

  1. sp<JHwRemoteBinder> JHwRemoteBinder::SetNativeContext(  
  2.         JNIEnv env, jobject thiz, const sp<JHwRemoteBinder> &context) {  
  3.     sp<JHwRemoteBinder> old =  
  4.         (JHwRemoteBinder )env->GetLongField(thiz, gProxyOffsets.contextID);  
  5.   
  6.     if (context != NULL) {  
  7.         context->incStrong(NULL / id /);  
  8.     }  
  9.   
  10.     if (old != NULL) {  
  11.         old->decStrong(NULL / id /);  
  12.     }  
  13.   
  14.     env->SetLongField(thiz, gProxyOffsets.contextID, (long)context.get());  
  15.   
  16.     return old;  
  17. }  
sp<JHwRemoteBinder> JHwRemoteBinder::SetNativeContext( 

        JNIEnv *env, jobject thiz, const sp<JHwRemoteBinder> &context) { 

    sp<JHwRemoteBinder> old = 

        (JHwRemoteBinder *)env->GetLongField(thiz, gProxyOffsets.contextID);

if (context != NULL) { context->incStrong(NULL /* id */); } if (old != NULL) { old->decStrong(NULL /* id */); } env->SetLongField(thiz, gProxyOffsets.contextID, (long)context.get()); return old; 

到此就完成了HwRemoteBinder对象的创建过程,接着会将查询到的IBinder保存到JHwRemoteBinder的mBinder变量中。

JHwRemoteBinder::GetNativeContext(env,obj)->setBinder(binder);

[cpp] view plain copy

  1. void JHwRemoteBinder::setBinder(const sp<hardware::IBinder> &binder) {  
  2.     mBinder = binder;  
  3. }  

这些对象之间的关系如下图所示:

因此Java hidl服务查询最终得到一个HwRemoteBinder对象。

接口转换过程

 

通过服务查询得到HwRemoteBinder对象,这个只是传输层面的对象而已,需要转换为业务层面的对象,这个是由IXXX.asInterface函数完成。

[java] view plain copy

  1. / package private / static IWifi asInterface(android.os.IHwBinder binder) {  
  2.     if (binder == null) {  
  3.         return null;  
  4.     }  
  5.   
  6.     android.os.IHwInterface iface =  
  7.             binder.queryLocalInterface(kInterfaceName);  
  8.   
  9.     if ((iface != null) && (iface instanceof IWifi)) {  
  10.         return (IWifi)iface;  
  11.     }  
  12.   
  13.     IWifi proxy = new IWifi.Proxy(binder);  
  14.   
  15.     try {  
  16.         for (String descriptor : proxy.interfaceChain()) {  
  17.             if (descriptor.equals(kInterfaceName)) {  
  18.                 return proxy;  
  19.             }  
  20.         }  
  21.     } catch (android.os.RemoteException e) {  
  22.     }  
  23.   
  24.     return null;  
  25. }  
/* package private */ static IWifi asInterface(android.os.IHwBinder binder) { 

    if (binder == null) { 

        return null; 

    }

android.os.IHwInterface iface = binder.queryLocalInterface(kInterfaceName); if ((iface != null) && (iface instanceof IWifi)) { return (IWifi)iface; } IWifi proxy = new IWifi.Proxy(binder); try { for (String descriptor : proxy.interfaceChain()) { if (descriptor.equals(kInterfaceName)) { return proxy; } } } catch (android.os.RemoteException e) { } return null; 

这里在HwRemoteBinder对象的基础上包裹了一层与业务相关的Proxy对象,这样业务和传输分离,通过代理方式实现IPC调用。

到此Treble架构下的hwBinder实现过程就基本介绍完成。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值