Android Notification

日记月累第四天。发现做事情坚持真的很重要,其实我今天都有点凌乱了,不想记录了,在坚持一下吧,毕竟才第四天。今天对昨天同事所说的Notification进行了一下温故今天主要是拿来主意,明天代码实践。今天公司又有同事分享了工具git的使用,真是学不完的东西,明天对常用的git命令进行分享。

参考自:http://docs.eoeandroid.com/guide/topics/ui/notifiers/notifications.html


目录

状态通知--Status Notifications

一个状态通知添加一个图标到系统的状态栏(带有可选的ticker-text消息)和通知消息到通知窗口。当用户选择了一个通知,Android会启动一个Intent,这个Intent是由Notification定义的(通常是为了启动一个Activity)你也可以自定义通知来通知用户,例如用声音文件,振动或者是闪动的通知灯。

当一个后台服务需要通知用户一个需要反应的事件的时候,状态通知就要被用到了。后台服务永远不需要启动一个activity与用户互动,而是应该创建一个状态通知--当用户选择的时候可以启动一个activity。

如图1 在状态栏的左边有一个通知的图标

status_bar.png

图1 状态栏上的图标

如图2 显示在通知窗口里的信息

notifications_window.png

图2 通知窗口

设计-Notification Design

想了解设计准则,请阅读 Android Design's Notifications指南

基础-The Basics

一个Activity或者Service可以初始化一个状态通知。然而,因为一个activity只有运行在前台并且它所在的窗口获得焦点的时候,它才能执行动作,所以你通常需要用service来创建通知。当用户正在使用其他应用或者设备待机时,通知可以由后台创建。为了创建通知,你必须用到两个类:NotificationNotificationManager

使用Notification类的一个实例去定义状态通知的属性,如图标,通知信息和一些其他的设定,如播放的声音。NotificationManager是系统服务,它来执行和管理所有的状态通知。你不需要直接初始化NotificationManager。为了把你的通知给它,你必须使用getSystemService()检索到指向NotificationManager的引用,然后,当你要通知用户的时候,使用android.app.Notification) notify()把你的Notification传给它。

按如下方式创建状态通知:

1. 得到指向NotificationManager的引用:

String ns = Context.NOTIFICATION_SERVICE;
NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns);

2. 初始化Notification:
 
int icon = R.drawable.notification_icon;
CharSequence tickerText = "Hello";
long when = System.currentTimeMillis();
 
Notification notification = new Notification(icon, tickerText, when);
 
3. 定义通知的信息和PendingIntent:
 
Context context = getApplicationContext();
CharSequence contentTitle = "My notification";
CharSequence contentText = "Hello World!";
Intent notificationIntent = new Intent(this, MyClass.class);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
 
notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
 
4. 把Notification传递给NotificationManager:
 
private static final int HELLO_ID = 1;
 
mNotificationManager.notify(HELLO_ID, notification);


 

响应通知-Responding to Notifications

关于通知,用户体验的核心围绕在怎样使它与应用程序的界面相结合。你必须正确地去实现它以便于为你的应用带来统一的用户体验。

日历应用和电子邮件的两个典型的通知例子:活动即将开始的通知;新邮件到来的通知。他们所展现的是两个被推荐的处理通知的形式:或者启动一个与应用无关的独立的activity,或者调用一个应用的全新的实例。

接下来的场景展示了activity栈应该如何在这两种典型的通知流里面工作,首先来操作日历的通知:

1. 用户正在日历应用里创建一个新的活动。他们意识到需要复制一部分电子邮件消息到这个活动中来。

2. 用户选择Home>Email

3. 当操作Email应用时,用户收到从日历应用那里收到一个即将开始的会议的通知。

4. 用户选择了通知,他们开始关注那个日历应用所展现出来的简要的会议信息。

5. 用户已经确定了他们即将有个会议,所以他们按了返回键。他们现在返回了Email应用--接到通知的地方。

操作电子邮件的通知:

1. 目前用户正在电子邮件应用里撰写邮件,需要核对一下日期。

2. 用户选择Home>Calendar(日历应用)

3. 而在日历应用中,他们收到电子邮件关于一个新的讯息的通知。

4. 用户选择了通知,这使他们回到了电子邮件展示详细信息的页面。这取代了他们以前所做的(写信),但这信件仍然保存在草稿箱中。

5. 用户按下了Back键,来到了信件列表页面,然后按下Back键返回日历应用。

在电子邮件那样风格的通知中,按下通知所调出的UI以一种形式展示出通知的应用。例如,当电子邮件应用通过通知来到前台展示的时候,他展示出的是一个列表还是一个具体信件信息要依赖于新邮件的数量,是多封还是一封。为了达到这种效果,我们需要用一个新的acitivity栈所展现出的新通知的状态来完全代替当下的任何一种状态。

下面的代码举例说明了显示这种通知。最关键的是makemessageintentstack()方法,这个方法构建了一个由intent组成的数组-代表了在这种状态下,应用的新的activity栈(如果你正在使用片段(fragments),你可能需要初始化你的片段和应用的状态,从而按下Back键时UI会返回到前一次的状态),其中的核心是Intent.makeRestartActivityTask(),它在栈里构建了根activity,并使用了合适的标志(flag),如Intent.FLAG_ACTIVITY_CLEAR_TASK

 
/**
 * This method creates an array of Intent objects representing the
 * activity stack for the incoming message details state that the
 * application should be in when launching it from a notification.
 */
static Intent[] makeMessageIntentStack(Context context, CharSequence from,
        CharSequence msg) {
    // A typical convention for notifications is to launch the user deeply
    // into an application representing the data in the notification; to
    // accomplish this, we can build an array of intents to insert the back
    // stack stack history above the item being displayed.
    Intent[] intents = new Intent[4];
 
    // First: root activity of ApiDemos.
    // This is a convenient way to make the proper Intent to launch and
    // reset an application's task.
    intents[0] = Intent.makeRestartActivityTask(new ComponentName(context,
            com.example.android.apis.ApiDemos.class));
 
    // "App"
    intents[1] = new Intent(context, com.example.android.apis.ApiDemos.class);
    intents[1].putExtra("com.example.android.apis.Path", "App");
    // "App/Notification"
    intents[2] = new Intent(context, com.example.android.apis.ApiDemos.class);
    intents[2].putExtra("com.example.android.apis.Path", "App/Notification");
 
    // Now the activity to display to the user.  Also fill in the data it
    // should display.
    intents[3] = new Intent(context, IncomingMessageView.class);
    intents[3].putExtra(IncomingMessageView.KEY_FROM, from);
    intents[3].putExtra(IncomingMessageView.KEY_MESSAGE, msg);
 
    return intents;
}
 
/**
 * The notification is the icon and associated expanded entry in the
 * status bar.
 */
void showAppNotification() {
    // look up the notification manager service
    NotificationManager nm = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
 
    // The details of our fake message
    CharSequence from = "Joe";
    CharSequence message;
    switch ((new Random().nextInt()) % 3) {
        case 0: message = "r u hungry?  i am starved"; break;
        case 1: message = "im nearby u"; break;
        default: message = "kthx. meet u for dinner. cul8r"; break;
    }
 
    // The PendingIntent to launch our activity if the user selects this
    // notification.  Note the use of FLAG_CANCEL_CURRENT so that, if there
    // is already an active matching pending intent, cancel it and replace
    // it with the new array of Intents.
    PendingIntent contentIntent = PendingIntent.getActivities(this, 0,
            makeMessageIntentStack(this, from, message), PendingIntent.FLAG_CANCEL_CURRENT);
 
    // The ticker text, this uses a formatted string so our message could be localized
    String tickerText = getString(R.string.imcoming_message_ticker_text, message);
 
    // construct the Notification object.
    Notification notif = new Notification(R.drawable.stat_sample, tickerText,
            System.currentTimeMillis());
 
    // Set the info for the views that show in the notification panel.
    notif.setLatestEventInfo(this, from, message, contentIntent);
 
    // We'll have this notification do the default sound, vibration, and led.
    // Note that if you want any of these behaviors, you should always have
    // a preference for the user to turn them off.
    notif.defaults = Notification.DEFAULT_ALL;
 
    // Note that we use R.layout.incoming_message_panel as the ID for
    // the notification.  It could be any integer you want, but we use
    // the convention of using a resource id for a string related to
    // the notification.  It will always be a unique number within your
    // application.
    nm.notify(R.string.imcoming_message_ticker_text, notif);
}
 
在日历应用风格的通知中,被通知调用的UI是一个专用的activity,而不是应用流程中的一部分。例如,当用户接受到日历应用的通知时,点击查看,触发了一个特殊的activity来显示即将开始的活动的列表-这个界面只能由通知调用,而不能由日历应用中的界面进入。
这种通知的代码是很简单的,就像上面的代码,但是PendingIntent只能用于单个的activity,我们那个专门的通知activity。
 
/**
 * The notification is the icon and associated expanded entry in the
 * status bar.
 */
void showInterstitialNotification() {
    // look up the notification manager service
    NotificationManager nm = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
 
    // The details of our fake message
    CharSequence from = "Dianne";
    CharSequence message;
    switch ((new Random().nextInt()) % 3) {
        case 0: message = "i am ready for some dinner"; break;
        case 1: message = "how about thai down the block?"; break;
        default: message = "meet u soon. dont b late!"; break;
    }
 
    // The PendingIntent to launch our activity if the user selects this
    // notification.  Note the use of FLAG_CANCEL_CURRENT so that, if there
    // is already an active matching pending intent, cancel it and replace
    // it with the new Intent.
    Intent intent = new Intent(this, IncomingMessageInterstitial.class);
    intent.putExtra(IncomingMessageView.KEY_FROM, from);
    intent.putExtra(IncomingMessageView.KEY_MESSAGE, message);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
    PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
            intent, PendingIntent.FLAG_CANCEL_CURRENT);
 
    // The ticker text, this uses a formatted string so our message could be localized
    String tickerText = getString(R.string.imcoming_message_ticker_text, message);
 
    // construct the Notification object.
    Notification notif = new Notification(R.drawable.stat_sample, tickerText,
            System.currentTimeMillis());
 
    // Set the info for the views that show in the notification panel.
    notif.setLatestEventInfo(this, from, message, contentIntent);
 
    // We'll have this notification do the default sound, vibration, and led.
    // Note that if you want any of these behaviors, you should always have
    // a preference for the user to turn them off.
    notif.defaults = Notification.DEFAULT_ALL;
 
    // Note that we use R.layout.incoming_message_panel as the ID for
    // the notification.  It could be any integer you want, but we use
    // the convention of using a resource id for a string related to
    // the notification.  It will always be a unique number within your
    // application.
    nm.notify(R.string.imcoming_message_ticker_text, notif);
}
 
然而这还不够。通常Android认为一个应用里的所有activity都是这个应用界面流程的一部分,所以像这样简单地调用activity能够导致:使这个activity被混入应用的返回栈中。为了让它正确的工作,在manifest声明的时候,android:launchMode="singleTask", android:taskAffinity="" 和android:excludeFromRecents="true"是必须要设置的。例子如下:
 
<activity android:name=".app.IncomingMessageInterstitial"
        android:label="You have messages"
        android:theme="@style/ThemeHoloDialog"
        android:launchMode="singleTask"
        android:taskAffinity=""
        android:excludeFromRecents="true">
</activity>

你必须要小心翼翼地通过这个初始的activity来调用其他的activity,因为它不是应用的最上层,也没有在最近出现过,而且它需要被通知调用,任何时候。最好的方法就是确保任何被它调用的activity都是在它自己的任务中被调用的。当这么做的时候,必须要小心,确保这个新的任务和当前存在于应用中的任务能够很好的交互。这在本质上与之前提到的电子邮件风格的通知,切换到主界面的情况一样。想想之前的makeMessageIntentStack() ,处理一个点击事件然后看起来是这样的:

 
/**
 * Perform a switch to the app.  A new activity stack is started, replacing
 * whatever is currently running, and this activity is finished.
 */
void switchToApp() {
    // We will launch the app showing what the user picked.  In this simple
    // example, it is just what the notification gave us.
    CharSequence from = getIntent().getCharSequenceExtra(IncomingMessageView.KEY_FROM);
    CharSequence msg = getIntent().getCharSequenceExtra(IncomingMessageView.KEY_MESSAGE);
    // Build the new activity stack, launch it, and finish this UI.
    Intent[] stack = IncomingMessage.makeMessageIntentStack(this, from, msg);
    startActivities(stack);
    finish();
}


管理通知-Managing your Notifications

NotificationManager是系统服务,负责管理所有的通知。你必须使用getSystemService()得到它的引用。如下:

 
String ns = Context.NOTIFICATION_SERVICE;
NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns);

当你要发表通知时,要使用notify(int, Notification)Notification传递给NotificationManager。方法的第一个参数是通知的唯一的ID,第二个参数就是Notification对象。ID唯一地标识了通知是来自你的应用程序。如果你需要更新通知或者(如果你的应用管理不同种类的通知)当用户通过通知中定义的intent返回到你的应用时,选择合适的action,那么ID就是必需的。

当用户点击选择通知后,为了清空通知,给你的Notification添加一个标志""FLAG_AUTO_CANCEL",你也可以使用cancel(int)手动清空它,参数是通知的ID,或者使用cancelAll()清空所有通知。

创建通知-Creating a Notification

Notification对象定义了通知的细节信息和其他提醒的设置,例如声音和闪灯。

一个状态通知需要以下:

  • 状态栏的图标

可选的设定包括:

  • 标题栏的滚动文本
  • 提醒的声音
  • 振动的设定
  • 闪屏的设定

新通知的starter-kit包括Notification(int, CharSequence, long)构造函数和setLatestEventInfo(Context, CharSequence, CharSequence, PendingIntent)方法。这些为通知定义了所有的需求。下面是一个基本的通知:

 
int icon = R.drawable.notification_icon;        // icon from resources
CharSequence tickerText = "Hello";              // ticker-text
long when = System.currentTimeMillis();         // notification time
Context context = getApplicationContext();      // application Context
CharSequence contentTitle = "My notification";  // message title
CharSequence contentText = "Hello World!";      // message text
 
Intent notificationIntent = new Intent(this, MyClass.class);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
 
// the next two lines initialize the Notification, using the configurations above
Notification notification = new Notification(icon, tickerText, when);
notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent
 

更新通知-Updating the notification

当事件继续出现在你的应用中的时候,你就可以更新状态通知的信息。例如,当上一个条短信被阅读之前又来了一条新短信,这时短信应用会更新已存在的通知,给未读信息条数加一。这个更新通知的实践要比添加一个新通知好得多,因为它避免了在通知窗口里遇到的混乱。

因为每个通知都是由NotificationManager定义的,有着唯一的ID,你可以通过调用setLatestEventInfo()来修改通知,然后再调用一次notify().

你可以修改对象的每个属性(除了Context和通知的标题和内容)。你应该随时修改文本信息当调用setLatestEventInfo()并且contentTitle和contentText都有新值的时候,然后调用notify()(当然,如果你已经创建了custom notification layout,那么更新标贴和文本是不起作用的)

添加声音-Adding a sound

你可以使用默认的声音文件或者自定义的声音文件来提醒用户。

为了使用默认声音,要给defaults属性赋值"DEFAULT_SOUND":

 
notification.defaults |= Notification.DEFAULT_SOUND;

为了给你的通知使用不同的声音,需要把声音文件的Uri传递给sound属性。如下:

notification.sound = Uri.parse("file:///sdcard/notification/ringer.mp3");

在下面的例子里,我们从内部的MediaStore's ContentProvider:

 
notification.sound = Uri.withAppendedPath(Audio.Media.INTERNAL_CONTENT_URI, "6");
 

在上面的例子中,数字6是媒体文件的ID,而且被加在了Uri的后面。如果你不知道确切的ID,你必须使用ContentResolverMediaStore要查询一下。你可以查看Content Providers文档,来了解如何使用ContentResolver。

如果你希望提示音可以反复的播放,直到用户对通知做出了反应或者通知被取消了,可以把FLAG_INSISTENT赋值给flags属性。

注意:如果defaults属性的值是DEFAULT_SOUND,那么无论设置什么声音都不会有效果的,仍只会播放默认的声音。

添加振动-Adding vibration

你可以用默认的振动方式或自定义的振动方式来提示用户。

默认的方式,要用到DEFAULT_VIBRATE

notification.defaults |= Notification.DEFAULT_VIBRATE;

自定义的方式,要是定义一个long型数组,赋值给vibrate属性:

long[] vibrate = {0,100,200,300};
notification.vibrate = vibrate;
 

long型的数组定义了交替振动的方式和振动的时间(毫秒)。第一个值是指振动前的准备(间歇)时间,第二个值是第一次振动的时间,第三个值又是间歇的时间,以此类推。振动的方式任你设定。但是不能够反复不停。

注意:如果defaults属性的值是DEFAULT_VIBRATE,那么无论设置什么振动都不会有效果的,仍只会以默认的方式振动。

添加闪灯-Adding flashing lights

使用闪灯来提示用户,你可以使用默认的也可以自定义

默认的方式,DEFAULT_LIGHTS

notification.defaults |= Notification.DEFAULT_LIGHTS;
 

可以自定义灯光的颜色和闪动的方式。ledARGB属性是定义颜色的,ledOffMS属性是定义灯光关闭的时间(毫秒),ledOnMs是灯光打开的时间(毫秒),还要给flags属性赋值为FLAG_SHOW_LIGHTS

 
notification.ledARGB = 0xff00ff00;
notification.ledOnMS = 300;
notification.ledOffMS = 1000;
notification.flags |= Notification.FLAG_SHOW_LIGHTS;

 

上面的例子中,绿色的灯亮了300毫秒,暗了1秒,,,,如此循环。

更多特性-More features

你可以使用Notification和标志(flags)来为自己的通知做定制。下面是一些有用的特性:

FLAG_AUTO_CANCEL标志

使用这个标志可以让在通知被选择之后,通知提示会自动的消失。

FLAG_INSISTENT标志

使用这个标志,可以让提示音循环播放,知道用户响应。

FLAG_ONGOING_EVENT标志

使用这个标记,可以让该通知成为正在运行的应用的通知。 这说明应用还在运行-它的进程还跑在后台,即使是当应用在前台不可见(就像音乐播放和电话通话)。

FLAG_NO_CLEAR标志

使用这个标志,说明通知必须被清除,通过"Clear notifications"按钮。如果你的应用还在运行,那么这个就非常有用了。

number属性

这个属性的值,指出了当前通知的数量。这个数字是显示在状态通知的图标上的。如果你想使用这个属性,那么当第一个通知被创建的时候,它的值要从1开始。而不是零。

iconLevel

这个属性的值,指出了LevelListDrawable的当前水平。你可以通过改变它的值与LevelListDrawable定义的drawable相关联,从而实现通知图标在状态栏上的动画。查看LevelListDrawable可以获得更多信息。

查看Notification可以了解更多。

自定义通知的布局-Creating a Custom Notification Layout

custom_message.png

图3 自定义布局

默认的,通知窗口里的通知会包括标题和消息文本两部分。它们是通过setLatestEventInfo()定义了contentTitle和contentText来实现的。然而,你也可以使用RemoteViews为通知界面定义一个布局。它看起来与默认的布局很像,但实际上是由XML创建的。

为了自定义通知的布局,首先实例化RemoteViews创建一个布局文件,然后把RemoteViews传递给Notification的contentView属性。

通过下面的例子可以更好的理解:

1. 创建一个XML布局文件 custom_notification.xml:

 
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:padding="10dp" >
    <ImageView android:id="@+id/image"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:layout_alignParentLeft="true"
        android:layout_marginRight="10dp" />
    <TextView android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/image"
        style="@style/NotificationTitle" />
    <TextView android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/image"
        android:layout_below="@id/title"
        style="@style/NotificationText" />
</RelativeLayout>
 
注意那两个TextView的style属性。在定制的通知界面中,为文本使用style文件进行定义是很重要的,因为通知界面的背景色会因为不同的硬件,不同的os版本而改变。从android2.3(API 9)开始,系统为默认的通知界面定义了文本的style属性。因此,你应该使用style属性,以便于在android2.3或更高的版本上可以清晰地显示你的文本,而不被背景色干扰。
例如,在低于android2.3的版本中使用标准文本颜色,应该使用如下的文件res/values/styles.xml:
 
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="NotificationText">
      <item name="android:textColor">?android:attr/textColorPrimary</item>
    </style>
    <style name="NotificationTitle">
      <item name="android:textColor">?android:attr/textColorPrimary</item>
      <item name="android:textStyle">bold</item>
    </style>
    <!-- If you want a slightly different color for some text,
         consider using ?android:attr/textColorSecondary -->
</resources>
 


然后,在高于android2.3的系统中使用系统默认的颜色,如下文件res/values-v9/styles.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="NotificationText" parent="android:TextAppearance.StatusBar.EventContent" />
    <style name="NotificationTitle" parent="android:TextAppearance.StatusBar.EventContent.Title" />
</resources>

 

现在,当运行在2.3版本以上时,在你的定制界面中,文本都会是同一种颜色-系统为默认通知界面定义的颜色。这很重要,能保证你的文本颜色是高亮的,即使背景色是意料之外的颜色,你的文本页也会作出适当的改变。

2. 现在,在应用的代码中,使用RemoveViews方法定义了图片和文本。然后把RemoveViews对象传给contentView属性。例子如下:

 
RemoteViews contentView = new RemoteViews(getPackageName(), R.layout.custom_notification_layout);
contentView.setImageViewResource(R.id.image, R.drawable.notification_image);
contentView.setTextViewText(R.id.title, "Custom notification");
contentView.setTextViewText(R.id.text, "This is a custom layout");
notification.contentView = contentView;



如上所示,把应用的包名和布局文件的ID传给RemoteViews的构造器。然后分别使用setImageViewResource()setTextViewText()定义ImageView和TextView的内容。最后,吧RemoteViews对象传递给通知的contentView属性。

3.因为当你在使用定制界面时,不需要使用setLatestEventInfo(),你必须为通知定义一个intent,并赋值给contentIntent属性,如下代码:

 
Intent notificationIntent = new Intent(this, MyClass.class);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.contentIntent = contentIntent;


4.发送通知:

 
mNotificationManager.notify(CUSTOM_VIEW_ID, notification);
 

RemoteViews类还包括一些方法可以让你轻松地在通知界面的布局里添加ChronometerProgressBar。如果要为你的通知界面做更多的定制,请参考RemoteViews

特别注意:当创建一个自定义通知布局,你必须特别小心,以确保布局能地在不同的设备上适当地显示。而这个建议适用于所有视图布局创建,不单单是通知界面布局,在这种情况下(通知界面)尤其重要,因为是布局的空间是非常有限的。所以不要让你的自定义布局太复杂并确报在不同的配置上测试它。





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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值