Android Activity输入事件分发机制

6 篇文章 0 订阅

前言

Android输入事件的分发主要为按键(KeyEvent)事件和触摸(MotionEvent)事件,事件由ViewRootImp经DecorView层层传递到子控件中,最终由子控件处理的一个过程

KeyEvent事件类型

public static final int ACTION_DOWN             = 0;
public static final int ACTION_UP               = 1; 
public static final int ACTION_MULTIPLE         = 2;

MotionEvent事件类型

public static final int ACTION_DOWN             = 0;
public static final int ACTION_UP               = 1;
public static final int ACTION_MOVE             = 2;
public static final int ACTION_CANCEL           = 3;
输入事件从WindowInputEventReceiver到DecorView的整个流程如下:

在这里插入图片描述

Activity::attach

ActivityManagerService回调ActivityThread的performLaunchActivity,和Activity的onCreate函数对应。performLaunchActivity函数会去创建新的Activity,并且调用activity的attach函数来对初始化mWindow,设置Window回调函数为当前Activity

	// step 1, 创建PhoneWindow对象
	mWindow = new PhoneWindow(this, window, activityConfigCallback);
	mWindow.setWindowControllerCallback(this);
	// step 2 , 设置回调函数为当前Activity
	mWindow.setCallback(this);
Activity::makeVisible

ActivityManagerService回调ActivityThread的performResumeActivity,和Activity的onResume函数对应。performResumeActivity函数将Activity中的mDecor添加到WindowManagerGlobal中并添加相对应的Window到WindowManagerService中,然后将mDecor设置为可见状态,此时的Activity界面就能显示出来了

void makeVisible() {
	// step 1, getWindowManager()对象为WindowManagerImpl对象
	ViewManager wm = getWindowManager();
	// step 2, wm.addView最终调用的是WindowManagerGlobal的addView函数
	wm.addView(mDecor, getWindow().getAttributes());
	// step 3, mDecor设置为可见状态
	mDecor.setVisibility(View.VISIBLE);
}
WindowManagerGlobal::addView

WindowManagerGlobal设置为单列模式,一个进程中对应一个实列,用来和WindowManagerService之间通信, WindowManagerGlobal有几个比较重要的成员变量sWindowSession, mViews,mRoots,mParams

// 用来和WindowManagerService之间通信
private static IWindowSession sWindowSession;

// 保存Activity的根View
private final ArrayList<View> mViews = new ArrayList<View>();

// 一个窗口对应一个ViewRootImpl
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();

// 窗口属性
private final ArrayList<WindowManager.LayoutParams> mParams =
		new ArrayList<WindowManager.LayoutParams>();
public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
	// step 1 , 创建ViewRootImpl对象
	root = new ViewRootImpl(view.getContext(), display);
	view.setLayoutParams(wparams);
	// step 2 , 将view, mRoots,wparams参数保存在WindowManagerGlobal成员变量中
	mViews.add(view);
	mRoots.add(root);
	mParams.add(wparams);
	// step 3 , 调用ViewRootImpl的setView函数,
	root.setView(view, wparams, panelParentView);
}
ViewRootImpl::setView

ViewRootImpl是APK进程和WindowManagerService之间的中转站,根据窗口属性LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL来判断view是否能接收输入事件,如果对应的bit位不为0,则表示能接收输入事件,创建对应的输入通道,mWindowSession将ViewRootImpl中的mWinodw添加到WMS中,把mInputChannel也传进去,他是out参数, WMS创建完socket输入系统关联上后返回给APK端(客服端)

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
	synchronized (this) {
		if (mView == null) {

			mView = view;
			// step 1 , 如果窗口能接收事件,则创建InputChannel
			if ((mWindowAttributes.inputFeatures
					& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
				mInputChannel = new InputChannel();
			}

			// step 2 , 把mWindow添加到WindowManagerService中
			mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);

			if (mInputChannel != null) {
				if (mInputQueueCallback != null) {
					mInputQueue = new InputQueue();
					mInputQueueCallback.onInputQueueCreated(mInputQueue);
				}
				// step 3, 创建事件接收者
				mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
						Looper.myLooper());
			}


			// Set up the input pipeline.
			// step 4, 构建事件处理的责任链,处理KeyEvent和MotionEvent事件为ViewPostImeInputStage
			CharSequence counterSuffix = attrs.getTitle();
			mSyntheticInputStage = new SyntheticInputStage();
			InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
			InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
					"aq:native-post-ime:" + counterSuffix);
			InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
			InputStage imeStage = new ImeInputStage(earlyPostImeStage,
					"aq:ime:" + counterSuffix);
			InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
			InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
					"aq:native-pre-ime:" + counterSuffix);

			mFirstInputStage = nativePreImeStage;
			mFirstPostImeInputStage = earlyPostImeStage;

		}
	}
}
WindowInputEventReceiver

WindowInputEventReceiver是Apk进程中的事件接收者,WindowInputEventReceiver继承InputEventReceiver,当有系统按键派发到APK进程中时,会触发InputEventReceiver的onInputEvent函数

final class WindowInputEventReceiver extends InputEventReceiver {
	public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
		super(inputChannel, looper);
	}

	@Override
	public void onInputEvent(InputEvent event, int displayId) {
		// step 1, 最后一个参数为true表示需要立即处理这个事件
		enqueueInputEvent(event, this, 0, true);
	}
}
ViewRootImpl::enqueueInputEvent

将InputEvent对象封装到QueuedInputEvent中,last不为空,则将event事件添加到队尾,为空,则添加到队列头,先来先处理的原则。onInputEvent中调用的enqueueInputEvent事件,processImmediately为true, 需要马上处理此事件。

void enqueueInputEvent(InputEvent event,
            InputEventReceiver receiver, int flags, boolean processImmediately) {
    // step 1, 获取输入事件队列
	QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
	QueuedInputEvent last = mPendingInputEventTail;
	if (last == null) {
		mPendingInputEventHead = q;
		mPendingInputEventTail = q;
	} else {
		last.mNext = q;
		mPendingInputEventTail = q;
	}
	mPendingInputEventCount += 1;

	// step 2, 处理输入事件
	if (processImmediately) {
		doProcessInputEvents();
	} else {
		// scheduleProcessInputEvents通过handler发送异步消息,最终也是调用doProcessInputEvents
		scheduleProcessInputEvents();
	}
}
ViewRootImpl::doProcessInputEvents

mPendingInputEventHead指向队列头部,循环遍历QueuedInputEvent链表, 通过deliverInputEvent函数派发输入事件

void doProcessInputEvents() {
	// step 1, 派发队列中的输入事件
	while (mPendingInputEventHead != null) {
		QueuedInputEvent q = mPendingInputEventHead;
		mPendingInputEventHead = q.mNext;
		if (mPendingInputEventHead == null) {
			mPendingInputEventTail = null;
		}
		q.mNext = null;

		mPendingInputEventCount -= 1;


		long eventTime = q.mEvent.getEventTimeNano();
		long oldestEventTime = eventTime;
		if (q.mEvent instanceof MotionEvent) {
			MotionEvent me = (MotionEvent)q.mEvent;
			if (me.getHistorySize() > 0) {
				oldestEventTime = me.getHistoricalEventTimeNano(0);
			}
		}
		mChoreographer.mFrameInfo.updateInputEventTime(eventTime, oldestEventTime);
		// step 2 , 传递输入事件
		deliverInputEvent(q);
	}

	// step 3, 从handler中移除MSG_PROCESS_INPUT_EVENTS异步消息
	if (mProcessInputEventsScheduled) {
		mProcessInputEventsScheduled = false;
		mHandler.removeMessages(MSG_PROCESS_INPUT_EVENTS);
	}
}
ViewRootImpl::deliverInputEvent

InputStage责任链在setView的时候设置

private void deliverInputEvent(QueuedInputEvent q) {
	// step 1, 找到对应的责任链
	InputStage stage;
	if (q.shouldSendToSynthesizer()) {
		stage = mSyntheticInputStage;
	} else {
		stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
	}

	// step 2, 朝着责任链派发事件
	if (stage != null) {
		stage.deliver(q);
	} else {
		// step 3, 事件处理完成, 告知系统(InputDispatch线程),派发下一个输入事件
		finishInputEvent(q);
	}
}
InputStage::deliver

如果q.mFlags的QueuedInputEvent.FLAG_FINISHED的bit位不为0,表示事件消费完了,接下来处理下一事件。每个InputStage对象都实现了onProcess函数,返回值为FORWARD,FINISH_HANDLED,FINISH_NOT_HANDLED, 如果返回FORWARD表示事件需要沿责任链往下传,FINISH_HANDLED或FINISH_NOT_HANDLED则finish当前事件,派发结束

public final void deliver(QueuedInputEvent q) {
	if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
		// step 1, 派发下一个事件
		forward(q);
	} else if (shouldDropInputEvent(q)) {
		// step 2, 事件处理完成, 结束事件
		finish(q, false);
	} else {
		// step 3, onProcess函数对事件的处理
		// apply根据此事件是否消费,是否需要继续往下流转
		apply(q, onProcess(q));
	}
}
InputStage::apply
protected void apply(QueuedInputEvent q, int result) {
	if (result == FORWARD) {
		// step 1, 派发下一个事件
		forward(q);
	} else if (result == FINISH_HANDLED) {
		// step 2, 结束事件
		finish(q, true);
	} else if (result == FINISH_NOT_HANDLED) {
		// step 3, 结束事件
		finish(q, false);
	} else {
		throw new IllegalArgumentException("Invalid result: " + result);
	}
}
InputStage::forward
protected void forward(QueuedInputEvent q) {
	// 派发下一个事件
	onDeliverToNext(q);
}
ViewPostImeInputStage::onProcess

在没有输入法的情况下,处理按键事件和触摸事件的主要InputStage为ViewPostImeInputStage

protected int ViewPostImeInputStage::onProcess(QueuedInputEvent q) {
	if (q.mEvent instanceof KeyEvent) {
		// step 1, 处理按键事件
		return processKeyEvent(q);
	} else {
		final int source = q.mEvent.getSource();
		if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
			// step 2, 处理触摸事件
			return processPointerEvent(q);
		} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
			return processTrackballEvent(q);
		} else {
			return processGenericMotionEvent(q);
		}
	}
}

KeyEvent事件分析

ViewPostImeInputStage::processKeyEvent

mView为DecorView

private int processKeyEvent(QueuedInputEvent q) {
	final KeyEvent event = (KeyEvent)q.mEvent;

	// step1 , 派发按键事件
	if (mView.dispatchKeyEvent(event)) {
		return FINISH_HANDLED;
	}

	return FORWARD;
}
DecorView::dispatchKeyEvent

mWindow.getCallback对象为Activity, 调用actvity的dispatchKeyEvent(event)函数,如果事件被activity消费了, 则返回ture,直接返回了,当Activity没有消费这个按键事件时,则会调用PhoneWindow的onKeyDown或onKeyUp事件

public boolean dispatchKeyEvent(KeyEvent event) {
	final int keyCode = event.getKeyCode();
	final int action = event.getAction();
	final boolean isDown = action == KeyEvent.ACTION_DOWN;


	if (!mWindow.isDestroyed()) {
		// step 1, Window.Callback为Acitivity对象
		final Window.Callback cb = mWindow.getCallback();
		final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
				: super.dispatchKeyEvent(event);
		if (handled) {
			return true;
		}
	}

	// step 2, 按键事件没有被activity处理,则将按键交给PhoneWindow的onKeyDown,onKeyUp处理
	return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
			: mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
}
Activity::dispatchKeyEvent

首先判断如果是KEYCODE_MENU事件,则交给mActionBar处理, 不是则将事件交给PhoneWindow的superDispatchKeyEvent处理, 最终又回到DecorView中, 如果Window不能处理此事件,最后交由event.dispatch处理, 第一个参数传入为this,最终回调Activity的onKeyDown,onKeyUp来处理按键事件

public boolean dispatchKeyEvent(KeyEvent event) {

	// step 1, KEYCODE_MENU事件,如果有mActionBar, 则交给mActionBar处理
	final int keyCode = event.getKeyCode();
	if (keyCode == KeyEvent.KEYCODE_MENU &&
			mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
		return true;
	}

	Window win = getWindow();
	// step 2, 将事件交给PhoneWindow的superDispatchKeyEvent处理
	if (win.superDispatchKeyEvent(event)) {
		return true;
	}
	View decor = mDecor;
	if (decor == null) decor = win.getDecorView();
	// step 3, PhoneWindow没有处理KeyEvent事件,则交给event.dispatch处理
	// event.dispatch传入this,最终回调Activity的onKeyDown,onKeyUp处理事件
	return event.dispatch(this, decor != null
			? decor.getKeyDispatcherState() : null, this);
}
public class Activity extends ContextThemeWrapper
        implements KeyEvent.Callback {}
PhoneWindow::superDispatchKeyEvent
public boolean superDispatchKeyEvent(KeyEvent event) {
	// step 1, 调用DecorView的superDispatchKeyEvent
	return mDecor.superDispatchKeyEvent(event);
}
DecorView::superDispatchKeyEvent

DecorView继承FrameLayout,FrameLayout继承ViewGroup,ViewGroup继承View

public boolean superDispatchKeyEvent(KeyEvent event) {
	// step 1,调用ViewGroup的superDispatchKeyEvent
	return super.dispatchKeyEvent(event);
}
ViewGroup::dispatchKeyEvent
public boolean dispatchKeyEvent(KeyEvent event) {

	if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
			== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
		// step 1, 调用View的dispatchKeyEvent函数
		if (super.dispatchKeyEvent(event)) {
			return true;
		}
	} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
			== PFLAG_HAS_BOUNDS) {
		// step 2, 调用mFocused View的dispatchKeyEvent函数
		if (mFocused.dispatchKeyEvent(event)) {
			return true;
		}
	}

	return false;
}
View::dispatchKeyEvent
public boolean dispatchKeyEvent(KeyEvent event) {

	// step 1, 如果View实现了mOnKeyListener,则调用View的mOnKeyListener的onKey函数
	ListenerInfo li = mListenerInfo;
	if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
			&& li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
		// 事件消费, 直接返回
		return true;
	}

	// step 2, 事件没有消费完,则调用View自身的onKeyDown,onKeyUp处理事件
	if (event.dispatch(this, mAttachInfo != null
			? mAttachInfo.mKeyDispatchState : null, this)) {
		return true;
	}
	return false;
}
KeyEvent事件时序图

在这里插入图片描述

MotionEvent事件分析

ViewPostImeInputStage::processPointerEvent
private int ViewPostImeInputStage::processPointerEvent(QueuedInputEvent q) {
	final MotionEvent event = (MotionEvent)q.mEvent;
	// 调用DecorView的dispatchPointerEvent
	boolean handled = mView.dispatchPointerEvent(event);
	return handled ? FINISH_HANDLED : FORWARD;
}
View::dispatchPointerEvent

DecorView和ViewGroup都没有dispatchPointerEvent函数,dispatchPointerEvent函数实现在View中,View中的dispatchPointerEvent函数又调用dispatchTouchEvent函数,ViewGroup中重写父类dispatchTouchEvent函数,dispatchTouchEvent函数最终调用的是ViewGroup中的dispatchTouchEvent函数

public final boolean dispatchPointerEvent(MotionEvent event) {
	if (event.isTouchEvent()) {
		// step 1, 派发触摸事件
		return dispatchTouchEvent(event);
	} else {
		return dispatchGenericMotionEvent(event);
	}
}
ViewGroup::dispatchTouchEvent_01

ViewGroup::dispatchPointerEvent函数有点复杂,我们拆开来分析, 如果事件是down事件则清空TouchTartget链表, 重置状态。接下来调用onInterceptTouchEvent函数判断是否要将此事件拦截

final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;

// step 1, 如果是down事件,重置状态,触摸事件是以down事件开始
if (actionMasked == MotionEvent.ACTION_DOWN) {
	cancelAndClearTouchTargets(ev);
	resetTouchState();
}

// step 2,事件是否被拦截,intercepted返回为true表示拦截
// mFirstTouchTarget表示消费down事件的view
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
		|| mFirstTouchTarget != null) {
	final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
	if (!disallowIntercept) {
		// step 3, onInterceptTouchEvent是否需要拦截此事件
		intercepted = onInterceptTouchEvent(ev);
		ev.setAction(action); // restore action in case it was changed
	} else {
		intercepted = false;
	}
} else {
	// There are no touch targets and this action is not an initial down
	// so this view group continues to intercept touches.
	intercepted = true;
}
ViewGroup::dispatchTouchEvent_02

人为是不能触发canceled事件的, 触摸事件没有被取消并且没有被拦截的情况下,遍历所有子View或ViewGroup,在子View或ViewGroup区域内的触摸事件交给子对应的View或ViewGroup处理,调用dispatchTransformedTouchEvent函数来做后面的处理

// step 3, 事件没有取消,并且没有被拦截
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
	if (actionMasked == MotionEvent.ACTION_DOWN
			|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
			|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
		final int actionIndex = ev.getActionIndex(); // always 0 for down
		// step 4, 遍历ViewGroup中的子View或子ViewGroup
		final int childrenCount = mChildrenCount;
		if (newTouchTarget == null && childrenCount != 0) {
			final float x = ev.getX(actionIndex);
			final float y = ev.getY(actionIndex);
			final View[] children = mChildren;
			for (int i = childrenCount - 1; i >= 0; i--) {
				// step 5, 如果能接收触摸事件并且触摸点在对应的子View内,则往下走
				if (!canViewReceivePointerEvents(child)
						|| !isTransformedTouchPointInView(x, y, child, null)) {
					ev.setTargetAccessibilityFocus(false);
					continue;
				}

				newTouchTarget = getTouchTarget(child);
				// step 6, 调用dispatchTransformedTouchEvent来处理触摸事件
				if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
					// step 7, 将当前view添加到TouchTarget链表中
					newTouchTarget = addTouchTarget(child, idBitsToAssign);
					alreadyDispatchedToNewTouchTarget = true;
					break;
				}
			}
		}
	}
}
ViewGroup::dispatchTouchEvent_03

最后交给dispatchTransformedTouchEvent函数来做处理

// step 8, 如果没有子View处理down事件,则ViewGroup自己处理
if (mFirstTouchTarget == null) {
	handled = dispatchTransformedTouchEvent(ev, canceled, null,
			TouchTarget.ALL_POINTER_IDS);
} else {
	TouchTarget target = mFirstTouchTarget;
	while (target != null) {
		final TouchTarget next = target.next;
		// step 9, alreadyDispatchedToNewTouchTarget为true表示此触摸事件为down事件,并且已经消费
		if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
			handled = true;
		} else {
			// step 10, 派发给对应的target对应的View处理
			if (dispatchTransformedTouchEvent(ev, cancelChild,
					target.child, target.pointerIdBits)) {
				handled = true;
			}
		}
	}
}
ViewGroup::dispatchTransformedTouchEvent

如果ViewGroup没有子View则自己处理,否则交给子View或子ViewGroup处理,递归处理

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
		View child, int desiredPointerIdBits) {
	final boolean handled;

	// step 1, 如果没有子View,super则为View, 调用View的dispatchTouchEvent函数
	if (child == null) {
		handled = super.dispatchTouchEvent(transformedEvent);
	} else {
		final float offsetX = mScrollX - child.mLeft;
		final float offsetY = mScrollY - child.mTop;
		transformedEvent.offsetLocation(offsetX, offsetY);
		if (!child.hasIdentityMatrix()) {
			transformedEvent.transform(child.getInverseMatrix());
		}
		// step 2, 递归调用View或ViewGroup的dispatchTouchEvent函数
		handled = child.dispatchTouchEvent(transformedEvent);
	}

	return handled;
}
View::dispatchTouchEvent

View的mOnTouchListener处理事件的优先级高于View自己的onTouchEvent

public boolean dispatchTouchEvent(MotionEvent event) {

	boolean result = false;
	// step 1, event事件带有MotionEvent.FLAG_WINDOW_IS_OBSCURED标记会返回false
	if (onFilterTouchEventForSecurity(event)) {
		if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
			result = true;
		}
		// step 2, View的mOnTouchListener不为空且view为ENABLE状态
		// 则调用mOnTouchListener的onTouch函数来处理MotionEvent事件
		ListenerInfo li = mListenerInfo;
		if (li != null && li.mOnTouchListener != null
				&& (mViewFlags & ENABLED_MASK) == ENABLED
				&& li.mOnTouchListener.onTouch(this, event)) {
			// step 3, 事件被mOnTouchListener消费
			result = true;
		}

		// step 4, 事件没有被消费, 则调用View的onTouchEvent函数来处理MotionEvent事件
		if (!result && onTouchEvent(event)) {
			// step 5, 事件被onTouchEvent消费
			result = true;
		}
	}

	// 返回事件处理结果
	return result;
}
MotionEvent事件时序图

在这里插入图片描述

总结

触摸事件的传导流程中,一些重要的函数或监听函数如下:
在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值