Intents and Intent Filters(Intent和Intent过滤器)

文章转载禁止用于商业用途,且不能带有虚拟货币、积分、注册等附加条件。转载须注明出处莫高雷草原以及作者@JiongBull

访问Android API指南目录索引查看全部翻译

Intent和Intent过滤器

Intent是一种消息对象,你可以使用它来请求其他应用组件(app component)的操作。尽管intent有许多方式可以帮助组件之间进行交流,大体上有这三种最基础的用例:

  • 启动activity:

    在应用中,一个Activity代表一个页面。你可以传递一个IntentstartActivity()来启动一个新的Activity实例。该Intent描述了要启动的activity,并且可以携带任何需要的数据。

    调用startActivityForResult()来启动activity能在它结束时接收返回的结果。你的activity会在onActivityResult()回调中接收到另外一个Intent对象作为返回的结果。更多信息,请参阅Activities指南。

  • 启动service:

    Service是一种可以不需要用户界面、在后台执行操作的组件。你可以传递一个IntentstartService()来开启一个service,通过service来执行一次性的操作(例如下载文件)。Intent描述了要启动的service,并且可以携带任何需要的数据。

    如果service是使用客户端-服务器接口设计的,你可以从其他组件传递一个IntentbindService()来绑定到service上。更多信息,请参阅Services指南。

  • 分发broadcast:

    broadcast是一种任何应用都能接收到的消息。系统会为系统事件分发不同的broadcast,比如系统启动时或设备充电时。你可以传递一个IntentsendBroadcast()sendOrderedBroadcast()sendStickyBroadcast()来把broadcast分发给其他应用。

Intent类型


intent有两种类型:

  • 显式intent 通过名称(全限定类名)来指定要启动的组件。你通常可以使用显式intent来启用你应用里的组件,因为你知道你想启动的activity和service的类名。例如,启动一个响应用户操作的新的activity或者启动一个在后台下载文件的service。
  • 隐式intent 不用指定组件的名称,但是必须声明要执行的操作,这样其他应用的组件就能捕获到它。例如,如果你想在地图上显示用户的位置,你可以使用隐式intent去请求其他满足条件的应用在地图上显示具体的位置。

当你创建了显式intent来启动activity或service时,系统就会立即启动在Intent对象中指定的组件。

 

图1. 插图显示了隐式intent如何通过系统分发来启动另外的activity:[1] Activity A创建带操作描述的Intent,然后把它传递给startActivity()。 [2] Android系统搜索所有的应用来寻找匹配该intent的intent filter。当有匹配时,[3] 系统通过把Intent传递给匹配的activity(Activity B)然后调用它的onCreate()方法启动它。

当你创建了隐式intent时,Android系统就会通过把intent里的内容与设备上其他应用在manifest file中声明的intent filter进行比较来找出合适的组件启动。如果intent能匹配到一个intent filter,系统就会启动那个组件,并把Intent对象传递给它。如果匹配了不止一个intent filter,系统就会显示一个对话框,让用户来挑选使用哪个应用。

intent filter是一种在应用manifest文件里的表达式,它说明了组件想要收到的intent类型。例如,为activity声明intent filter,其他应用就能使用某种确定类型的intent启动你的activity。同样地,如果你没有为activity声明任何intent filter,那么它只能被显式intent启动。

注意:为了保证应用的安全性,在启动Service时请确保使用显式intent,并且不要为你的service声明intent filter。使用隐式intent启动service是有安全风险的,因为你不能确定什么service会响应你的intent,并且用户也不知道哪个service启动了。

构建Intent


Intent对象携带了一些信息,Android系统可以使用它们来决定启动哪个组件(例如确切的组件名或可以接收该intent的组件类别),为了确保接收的组件能正确的执行操作,intent还可以携带一些附加的信息给接收的组件使用(例如要执行的操作和处理的数据)。

Intent中主要包含的信息如下:

组件名称
要启动组件的名称。

这是可选的,但是建议使用显示intent,这意味着该intent只会被分发给组件名称定义的应用组件。如果没有组件名称,那么这个intent就是隐式的,系统会基于intent中的其他信息决定应该把该intent分发给哪个组件(例如下面描述的操作、数据和类别)。所以如果你希望启动应用中某个特定的组件,那么你应该指定组件的名称。

注意:启动Service时,你应该永远指定组件的名称。 否则,你无法确定哪个service会响应那个intent,并且用户也不知道哪个service启动了。

Intent里名称的域变量是ComponentName对象,你可以使用目标组件的包含应用包名的全限定类名来指定。例如,com.example.ExampleActivity。你可以使用setComponent()setClass()setClassName()Intent的构造方法来设置组件名称。

Action
表示要执行操作的字符串(比如view或pick)。

在与broadcast相关的intent里,就表示会发生并报告的操作。action大体上决定了intent剩余的部分的结构,具体来讲就是data和extra里包含什么。

虽然你可以自己定义action来启用自己应用中的组件(或为其他应用来调用你应用里的组件),但是你最好使用Intent类或其他框架类中已定义好的操作常量。下面是启动activity的一些常见操作:

ACTION_VIEW
当你有些信息需要启动activity来展示给用户看,可以在 startActivity()处理的intent里使用这个action,比如在相册应用里预览图片,或在地图应用中查看地址。
ACTION_SEND
也被称为“分享”intent,当你有些数据需要通过其他应用来分享时,可以在 startActivity()处理的intent里使用这个action,例如邮件应用或社交分享应用。

请参阅Intent类的参考文档获取更多定义action的常量。其他action定义在Android框架的其他地方,例如在Settings中定义了一些打开系统设置应用指定页面的action。

你可以使用setAction()Intent构造方法来为intent指定action。

如果你定义了自己的action,请确保它包含了你应用的包名作为前缀。例如:

static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";
Data
一种URI( Uri 对象),代表要操作的数据和/或数据的MIME类型。提供的data类型通常是intent的action决定的。例如,如果action是 ACTION_EDIT ,那么data应该包含要编辑文档的URI。

在创建intent时,通常在data的URI里附加指定data的类型(MIME类型),这点非常重要。例如,可以显示图片的activity可能就不能播放音频文件了,尽管URI的格式看起来很相似。所以指定data的MIME类型可以帮助Android系统找到最适合的组件来接收你的intent。然而,MIME类型有时可以从URI中推出来,特别当data是content(一种指明数据存储在设备位置的URI,受到可以使data得MIME类型对系统可见的ContentProvider控制

仅设置data的URI,请调用setData()。仅设置MIME类型,请调用setType()。如果需要的话,你可以使用setDataAndType()同时设置它们。

注意:如果你想同时设置URI和MIME类型,不要调用setData()setType(),因为它们都会让对方的值失效。一定要使用setDataAndType()来设置URI和MIME类型。

Category
一种字符串,包含了一些附加的信息,关于应该捕获intent的组件的类别。虽然在intent里可以放置任意数量的category描述,但是大多数的intent甚至都不需要category。下面是一些常见category:
CATEGORY_BROWSABLE
目标activity允许自己被浏览器启动来显示链接引用的数据,例如图像或邮件信息。 
CATEGORY_LAUNCHER
activity是任务栈里最顶端的activity,并在系统应用启动器里列举出来。

请参阅Intent类的描述查看全部的category列表。

你可以使用addCategory()来指定category。

上面列举的这些属性(组件名称、action、data和category)可以定义独一无二的intent。通过读取这些属性,Android系统可以决定应该启动哪个组件。

然而,intent同样也可以携带其他信息,它们不会影响到决定启动哪个组件。intent也支持: 

Extras
键-值对携带了完成请求action必须的附加信息。就像某些action需要特定类型的data URI,有些action也需要特定的extra。

你可以使用多种putExtra()方法来添加extra数据,它们都需要两种参数:键的名称和值。你也可以创建一个Bundle对象来添加所有的extra数据,然后使用putExtras()方法把Bundle插入到Intent中。

例如,当使用 ACTION_SEND创建了一个用来发送邮件的intent,你可以把“地址”指定给 EXTRA_EMAIL键,把“主题”指定给 EXTRA_SUBJECT键。

Intent类为标准数据类型指定了许多EXTRA_*常量。如果你需要声明自己的extra键(为你的应用接收的intent使用),请确保包含你应用的包名作为前缀。例如:

static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";
Flags
Flag定义在 Intent类中,对于intent来说它的功能就像元数据。flag可能会告诉Android系统如何启动activity(例如,activity应该属于哪个 task,启动后如何处理activity(例如,是否在最近启动activity列表中))。

更多信息,请参阅setFlags()方法。

显式intent事例

你可以使用显式intent启动指定的应用组件,例如你应用中某个特定的activity或service。为Intent对象指定组件名称就能创建显式的intent了,其他的intent属性是可选的。

例如,如果你在应用中构建了一个叫 DownloadService的service,用来从网络上下载文件,那么你可以使用下面的代码来启动它:

// 在Activity中执行,所以'this'即Context
// fileUrl是URL字符串,比如"http://www.example.com/image.png"
Intent downloadIntent = new Intent(this, DownloadService.class);
downloadIntent.setData(Uri.parse(fileUrl));
startService(downloadIntent);

构造方法Intent(Context, Class)传递应用的Context和组件的Class对象。像这样,intent显式的启动了应用中的DownloadService类。

更多关于构建和启动service的信息,请参阅Services指南。

隐式intent事例

隐式intent指定了action,它可以调用设备上任何可以执行该action的应用。当你的应用无法执行某个action,但是其他应用可能可以,并且你希望让用户来选择使用哪个应用,这种情形下使用隐式intent是非常有用的。

例如,如果你有一些想要用户分享给其他人的内容,使用ACTION_SEND action创建intent,然后把表示要分享内容的extra添加进来。当你使用那个intent调用startActivity()后,用户就能选择使用哪个应用来分享内容了。

注意:也有可能用户的设备上没有任何可以处理你通过startActivity()发送过来的隐式intent的应用。如果真的那样,调用会失败并且你的应用会崩溃掉。为了验证是否有activity能接收到该intent,应该在你的Intent对象上调用resolveActivity()。如果结果是非null的,那么说明至少有一个应用可以处理该intent,这样就能安全的调用startActivity()了。如果结果是null的,那么你不应该使用这个intent,如果可能的话,你应该禁用会发出该intent的功能。


// 创建字符串文本消息
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
sendIntent.setType(HTTP.PLAIN_TEXT_TYPE); // MIME类型为"text/plain"

// 验证该intent是否会匹配activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
    startActivity(sendIntent);
}

注意: 在这种情况下,虽然并没有使用到URI,但是通过声明intent的data类型来指定extra中携带的内容。

当调用了startActivity(),系统会检查所有安装的应用来确定哪个应用应该处理这种类型的intent(action类型为ACTION_SEND,并且携带"text/plain”数据的intent)。如果只有一个应用可以处理它,那么会立即打开那个应用,并且该intent会传递给那个应用。 如果多个activity都可以处理该intent,那么系统会给用户显示一个对话框,让用户来选择使用哪个应用。

 

图2. 选择对话框A chooser dialog.

强制应用选择器

当有不止一个应用可以响应你的隐式intent时,用户可以选择使用哪个应用并且使它作为该action的默认选择。对于执行那种用户以后可能愿意用相同应用处理的action来说,这是非常棒得功能。比如打开网页(用户更经常使用某种浏览器)。

然而,如果多种应用都可以响应该intent并且用户每次都想使用不同的应用来处理,那么你应该显式的显示选择对话框。该选择对话框每次都会询问用户使用哪个应用来处理该action(用户不能为该action选择默认的应用)。例如,当你的应用使用ACTION_SEND action来“分享”时,用户可能希望根据当前的情况来选择不同的应用来分享,所以你应该总是使用选择对话框,就像图2所示的。

使用createChooser()创建个Intent,然后把它传递给startActivity(),这样就能显示一个选择器了。例如:

Intent intent = new Intent(Intent.ACTION_SEND);
...

// 对于UI上的文本请使用字符串资源。
// 这里说些什么,比如“Share this photo with”
String title = getResources().getString(R.string.chooser_title);
// 创建intent来显示选择器
Intent chooser = Intent.createChooser(intent, title);

// 检验至少有一个activity可以匹配该intent
if (sendIntent.resolveActivity(getPackageManager()) != null) {
    startActivity(sendIntent);
}
然后就会显示一个对话框,内容是响应通过 createChooser()方法 传递过去的intent的应用列表,标题是传递的文本信息。

接收隐式Intent


为了声明你的应用可以接收到哪些隐式intent,你应该在manifest file中使用<intent-filter>元素为每个应要组件声明一个或多个intent filter。每个intent filter都会根据intent的action、data和category来指定它能接收到的intent类型。如果intent能满足你的其中一个intent filter,系统就会把这个隐式intent分发给你的应用组件。

注意:不管该组件有没有声明其他的intent filter,该隐式intent总是会被分发给它的目标。

应用组件应该为每个它能处理的独立功能单独声明filter。例如,在图库应用中某个activity可能有两个filter:一个filter用来预览图片,另一个用来编辑图片。当启动activity时,它会检查Intent,然后根据该Intent中的信息决定操作(比如是否显示编辑选项)。

每个intent filter都是在应用的manifest file里通过<intent-filter>元素定义的,并且嵌套在相关的应用组件中(比如<activity>元素)。在<intent-filter>中,你可以使用下面三个元素中的一个或多个来指定它能接受的intent类型:

<action>
name属性里声明intent接收的action。值必须是action的字面字符串值,而不是类常量。
<data>
使用一个或多个能指定data URI各方面信息( schemehostportpath等)和MIME类型的属性来声明接收的data类型。
<category>
name属性里声明intent接收的category 。值必须是action的字面字符串值,而不是类常量。

注意:为了接收隐式intent,你必须在intent filter中包含CATEGORY_DEFAULT category。方法startActivity()startActivityForResult()默认把所有的intent当做声明为CATEGORY_DEFAULT category来处理。如果你没有在intent filter中这样声明,就不会有隐式intent传递给你的activity。

例如,下面是用intent filter声明的一个activity,它可以接收action为ACTION_SEND并且data是文本的intent:

<activity android:name="ShareActivity">
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
    </intent-filter></activity>

创建一个包含多个<action><data>、或<category>的filter是允许的。如果你这么做了,只需要确保那个组件能匹配这个filter元素中的所有元素。

如果你想要处理多种不同的intent,但是它们仅仅匹配一些特定的action、data和category类型,那么你就需要创建多种intent filter了。

限制对组件的访问

使用intent filter来阻止其他应用启动你的应用不是一种安全的方式。尽管intent filter限制了组件只会响应某些特定的隐式intent,但是如果开发者公开了组件的名称,其他应用就可以悄悄的通过显式intent来启用你的组件。如果要求只有你自己应用才能启动你的组件,请把那些组件的exported属性设置为“false”

隐式intent会把intent里的这三种元素里的每样都拿来和filter进行测试对比。intent必须通过所有的这三种类型的测试后才能被分发给对应的组件。如果它一样都没匹配,那么Android系统不会把该intent分发给组件。不过,因为组件可能拥有多个intent filter,如果某个intent没有匹配组件的一个filter,它还有可能匹配另外一个filter。更多关于系统如何解析intent的信息请参阅下面关于Intent Resolution的章节。

注意:请确保每次都使用显式intent去启动自己的service,并且不要为你的service声明intent filter,因为这样可以避免不经意间运行其他应用的Service

注意:对于activity来说,你必须在manifest文件声明intent filter。但是,board cast receiver的filter可以通过调用registerReceiver()来动态注册。然后你可以使用unregisterReceiver()来解除receiver。这样可以让你的应用只在运行期间的某个时间段里监听特定的broadcast。

filter事例

为了更好的理解intent filter的行为,请阅读下面某个社交分享应用的manifest file的代码片段。

<activity android:name="MainActivity">

    <!-- 这个activity是主入口,应该显示在应用启动器上 -->
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

<activity android:name="ShareActivity">

    <!-- 这个activity处理带文本数据的“SEND” action -->
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
    <!-- 这个activity可以处理媒体数据的“SEND”和“SEND_MULTIPLE” -->
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <action android:name="android.intent.action.SEND_MULTIPLE"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="application/vnd.google.panorama360+jpg"/>
        <data android:mimeType="image/*"/>
        <data android:mimeType="video/*"/>
    </intent-filter>
</activity>

第一个activity,MainActivity,是应用的主入口,用户通过应用的启动图标启动应用后就打开这个activity了:

  • ACTION_MAIN action说明这是主入口并且不需要任何intent data。this is the main entry point and does not expect any intent data.
  • CATEGORY_LAUNCHER category说明这个activity的图标应该出现在系统的应用启动器中。如果<activity>元素没有使用icon指定图标,那么系统会使用<application>元素中的图标。

为了在应用启动器中出现该activity,这两个必须成对出现。

第二个activity,ShareActivity,被用来分享文本和媒体内容。尽管用户可能直接从MainActivity的导航功能进入该activity,它们同样也可以直接从其他应用发出匹配这两个intent filter之一的的隐式intent来进入ShareActivity。 

注意:这个MIME类型,application/vnd.google.panorama360+jpg,是一种指定全景图片的特殊数据类型,你可以使用Google panorama API来处理它。

使用Pending Intent


PendingIntent对象是对Intent对象的包装。PendingIntent的主要作用是给外部应用权限来使用被包含的intent,就好像在你的应用自己的进程中执行一样。

pending intent的主要使用情形包括:

因为每个Intent对象都被设计成被特定类型的应用组件处理(ActivityServiceBroadcastReceiver),所以PendingIntent和他们情况相同。在使用pending intent时,你的应用不是调用诸如startActivity()等方法来执行该intent,而是通过调用相关构造方法创建PendingIntent时声明目标组件类型:

除非你的应用要接收其他应用的pending intent,否则你可能只用得到上面的PendingIntent方法来创建PendingIntent

每种方法都需要当前应用的Context,和你想要包装的Intent,还有一个或多个用来指定该intent应该如何被使用的flag(比如该intent是否能被多次使用)。

更多关于使用pending intent的信息请查看对应使用案例的文档,例如NotificationsApp Widgets API指南。

Intent解析


当系统接收到一个启动activity的隐式intent时,它会基于下面三个方面来把intent与intent filter进行比较,找出最佳的activity:

  • intent action
  • intent data (URI和data类型都考虑)
  • intent category

下面的章节描述了intent如何依据应用的manifest file里的intent filter匹配合适的组件。

Action测试

指定接收的intent action,intent filter可以声明0个或多个<action>元素。例如:

<intent-filter>
    <action android:name="android.intent.action.EDIT" />
    <action android:name="android.intent.action.VIEW" />
    ...
</intent-filter>

要匹配这个filter的话,在Intent中指定的action必须要匹配上面列举的action之一。

如果filter中没有列举任何action,那么就没有东西给intent来匹配了,所有的intent都不会通过测试。但是,如果Intent没有指定任何action,那么它会通过测试(只要filter包含至少一个action)。

Category测试

定接收的intent category,intent filter可以声明0个或多个<category>元素。例如:

<intent-filter>
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    ...
</intent-filter>

intent要通过category测试的话,在Intent中的每个category都必须匹配filter中的category。反之不必要,intent filter中可能声明的category比Intent还多,但是Intent依然会通过测试。因此,没有category的intent不管filter里声明了什么category都会通过测试。

注意:使用startActivity()startActivityForResult()时,Android会自动的在所有的隐式intent中添加CATEGORY_DEFAULT category。所以如果你想要你的activity能接收到这些隐式intent的话,就必须在它的intent filter中包含"android.intent.category.DEFAULT category(如前面<intent-filter> 例子所示)。

Data测试

指定接收的intent data,intent filter可以声明0个或多个<data>元素。例如:

<intent-filter>
    <data android:mimeType="video/mpeg" android:scheme="http" ... />
    <data android:mimeType="audio/mpeg" android:scheme="http" ... />
    ...
</intent-filter>

每个<data> 元素可以指定一个URI结构和一个数据类型(MIME媒体类型)。有许多独立的属性,schemehostportpath,对应着URI的各个部分:

<scheme>://<host>:<port>/<path>

例如:

content://com.example.project:200/folder/subfolder/etc

在这个URI中,scheme是content,host是com.example.project,端口是200,path是folder/subfolder/etc

<data>元素中的这些属性都是可选的,但是它们有线形依赖关系:

  • 如果没有指定schema,那么host就被忽略了。
  • 如果没有指定host,那么port就被忽略了。
  • 如果schema和host都没有被指定,那么path就被忽略了。

当把intent中的URI与filter中指定的URI标准进行比较时,只需要比较filter中包含的部分URI就行了。例如:

  • 如果filter只指定了scheme,所有拥有那个scheme的URI都会匹配这个filter。
  • 如果filter指定了scheme和authority,但是没有path,那么所有拥有相同scheme和authority的URI都会通过该filter,而不用管它们的path是什么。
  • 如果filter指定了scheme、authority和path,那么只有拥有相同scheme、authority和path的才能通过该filter。

注意:path表达式可能包含星号(*)通配符,会匹配部分path名称。

data测试会把intent中的URI和MIME类型与filter中指定的URI和MIME类型进行比较。 规则如下:

  1. 既不包含URI,又不包含MIME类型的intent只能通过那些没有指定任何URI和MIME类型的filter的测试。
  2. 包含URI,但是不包含MIME类型(URI中没有隐含也无法推理出来)的intent只能通过那些URI格式匹配并且没有指定MIME类型的filter的测试。
  3. 包含MIME类型,但是不包含URI的intent只能通过那些有相同MIME类型并且没有指定URI格式的filer的测试。
  4. 既包含URI又包含MIME类型(URI隐含或者可以推理出来)的intent,它的MIME类型匹配filter中列举的MIME类型时,它才能通过MIME类型部分的测试,它的URI匹配filter中的URI,或它包含content:file:URI并且filter没有指定URI时,它才能通过URI部分的测试。

最后一条规则,就是说组件可以从文件或content provider中获取本地数据。因此,它们的filter可以只列举data type,而不需要显式标明content:file:scheme。这是一种典型得使用方式。例如,像下面的<data>元素,告诉Android这个组件可以从content provider中获取图片并显示:

<intent-filter>
    <data android:mimeType="image/*" />
    ...
</intent-filter>

因为大多数的数据都是通过content provider分发的,所以filter只指定data类型而不指定URI是很常见的。

另外一种常见得filter配置是使用scheme和data type。例如,像下面的<data>元素,告诉Android这个组件可以从网络上获取视频数据来播放:

<intent-filter>
    <data android:scheme="http" android:type="video/*" />
    ...
</intent-filter>

Intent匹配

intent与intent filter比较不只是用来发现要启动的目标组件,同时也为发现设备上组件的其他信息。例如,Home应用通过寻找所有指定了action intent为ACTION_MAIN action和 CATEGORY_LAUNCHER category的activity来放置应用启动器。

你的应用可以很容易的使用intent匹配。PackageManager有一系列得query...()方法,它可以返回所有可以接收某个特定intent的组件,同样,还有一系列的resolve...()方法,可以找出最适合响应某个intent的组件。例如,queryIntentActivities()会返回能执行参数intent的所有activity列表,同样,queryIntentServices()会返回的service列表。所有的方法都不会激活组件的,它们只是列举它们能影响的组件列表。同样,queryBroadcastReceivers()针对broadcast receivers。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值