您的位置:首页 > 移动开发 > Android开发

Android ViewGroup 触摸屏事件派发机制和源码分析

2017-02-14 14:59 447 查看

Android ViewGroup 触摸屏事件派发机制和源码分析

Android 中不管是View 还是ViewGoup,触摸事件来的时候都是从dispatchTouchEvent开始的.其中dispatchTouchEvent()是View.java 的方法,ViewGroup 只是重写了这个方法.看ViewGroup的dispatchTouchEvent() 之前最好先看View的dispatchTouchEvent().View 的逻辑简单些,而且ViewGroup的dispatchTouchEvent()最终也会调用View的dispatchTouchEvent().以下分析基于Android L.我们现在从ViewGroup的dispatchTouchEvent()开始分析.相关代码如下:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
if (DBG_MOTION || DBG_TOUCH) {
Xlog.d(TAG, "(ViewGroup)dispatchTouchEvent 1: ev = " + ev + ",mFirstTouchTarget = "
+ mFirstTouchTarget + ",this = " + this);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {//调用View.java判断当前View 没有被遮蔽
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();//mFirstTouchTarget设置为null;mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;//true:不拦截;false:拦截
if (!disallowIntercept) {//拦截
intercepted = onInterceptTouchEvent(ev);// 默认返回false
/// M : add log to help debugging
if (intercepted == true && DBG_TOUCH) {
Xlog.d(TAG, "Touch event was intercepted event = " + ev
+ ",this = " + this);
}
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;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
	ev.setTargetAccessibilityFocus(false);
}
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;//是否cancle事件
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;//默认true,用于判断分发事件,
作用是判断可以把事件分发到多个子View.
if (DBG_MOTION) {
Xlog.d(TAG, "(ViewGroup)dispatchTouchEvent 2: actionMasked = " + actionMasked
+ ",intercepted = " + intercepted + ",canceled = " + canceled + ",split = "
+ split + ",mChildrenCount = " + mChildrenCount + ",mFirstTouchTarget = "
	+ mFirstTouchTarget + ",this = " + this);
}
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {//没有被拦截
// If the event is targeting accessiiblity focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
	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
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {//! View.VISIBLE || ! is in view
		ev.setTargetAccessibilityFocus(false);
if (DBG_MOTION) {
Xlog.d(TAG, "(ViewGroup)dispatchTouchEvent continue 6: i = "
+ i + ",count = " + childrenCount + ",child = " + child
			+ ",this = " + this);
}
continue;
}
newTouchTarget = getTouchTarget(child);//Down事件:由于之前的
mFirstTouchTarget已经被设置为null,这里newTouchTarget 赋值结果也是null
if (DBG_MOTION) {
Xlog.d(TAG, "(ViewGroup)dispatchTouchEvent to child 3: child = "
+ child + ",childrenCount = " + childrenCount + ",i = " + i
+ ",newTouchTarget = " + newTouchTarget
+ ",idBitsToAssign = " + idBitsToAssign
+ ",mFirstTouchTarget = " + mFirstTouchTarget
		+ ",this = " + this);
}
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {//将事件传递给child view
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
			break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
	ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {//事件没有被消耗掉或者被拦截了
if (DBG_MOTION) {
Xlog.d(TAG, "(ViewGroup)dispatchTouchEvent mFirstTouchTarget = null,"
+ " canceled = " + canceled + ",this = " + this);
}
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
//内部会转调super.dispatchTouchEvent也就是View的onTouchEvent(),//可以理解为:如果如果视图的child 都没有消耗掉这个事件,那么视图自己来调用onTouchEvent.//还有一种情况是被拦截了,也是会调用自己的onTouchEvent
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it.  Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {//ACTION_DWON执行这里
handled = true;
} else {//其他事件move/up执行这里
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
	handled = true;
}
if (DBG_MOTION) {
Xlog.d(TAG, "dispatchTouchEvent middle 5: cancelChild = " + cancelChild
+ ",mFirstTouchTarget = " + mFirstTouchTarget + ",target = "
+ target + ",predecessor = " + predecessor + ",next = " + next
	+ ",this = " + this);
}
if (cancelChild) {//child 通知父元素拦截后续事件
if (predecessor == null) {
mFirstTouchTarget = next;//此时next=null,所以mFirstTouchTarget=null,后续再由其他事件来就是执行的上面的if分支而不是现在的else分支
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (DBG_MOTION) {
Xlog.d(TAG, "(ViewGroup)dispatchTouchEvent end 4: handled = " + handled
+ ",mFirstTouchTarget = " + mFirstTouchTarget + ",this = " + this);
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
[/code]上面这个方法有点长,我们先从ACTION_DWON事件开始分析.在ACTION_DWON 事件处理之前,是一些关于手手势操作的处理,不是我们目前要关注的重点,接这再调用View.java判断当前View 没有被遮蔽,这些和View.java的dispatchTouchEvent()相似.从24行就是ACTION_DWON的处理逻辑的开始.其中cancelAndClearTouchTargets()主要是执行一个ACTION_CANCEL和设置mFirstTouchTarget为null.看注释就可以知道,执行ACTION_CANCEL是为了防止ANR和其他原因导致上一个触摸动作的ACTION_CANCEL或者ACTION_UP被丢失了.resetTouchState():mFirstTouchTarget设置为null同时执行mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT.总之ACTION_DWON 来了之后mFirstTouchTarget会被设置为null,而且mGroupFlags 已经被设置了不容许拦截的标志.接着看34-48行代码,为了方便将这一小段重新贴出来.
//......................
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {//action=down的时候就可以通过了
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//true:不拦截;false:拦截
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);// 默认返回false
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
}//....................
[/code]由于之前执行了cancelAndClearTouchTargets()和resetTouchState(),mGroupFlags& FLAG_DISALLOW_INTERCEPT就等于0,所以booelan disallowIntercept现在就是false,也就是说接下来会执行onInterceptTouchEvent().这个方法在ViewGroup里面默认是直接返回false的.所以变量intercepted的值由onInterceptTouchEvent()的返回值来决定.上面又说到mGroupFlags 设置是否容许拦截标志位的问题,我们在源码中搜索会发现有requestDisallowInterceptTouchEvent(),这个方法是公开用来调用,决定后续事件来的时候父层的view是否可以调用它的onInterceptTouchEvent().对于底层的View来说,有一种方法可以阻止父层的View截获touch事件,就是调用getParent().requestDisallowInterceptTouchEvent(true);方法。一旦底层View收到touch的action后调用这个方法那么父层View就不会再调用onInterceptTouchEvent了,也无法截获以后的action.61行是判断是否ACTION_CANCLE事件的,并保持到boolean canceled变量中.65行是判断是否可以分发事件,并保存到split变量中,作用是判断可以把事件分发到多个子View.这个同样在ViewGroup中提供了public的方法:publicvoidsetMotionEventSplittingEnabled(booleansplit)来设置.75行if (!canceled && !intercepted) {,就是开始事件的分发处理.ACTION_DOWN事件执行到这里canceled = false,intercepted的值由onInterceptTouchEvent(ev)来决定.85行 if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE)这个条件满足了才会将touch事件传递到子View,当然也要child view存在也就是mChildrenCount >0. 也就是133行的if (newTouchTarget == null && childrenCount != 0)是否可以通过,由于newTouchTarget 在ACTION_DOWN时是null,所以这个条件pass.接着会调用buildOrderedChildList()方法得到一个子View的ArrayList 集合,然后从childrenCount - 1 到0开始循环查找那个View是View.VISIBIE状态或者在View视图的范围之内,然后调用dispatchTransformedTouchEvent来让child view处理事件.135行调用getTouchTarget(child)来赋值newTouchTarget.当是Down事件时:由于之前的mFirstTouchTarget已经被设置为null,这里newTouchTarget 赋值结果也是null,所以181行的break跳出for循环暂时不会执行.152行开始事件传递到child View,if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))这个if判断的结果由dispatchTransformedTouchEvent()来决定,这个方法代码很长就不贴出来,但是流程不是很复杂.dispatchTransformedTouchEvent在这里它最终会调用child.dispatchTouchEvent(),并且将child.dispatchTouchEvent()的返回值作为dispatchTransformedTouchEvent()的返回值,也就是if()判断的条件值.显然dispatchTransformedTouchEvent就是递归调用dispatchTouchEvent()方法,如果递归传递到了一个View哪里,一般都会执行onTouchEvent(),并且onTouchEvent()的返回值就是dispatchTouchEvent()的返回值.(为什么是一般会执行需要查看View.java)在这个if()通过之后会做如下3件事情:a.调用addTouchTarget()来赋值newTouchTarget,同时mFirstTouchTarget也会被赋值,也就是说mFirstTouchTarget和newTouchTarget都不会是null了.b.设置alreadyDispatchedToNewTouchTarget= true,这个变量和他的名字一样就是说已经将touch时间传递给新的目标view处理了,也就是消耗掉了.c.跳出上面的for循环,因为一个touch事件只能被一个目标View消耗掉,也就是一个View的dispatchTouchEvent()返回true.以上代码的分析其实都还是只有ACTION_DOWN才能执行的,后面192-247就是一个if-else,而且具体怎么走是有mFirstTouchTarget== null来判断.而mFirstTouchTarget 由前面调用dispatchTransformedTouchEvent()返回true再来调用addTouchTarget()来设置值的.而前面的dispatchTransformedTouchEvent()返回值就是有递归调用的子View 的dispatchTouchEvent()返回boolean参数来决定. dispatchTouchEvent()返回值一般和onTouchEvent()是一致的,也是由他决定的.所以dispatchTransformedTouchEvent()的返回值由onTouchEvent()决定.这也决定了后面其他move和up事件来的时候192-247行里面的具体怎么执行.
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
[/code]注意上面提到的addTouchTarget()在第一次执行的时候mFirstTouchTarget还是null,所以target.next 也会是null,但是target.child不会是null.这个在子视图调用getParent().requestDisallowInterceptTouchEvent(false)来让父元素拦截后续事件的时候很重要.192-247行:如果mFirstTouchTarget ==null,说明ACTION_DOWN没有被消耗掉或者被拦截掉了.接着执行dispatchTransformedTouchEvent(),这个方法的第3个参数是null,最终结果就是在方法内部执行的时候是会调用super.dispatchTouchEvent(),也就是说不会继续传递给子View了.其中super.dispatchTouchEvent()也就是View的dispatchTouchEvent(),这个方法内部会执行onTouchEvent()方法.(可以参考View的触摸屏事件派发机制和分析.)如果mFirstTouchTarget !=null,执行后面的while循环.206行:这个while循环内部有一个if-else来处理,其中if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) 只有Down事件才会执行这里,因为alreadyDispatchedToNewTouchTarget 在这个方法的内部开头是false,只有在169行才能设置为true,169行只有是Down相关的事件才能走的流程,168行的addTouchTarget会使 target== newTouchTarget为true.这个分支就是表示事件已经被子View消耗掉了,ViewGoup自己接下来什么都没有了,直接return true,等后面的move/up事件而else分支就是其他事件move/up才会走的流程.其实也是执行dispatchTransformedTouchEvent()方法,只是参数不一样.上面反复提到dispatchTransformedTouchEvent(),这里单独分析一下.
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case.  We don't need to perform any transformations
// or filtering.  The important part is the action, not the contents.
final int oldAction = event.getAction();
if (DBG_MOTION) {
Xlog.d(TAG, "dispatchTransformedTouchEvent 1: event = " + event + ",cancel = "
+ cancel + ",oldAction = " + oldAction + ",desiredPointerIdBits = "
+ desiredPointerIdBits + ",mFirstTouchTarget = " + mFirstTouchTarget
+ ",child = " + child + ",this = " + this);
}
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
if (DBG_MOTION) {
Xlog.d(TAG, "Dispatch cancel action end: handled = " + handled + ",oldAction = "
+ oldAction + ",child = " + child + ",this = " + this);
}
return handled;
}
// Calculate the number of pointers to deliver.
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
if (newPointerIdBits == 0) {
Xlog.i(TAG, "Dispatch transformed touch event without pointers in " + this);
return false;
}
// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
	handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
	handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
if (DBG_MOTION) {
Xlog.d(TAG, "dispatchTransformedTouchEvent 2 to child " + child
+ ",handled = " + handled + ",mScrollX = " + mScrollX + ",mScrollY = "
+ mScrollY + ",mFirstTouchTarget = " + mFirstTouchTarget + ",event = "
+ event + ",this = " + this);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
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());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
if (DBG_MOTION) {
Xlog.d(TAG, "dispatchTransformedTouchEvent 3 to child " + child + ",handled = "
+ handled + ",mScrollX = " + mScrollX + ",mScrollY = " + mScrollY
+ ",mFirstTouchTarget = " + mFirstTouchTarget + ",transformedEvent = "
+ transformedEvent + ",this = " + this);
}
// Done.
transformedEvent.recycle();
return handled;
}
[/code]其实逻辑很简单,如果第2个参数cancel为true,就是表示这个事件是ACTION_CANCEL,起码意见被当成是ACTION_CANCEL,接着判断第3个参数child是否为null,child为null,就调用父类的dispatchTouchEvent,也就是执行super. dispatchTouchEvent().如果3个参数child不是为null,就调用child. dispatchTouchEvent(),并且这个dispatchTouchEvent()的返回值就是dispatchTransformedTouchEvent()的返回值.如果第2个参数cancel不是null,还是会一样判断3个参数child是否为null,后面的流程都是和前面一样.到这里,ViewGroup 触摸屏事件派发流程就算是结束了.总结一下:1.触摸屏事件是先传递到最顶层的ViewGroup(也就是最parent)的dispatchTouchEvent()开始处理的,再传递到子View(child view)的dispatchTouchEvent().2.如果ViewGroup的onInterceptTouchEvent()拦截了事件也就是返回true,那么后续的move, up等事件将不再传递给onInterceptTouchEvent(),而是和down事件一样传递给该ViewGroup的onTouchEvent()处理.不会再传递给子View.如果ViewGroup的onInterceptTouchEvent()返回false,就是不拦截,后续的move,up和down事件一样传递给最终的目标View的onTouchEvent()来处理.3.如果子View将事件消耗掉了,ViewGroup自己就不能处理了,只能等后续的move/up事件了.4.某个view 一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一个事件序列中的其他事件就不会再交给他来处理,并将事件提交给父元素去处理(关键是mFirstTouchTarget).这里可以理解为如果ViewGroup的child 都没有消耗掉事件就是执行ViewGroup 的super()也就是View来执行.5.如果View不消耗除ACTION_Down以外的其他事件(up/move),那么这个点击事件就会消失,此时父元素的onTouchEvent不会执行,并且当前view可以继续接受后续事件,最终这些消失的点击事件会传递给activity处理.6.子View想要禁止父View的拦截事件只要调用getParent().requestDisallowInterceptTouchEvent(true)方法.一旦底层View收到touch的action后调用这个方法那么父层View就不会再调用onInterceptTouchEvent了,也无法截获以后的action.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: