Binder in Java and in C++

世界发展这么快,养尊处优的你跟的上吗?

我们知道android中有很多服务进程,在面向对象的今天,如果我们想和服务进程通过Binder来通信,那么我们要持有一些类,抛开业务层的通信不讲,基础的通信层的类有在客户端这边的BpBinder,服务这边的BBinder,当然这是C++层的代码,我们还知道jni把java程序员和c++程序员聚集在了一个圆桌上,我想今天试着把它们之间的“福利合照”公开一下。

可能是c++层的类影响了java层的类,无独有偶,java通信层的类有:BinderProxy,Binder。

按照我们的使用服务的流程,在ACtivity中使用:

getSystemService(String name)

调至:

 mBase.getSystemService(String name) ;

 mBase是ContextImpl类型,所以:

    @Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }

而:

final class SystemServiceRegistry {
    private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new HashMap<String, ServiceFetcher<?>>();
    private SystemServiceRegistry() { }

    static {
        ......

        registerService(Context.ACCOUNT_SERVICE, AccountManager.class,
                new CachedServiceFetcher<AccountManager>() {
            @Override
            public AccountManager createService(ContextImpl ctx) {
                IBinder b = ServiceManager.getService(Context.ACCOUNT_SERVICE);
                IAccountManager service = IAccountManager.Stub.asInterface(b);
                return new AccountManager(ctx, service);
            }});

        registerService(Context.ACTIVITY_SERVICE, ActivityManager.class,
                new CachedServiceFetcher<ActivityManager>() {
            @Override
            public ActivityManager createService(ContextImpl ctx) {
                return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());
            }});

        registerService(Context.ALARM_SERVICE, AlarmManager.class,
                new CachedServiceFetcher<AlarmManager>() {
            @Override
            public AlarmManager createService(ContextImpl ctx) {
                IBinder b = ServiceManager.getService(Context.ALARM_SERVICE);
                IAlarmManager service = IAlarmManager.Stub.asInterface(b);
                return new AlarmManager(service, ctx);
            }});	
	......
	}
    public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }
}

这些在上下文中的服务在静态代码块中初始化,这些服务大致的初始化方式分为2种。

第一种类似于ActivityManager这种,直接创建:

new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());

第二种类似于AlarmManager这种,先从ServiceManager里面获取一个IBinder,再创建:

 IBinder b = ServiceManager.getService(Context.ALARM_SERVICE);
 IAlarmManager service = IAlarmManager.Stub.asInterface(b);
 return new AlarmManager(service, ctx);

先看第一种服务的服务接口,例如:ActivityManager的getRecentTasks(int maxNum, int flags)方法:

    public List<RecentTaskInfo> getRecentTasks(int maxNum, int flags)
            throws SecurityException {
        try {
            return ActivityManagerNative.getDefault().getRecentTasks(maxNum,
                    flags, UserHandle.myUserId());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

ActivityManagerNative.getDefault()调至:

    private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
            IBinder b = ServiceManager.getService("activity");
            if (false) {
                Log.v("ActivityManager", "default service binder = " + b);
            }
            IActivityManager am = asInterface(b);
            if (false) {
                Log.v("ActivityManager", "default service = " + am);
            }
            return am;
        }
    };

从create方法看到,其实就明白了,第一种创建的和第二种创建的服务,使用接口的时候,都是先:

 IBinder b = ServiceManager.getService(servicename);

然后:

IXXXService service = (IXXXService.Stub.)asInterface(b);

最后拿着IXXXService去调用业务层的接口,只是在创建的时候,代码的位置不一样。

所以我们先来看ServiceManager.getService(servicename)是怎么把Binder Something in Java and in C++“合照”起来的。如下:

public final class ServiceManager {
    private static final String TAG = "ServiceManager";

    private static IServiceManager sServiceManager;
    private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();

    private static IServiceManager getIServiceManager() {
        if (sServiceManager != null) {
            return sServiceManager;
        }

        // Find the service manager
        sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
        return sServiceManager;
    }

    /**
     * Returns a reference to a service with the given name.
     * 
     * @param name the name of the service to get
     * @return a reference to the service, or <code>null</code> if the service doesn't exist
     */
    public static IBinder getService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                return getIServiceManager().getService(name);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
    }
//.......
}

先关注:BinderInternal.getContextObject():

public class BinderInternal {
。。。。。。
    /**
84     * Return the global "context object" of the system.  This is usually
85     * an implementation of IServiceManager, which you can use to find
86     * other services.
87     */
88    public static final native IBinder getContextObject();
。。。。。。
}

调至c++层的/frameworks/base/core/jni/android_util_Binder.cpp中:

899static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz)
900{
901    sp<IBinder> b = ProcessState::self()->getContextObject(NULL);
902    return javaObjectForIBinder(env, b);
903}

调至ProcessState.cpp中的getContextObject:

85sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/)
86{
87    return getStrongProxyForHandle(0);
88}

236wp<IBinder> ProcessState::getWeakProxyForHandle(int32_t handle)
237{
238    wp<IBinder> result;
239
240    AutoMutex _l(mLock);
241
242    handle_entry* e = lookupHandleLocked(handle);
243
244    if (e != NULL) {
245        // We need to create a new BpBinder if there isn't currently one, OR we
246        // are unable to acquire a weak reference on this current one.  The
247        // attemptIncWeak() is safe because we know the BpBinder destructor will always
248        // call expungeHandle(), which acquires the same lock we are holding now.
249        // We need to do this because there is a race condition between someone
250        // releasing a reference on this BpBinder, and a new reference on its handle
251        // arriving from the driver.
252        IBinder* b = e->binder;
253        if (b == NULL || !e->refs->attemptIncWeak(this)) {
254            b = new BpBinder(handle);
255            result = b;
256            e->binder = b;
257            if (b) e->refs = b->getWeakRefs();
258        } else {
259            result = b;
260            e->refs->decWeak(this);
261        }
262    }
263
264    return result;
265}

166ProcessState::handle_entry* ProcessState::lookupHandleLocked(int32_t handle)
167{
168    const size_t N=mHandleToObject.size();
169    if (N <= (size_t)handle) {
170        handle_entry e;
171        e.binder = NULL;
172        e.refs = NULL;
173        status_t err = mHandleToObject.insertAt(e, N, handle+1-N);
174        if (err < NO_ERROR) return NULL;
175    }
176    return &mHandleToObject.editItemAt(handle);
177}

77            struct handle_entry {
78                IBinder* binder;
79                RefBase::weakref_type* refs;
80            };

上面的代码是在讲,ProcessState里有一个缓存mHandleToObject,缓存的元素是 handle_entry,创建出来的BpBinder对象,挂在handle_entry的binder指针上,BpBinder有继承到RefBase的一些血统,所以BpBinder对象的弱引用挂在handle_entry的refs指针上。

创建,并返回Bpbinder后,继续javaObjectForIBinder:

547jobject javaObjectForIBinder(JNIEnv* env, const sp<IBinder>& val)
548{
549    if (val == NULL) return NULL;
550
551    if (val->checkSubclass(&gBinderOffsets)) {
552        // One of our own!
553        jobject object = static_cast<JavaBBinder*>(val.get())->object();
554        LOGDEATH("objectForBinder %p: it's our own %p!\n", val.get(), object);
555        return object;
556    }
557
558    // For the rest of the function we will hold this lock, to serialize
559    // looking/creation of Java proxies for native Binder proxies.
560    AutoMutex _l(mProxyLock);
561
562    // Someone else's...  do we know about it?
563    jobject object = (jobject)val->findObject(&gBinderProxyOffsets);
564    if (object != NULL) {
565        jobject res = jniGetReferent(env, object);
566        if (res != NULL) {
567            ALOGV("objectForBinder %p: found existing %p!\n", val.get(), res);
568            return res;
569        }
570        LOGDEATH("Proxy object %p of IBinder %p no longer in working set!!!", object, val.get());
571        android_atomic_dec(&gNumProxyRefs);
572        val->detachObject(&gBinderProxyOffsets);
573        env->DeleteGlobalRef(object);
574    }
575
576    object = env->NewObject(gBinderProxyOffsets.mClass, gBinderProxyOffsets.mConstructor);
577    if (object != NULL) {
578        LOGDEATH("objectForBinder %p: created new proxy %p !\n", val.get(), object);
579        // The proxy holds a reference to the native object.
580        env->SetLongField(object, gBinderProxyOffsets.mObject, (jlong)val.get());
581        val->incStrong((void*)javaObjectForIBinder);
582
583        // The native object needs to hold a weak reference back to the
584        // proxy, so we can retrieve the same proxy if it is still active.
585        jobject refObject = env->NewGlobalRef(
586                env->GetObjectField(object, gBinderProxyOffsets.mSelf));
587        val->attachObject(&gBinderProxyOffsets, refObject,
588                jnienv_to_javavm(env), proxy_cleanup);
589
590        // Also remember the death recipients registered on this proxy
591        sp<DeathRecipientList> drl = new DeathRecipientList;
592        drl->incStrong((void*)javaObjectForIBinder);
593        env->SetLongField(object, gBinderProxyOffsets.mOrgue, reinterpret_cast<jlong>(drl.get()));
594
595        // Note that a new object reference has been created.
596        android_atomic_inc(&gNumProxyRefs);
597        incRefsCreated(env);
598    }
599
600    return object;
601}

看jobject object = (jobject)val->findObject(&gBinderProxyOffsets);这一句,sp是google工程师编写的指针处理类强引用,相应的还有弱引用wp,引用基类RefBase,他们都是在管理在堆中创建过的对象,何时释放。sp重载了操作符->,所以findObject调至BpBinder的findObject。

先来看参数gBinderProxyOffsets是什么东西。

95static struct binderproxy_offsets_t
96{
97    // Class state.
98    jclass mClass;
99    jmethodID mConstructor;
100    jmethodID mSendDeathNotice;
101
102    // Object state.
103    jfieldID mObject;
104    jfieldID mSelf;
105    jfieldID mOrgue;
106
107} gBinderProxyOffsets;

 gBinderProxyOffsets结构记录这java层的BinderProxy类的一些字段和方法:

final class BinderProxy implements IBinder {
。。。。。。
540    BinderProxy() {
541        mSelf = new WeakReference(this);
542    }
。。。。。
555    private static final void sendDeathNotice(DeathRecipient recipient) {
556        if (false) Log.v("JavaBinder", "sendDeathNotice to " + recipient);
557        try {
558            recipient.binderDied();
559        }
560        catch (RuntimeException exc) {
561            Log.w("BinderNative", "Uncaught exception from death notification",
562                    exc);
563        }
564    }

。。。。。。
501    public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
502        Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");
503        return transactNative(code, data, reply, flags);
504    }
。。。。。。
507    public native boolean transactNative(int code, Parcel data, Parcel reply,
508            int flags) throws RemoteException;
。。。。。。
566    final private WeakReference mSelf;
567    private long mObject;//BpBinder的指针值,缓存在mHandleToObject中
568    private long mOrgue;
569}

 记录的过程在int_register_android_os_BinderProxy(JNIEnv* env)中,也在/frameworks/base/core/jni/android_util_Binder.cpp中。

 

1254static int int_register_android_os_BinderProxy(JNIEnv* env)
1255{
1256    jclass clazz = FindClassOrDie(env, "java/lang/Error");
1257    gErrorOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
1258
1259    clazz = FindClassOrDie(env, kBinderProxyPathName);
1260    gBinderProxyOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
1261    gBinderProxyOffsets.mConstructor = GetMethodIDOrDie(env, clazz, "<init>", "()V");
1262    gBinderProxyOffsets.mSendDeathNotice = GetStaticMethodIDOrDie(env, clazz, "sendDeathNotice",
1263            "(Landroid/os/IBinder$DeathRecipient;)V");
1264
1265    gBinderProxyOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject", "J");
1266    gBinderProxyOffsets.mSelf = GetFieldIDOrDie(env, clazz, "mSelf",
1267                                                "Ljava/lang/ref/WeakReference;");
1268    gBinderProxyOffsets.mOrgue = GetFieldIDOrDie(env, clazz, "mOrgue", "J");
1269
1270    clazz = FindClassOrDie(env, "java/lang/Class");
1271    gClassOffsets.mGetName = GetMethodIDOrDie(env, clazz, "getName", "()Ljava/lang/String;");
1272
1273    return RegisterMethodsOrDie(
1274        env, kBinderProxyPathName,
1275        gBinderProxyMethods, NELEM(gBinderProxyMethods));
1276}

再来接着前面来看看 BpBinder::findObject方法:

 

class BpBinder : public IBinder
{
public:
	......
	class ObjectManager
	{
	......
   void* find(const void* objectID) const;
    。。。。。。
	private:
        	struct entry_t
        	{
            		void* object;//创建的java层对象的一个弱引用
            		void* cleanupCookie;
            		IBinder::object_cleanup_func func;
        	};
 		KeyedVector<const void*, entry_t> mObjects;//缓存
	......
	}
   。。。。。。
   void* BpBinder::findObject(const void* objectID) const;
   。。。。。。

private:
	ObjectManager       mObjects;
}

294void* BpBinder::findObject(const void* objectID) const
295{
296    AutoMutex _l(mLock);
297    return mObjects.find(objectID);
298}

61void* BpBinder::ObjectManager::find(const void* objectID) const
62{
63    const ssize_t i = mObjects.indexOfKey(objectID);
64    if (i < 0) return NULL;
65    return mObjects.valueAt(i).object;
66}

 BpBinder的findObject方法其实是在缓存ObjectManager里,根据记录java类BinderProxy的gBinderProxyOffsets变量查找java类的BinderProxy对象,找到就返回,找不到再创建一个,并存储在BpBinder这个对象的缓存里,创建的代码还在javaObjectForIBinder方法里。

Bpbinder对象里有对应的java层对象的缓存,其实可以缓存(对应)各种类型的java层对象,这里主要只有一个类型是BinderProxy。

这里缓存的key是就是该结构体变量gBinderProxyOffsets的地址值,value就是一个entry_t结构体对象,其中object指针指向新创建的BinderProxy对象的mSelf字段值,它是本对象的弱引用。Bpbinder析构的时候,会清理掉这些缓存。

上面提到了BinderProxy对象的创建,假如, jobject object = (jobject)val->findObject(&gBinderProxyOffsets);没找到缓存,那就有:

576    object = env->NewObject(gBinderProxyOffsets.mClass, gBinderProxyOffsets.mConstructor);

 创建一个。

创建完成之后,把对应的BpBinder对象的地址赋给新创建的BinderProxy对象的mObject字段,这样BinderProxy对象持有了BpBinder对象的地址,BpBinder对象缓存了BinderProxy对象,都可以在各自的层级通过jni“索引”到对方;接着把BinderProxy对象的mSelf弱引用拿出去(它在构造方法里已经初始化完了),来构建Bpbinder缓存里的entry_t元素,所以entry_t元素的object指针指向Bindproxy的弱引用,而不是指向Bindproxy对象,Bindproxy对象就可以被GC回收。

576    object = env->NewObject(gBinderProxyOffsets.mClass, gBinderProxyOffsets.mConstructor);
577    if (object != NULL) {
578        LOGDEATH("objectForBinder %p: created new proxy %p !\n", val.get(), object);
579        // The proxy holds a reference to the native object.
580        env->SetLongField(object, gBinderProxyOffsets.mObject, (jlong)val.get());
581        val->incStrong((void*)javaObjectForIBinder);
582
583        // The native object needs to hold a weak reference back to the
584        // proxy, so we can retrieve the same proxy if it is still active.
585        jobject refObject = env->NewGlobalRef(
586                env->GetObjectField(object, gBinderProxyOffsets.mSelf));
587        val->attachObject(&gBinderProxyOffsets, refObject,
588                jnienv_to_javavm(env), proxy_cleanup);//拿出弱引用去构建缓存元素
589
。。。。。。
598    }

接着从javaObjectForIBinder返回一个Bindproxy对象到BinderInternal.java的getContextObject(),getContextObject()再返回到ServiceManager的getIServiceManager方法里,具体返回的地方:

       sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());

再看ServiceManagerNative.asInterface方法:

    static public IServiceManager asInterface(IBinder obj)
    {
        if (obj == null) {
            return null;
        }
        IServiceManager in =
            (IServiceManager)obj.queryLocalInterface(descriptor);
        if (in != null) {
            return in;
        }
        
        return new ServiceManagerProxy(obj);
    }

而obj.queryLocalInterface == null:

493final class BinderProxy implements IBinder {
。。。。。。
497    public IInterface queryLocalInterface(String descriptor) {
498        return null;
499    }
。。。。。。
}

所以,getIServiceManager()返回了new ServiceManagerProxy(new BinderProxy());接着看ServiceManager的getService(servicename)方法:

    public static IBinder getService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                return getIServiceManager().getService(name);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
    }

接着看ServiceManagerProxy的getService:

class ServiceManagerProxy implements IServiceManager {
    public ServiceManagerProxy(IBinder remote) {
        mRemote = remote;
    }
    
    public IBinder asBinder() {
        return mRemote;
    }
    
    public IBinder getService(String name) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IServiceManager.descriptor);
        data.writeString(name);
        mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);
        IBinder binder = reply.readStrongBinder();
        reply.recycle();
        data.recycle();
        return binder;
    }
。。。。。。

mRemote就是c++创建的jvm管控的BinderProxy对象,我们关注mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);这一句,.transact为BinderProxy的native方法,对应/frameworks/base/core/jni/android_util_Binder.cpp中的(void*)android_os_BinderProxy_transact方法:

1083static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
1084        jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException
1085{
86    if (dataObj == NULL) {
1087        jniThrowNullPointerException(env, NULL);
1088        return JNI_FALSE;
1089    }
1090
1091    Parcel* data = parcelForJavaObject(env, dataObj);
1092    if (data == NULL) {
1093        return JNI_FALSE;
1094    }
1095    Parcel* reply = parcelForJavaObject(env, replyObj);
1096    if (reply == NULL && replyObj != NULL) {
1097        return JNI_FALSE;
1098    }
1099
1100    IBinder* target = (IBinder*)
1101        env->GetLongField(obj, gBinderProxyOffsets.mObject);
 status_t err = target->transact(code, *data, reply, flags);
......
}

上面的target就是上次创建BinderProxy对象后赋给其mObject字段的c++层的BpBinder的地址值,所以target->transact(code, *data, reply, flags);这一句就是BpBinder->transact(code, *data, reply, flags);

BpBinder就是c++层的通信基础类,丢给它的参数,都会通过binder驱动的mmap操作,一次copy给目标进程,这里的目标进程就是Servicemanager进程。

至于AlarmManager那种方式,有一步是IAlarmManager service = IAlarmManager.Stub.asInterface(b);如果你自己创建aidl文件的话,这一步就是把ServiceManager.getService(Context.ALARM_SERVICE);返回的BinderProxy对象传给IAlarmManager.Stub.Proxy对象持有,IAlarmManager.Stub.Proxy对象实现了IAlarmManager接口,它作为业务通信类,持有基础通信类BinderProxy对象来向binder驱动传输命令。

总结一下,上面说到的调用各大服务的接口的过程:

1.要先获取到servicemanage服务进程的代理对象,new ServiceManagerProxy(new BinderPRoxy());

2.再getService(servicename),其实是调用BinderPRoxy的transact native方法,而BinderPRoxy通过持有c++层的BpBinder的指针值的mObject字段,找到对应的c++层的BpBinder对象,然后调用BpBinder的transact方法,把参数发送给binder驱动。

    public IBinder getService(String name) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IServiceManager.descriptor);
        data.writeString(name);
        mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);
        IBinder binder = reply.readStrongBinder();
        reply.recycle();
        data.recycle();
        return binder;
    }

3.驱动把返回的bind引用,放到c++层的reply中,不是上面的reply,c++层中reply通过readStrongBinder()先读到一个Bpbinder对象,然后把BpBinder通过上面所说的javaObjectForIBinder方法,就返回了java层的BinderProxy对象。

// c+层的reply接收返回的binder引用资源
reply->ipcSetDataReference(		
    reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),//内核给的binder引用在里面	
    tr.data_size,		
    reinterpret_cast<const size_t*>(tr.data.ptr.offsets),		
    tr.offsets_size/sizeof(size_t),		
    freeBuffer, this);		

// java侧的readStrongBinder(),调至native的jobject android_os_Parcel_readStrongBinder
    public final IBinder readStrongBinder() {
        return nativeReadStrongBinder(mNativePtr);
    }

//{"nativeReadStrongBinder",    "(J)Landroid/os/IBinder;", (void*)android_os_Parcel_readStrongBinder}
425static jobject android_os_Parcel_readStrongBinder(JNIEnv* env, jclass clazz, jlong nativePtr)
426{
427    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
428    if (parcel != NULL) {
429        return javaObjectForIBinder(env, parcel->readStrongBinder());//返回BinderProxy对象
430    }
431    return NULL;
432}

其实java层的Parcel 和c++层的Parcel 通过java层持有c++层的Parcel对象的指针一一对应起来,而它们的读和写的缓存都在c++层,从而保持了其中数据的一致性。

547static jlong android_os_Parcel_create(JNIEnv* env, jclass clazz)
548{
549    Parcel* parcel = new Parcel();
550    return reinterpret_cast<jlong>(parcel);
551}

再总结:

返回给客户端的 代理service都长这样:new XXXServiceProxy(new BinderPRoxy());

BinderPRoxy.java对象在c++层创建,并缓存在BpBinder.cpp对象的缓存里;而BpBinder.cpp对象的地址值挂在BinderPRoxy.java对象的mObject字段上,从而完成“福利合照”;

类似的,java层的Parcel 和c++层的Parcel 通过java层持有c++层的Parcel对象的指针一一对应起来,并且他们的读写都反应在c++层的Parcel对象的缓存里。

转载注明出处。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值