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

Android FrameWork——Touch事件派发过程详解 (转)

2012-10-28 23:51 483 查看
http://www.fengfly.com/plus/view-195195-1.html

http://blog.csdn.net/pgalxx/article/details/6768666

http://blog.sina.com.cn/s/blog_793940aa0100vu75.html

Android FrameWork——Touch事件派发过程详解 (转)
 (2011-11-25 09:32:02)
转载▼
 
 
对于android的窗口window管理,一直感觉很混乱,总想找个时间好好研究,却不知如何入手,
现在写的Touch事件派发过程详解,其实跟android的窗口window管理服务WindowManagerService存在紧密联系,
所以从这里入手切入到WindowManagerService的研究,
本blog主要讲述一个touch事件如何从用户消息的采集,
到WindowManagerService对Touch事件的派发,
再到一个Activity窗口touch事件的派发,
并着重讲了Activity窗口touch事件的派发,因为这个的理解对我们写应用很好地处理touch事件很重要
一.用户事件采集到WindowManagerService和派发
--1.WindowManagerService,顾名思义,它是是一个窗口管理系统服务,它的主要功能包含如下:

        --窗口管理,绘制

        --转场动画--Activity切换动画

        --Z-ordered的维护,Activity窗口显示前后顺序

        --输入法管理

        --Token管理

        --系统消息收集线程

        --系统消息分发线程
这里,我关注的是系统消息的收集和系统消息的分发,其他功能,当我对WindowManagerService有一个完整的研究后在发blog
--2.系统消息收集和分发线程的创建
这个的从WindowManagerService服务的创建说起,与其他系统服务一样,WindowManagerService在systemServer中创建的:

ServerThread.run

-->WindowManagerService.main

   -->WindowManagerService.WMThread.run(构建一个专门线程负责WindowManagerService)

      -->WindowManagerService s = newWindowManagerService(mContext, mPM,mHaveInputMethods);

         --mQueue = new KeyQ();//消息队列,在构造KeyQ中会创建一个InputDeviceReader线程去读取用户输入消息
         --mInputThread = newInputDispatcherThread();//创建一个消息分发线程,读取并处理mQueue中消息
整个过程处理原理很简单,典型的生产者消费者模型,我先画个图,后面针对代码进一步说明
--3.InputDeviceReader线程,KeyQ构建时,会启动一个线程去读取用户消息,具体代码在KeyInputQueue.mThread,在构造函数中,mThread会start,接下来,接研究一下mThread.run:

    //用户输入事件消息读取线程

    Thread mThread = newThread("InputDeviceReader") {

        public void run() {

            RawInputEventev = new RawInputEvent();

            while(true) {//开始消息读取循环

                try{

                    InputDevicedi;

                    //本地方法实现,读取用户输入事件

                    readEvent(ev);
                    //根据ev事件进行相关处理

                    ...

                    synchronized(mFirst) {//mFirst是keyQ队列头指针

                    ...

                    addLocked(di, curTimeNano,ev.flags,RawInputEvent.CLASS_TOUCHSCREEN, me);
                    ...

                    }

                }

        }

       }
函数我也没有看大明白:首先调用本地方法readEvent(ev);去读取用户消息,这个消息包括按键,触摸,滚轮等所有用户输入事件,后面不同的事件类型会有不同的处理,不过最后事件都要添加到keyQ的队列中,通过addLocked函数
--4队列添加和读取函数addLocked,getEvent

addLocked函数比较简单,就分析一下,有助于对消息队列KeyQ的数据结构进行理解:

    //event加入inputQueue队列

    private void addLocked(InputDevice device, longwhenNano, int flags,

            intclassType, Object event) {

        boolean poke = mFirst.next ==mLast;//poke为true表示消息队列为空
        //从QueuedEvent缓存QueuedEvent获取一个QueuedEvent对象,并填入用户事件数据,包装成一个QueuedEvent
        QueuedEventev = obtainLocked(device, whenNano, flags, classType, event);

        QueuedEvent p = mLast.prev;//队列尾节点为mLast,把ev添加到mlast前
        while(p != mFirst && ev.whenNano < p.whenNano) {

            p =p.prev;

        }

        ev.next = p.next;

        ev.prev = p;

        p.next = ev;

        ev.next.prev = ev;

        ev.inQueue = true;
        if (poke) {//poke为true,意味着在空队列中添加了一个QueuedEvent,这时系统消息分发线程可能在wait,需要notify一下
            long time;

            if (MEASURE_LATENCY) {

                time = System.nanoTime();

            }

            mFirst.notify();//唤醒在mFirst上等待的线程
            mWakeLock.acquire();

            if (MEASURE_LATENCY) {

                lt.sample("1addLocked-queued event ", System.nanoTime() - time);

            }

        }

    }

很简单,使用mFirst,mLast实现的指针队列,addLocked是QueuedEvent对象添加函数,对应在系统消息分发线程中会有一个getEvent函数来读取inputQueue队列的消息,我在这里也先讲一下:

    QueuedEventgetEvent(long timeoutMS) {

        long begin =SystemClock.uptimeMillis();

        final long end =begin+timeoutMS;

        long now = begin;

        synchronized (mFirst) {//获取mFirst上同步锁
            while (mFirst.next == mLast&& end > now) {

                try {//mFirst.next == mLast意味队列为空,同步等待mFirst锁对象
                    mWakeLock.release();

                    mFirst.wait(end-now);

                }

                catch (InterruptedExceptione) {

                }

                now =SystemClock.uptimeMillis();

                if (begin > now) {

                    begin = now;

                }

            }

            if(mFirst.next == mLast) {
                return null;
            }
            QueuedEvent p= mFirst.next;//返回mFirst的下一个节点为处理的QueuedEvent
            mFirst.next =p.next;
            mFirst.next.prev= mFirst;
            p.inQueue =false;
            return p;
        }
    }

通过上面两个函数得知,消息队列是通过mFirst,mLast实现的生产者消费模型的同步链表队列
--5.InputDispatcherThread线程

InputDispatcherThread处理InputDeviceReader线程存放在KeyInputQueue队列中的消息,分发到具体的一个客户端的IWindow

InputDispatcherThread.run

-->windowManagerService.process{                

            ...

            while (true){                

                // 从mQueue(KeyQ)获取一个用户输入事件,正上调用我上面提到的getEvent方法,若队列为空,线程阻塞挂起
                QueuedEvent ev = mQueue.getEvent(

                    (int)((!configChanged &&curTime < nextKeyTime)

                            ? (nextKeyTime-curTime) : 0));

                ...

                try {

                    if (ev != null) {

                        ...

                        if (ev.classType ==RawInputEvent.CLASS_TOUCHSCREEN) {//touch事件
                            eventType =eventType((MotionEvent)ev.event);

                        } else if (ev.classType ==RawInputEvent.CLASS_KEYBOARD ||

                                    ev.classType ==RawInputEvent.CLASS_TRACKBALL) {//键盘输入事件

                            eventType =LocalPowerManager.BUTTON_EVENT;

                        } else {

                            eventType =LocalPowerManager.OTHER_EVENT;//其他事件

                        }

                        ...

                        switch (ev.classType) {

                            case RawInputEvent.CLASS_KEYBOARD:

                                ...

                                dispatchKey((KeyEvent)ev.event, 0,0);//键盘输入,派发key事件

                                mQueue.recycleEvent(ev);

                                break;

                            case RawInputEvent.CLASS_TOUCHSCREEN:

                                dispatchPointer(ev, (MotionEvent)ev.event,0, 0);//touch事件,派发touch事件
                                break;

                            case RawInputEvent.CLASS_TRACKBALL:

                                dispatchTrackball(ev,(MotionEvent)ev.event, 0, 0);//滚轮事件,派发Trackball事件

                                break;

                            caseRawInputEvent.CLASS_CONFIGURATION_CHANGED:

                                configChanged = true;

                                break;

                            default:

                                mQueue.recycleEvent(ev);//销毁事件

                            break;

                        }
                    } 

                } catch (Exception e) {

                    Slog.e(TAG,

                        "Input thread received uncaughtexception: " + e, e);

                }

            }        

   }
WindowManagerService.dispatchPointer,一旦判断QueuedEvent为屏幕点击事件,就调用函数WindowManagerService.dispatchPointer进行处理:

WindowManagerService.dispatchPointer

-->WindowManagerService.KeyWaiter.waitForNextEventTarget(获取touch事件要派发的目标windowSate)

   -->WindowManagerService.KeyWaiter.findTargetWindow(从一个一个WindowSate的z-order顺序列表mWindow中获取一个能够接收当前touch事件的WindowSate)

-->WindowSate target = waitForNextEventTarget返回的WindowSate对象
-->target.mClient.dispatchPointer(ev,eventTime, true);(往目标window派发touch消息)

target.mClient是一个IWindow代理对象IWindow.Proxy,它对应的代理类是ViewRoot.W,通过远程代理调用,WindowManagerService把touch消息派发到了对应的Activity的PhoneWindow
之后进一步WindowManagerService到Activity消息的派发在下文中说明
二WindowManagerService派发Touch事件到当前topActivity
--1.先我们看一个system_process的touch事件消息调用堆栈,在WindowManagerService中的函数dispatchPointer,通过一个IWindow的客户端代理对象把消息发送到相应的IWindow服务端,也就是一个IWindow.Stub子类。

Thread [<21> InputDispatcher] (Suspended (breakpoint at line 321 inIWindow$Stub$Proxy))       

        IWindow$Stub$Proxy.dispatchPointer(MotionEvent,long, boolean) line: 321       

        WindowManagerService.dispatchPointer(KeyInputQueue$QueuedEvent,MotionEvent, int, int) line:5270              

        WindowManagerService$InputDispatcherThread.process()line: 6602        

        WindowManagerService$InputDispatcherThread.run()line: 6482  
--2.通过IWindow.Stub.Proxy代理对象把消息传递给IWindow.Stub对象。code=TRANSACTION_dispatchPointer,IWindow.Stub对象被ViewRoot拥有(成员mWindow,它是一个ViewRoot.W类对象)
--3.在case TRANSACTION_dispatchPointer会调用IWindow.Stub子类的实现方法dispatchPointer
--4.IWindow.Stub.dispatchPointer

        -->ViewRoot.W.dispatchPointer

                -->ViewRoot.dispatchPointer
    public voiddispatchPointer(MotionEvent event, long eventTime,

            boolean callWhenDone) {

        Messagemsg = obtainMessage(DISPATCH_POINTER);

        msg.obj= event;

        msg.arg1= callWhenDone ? 1 : 0;

        sendMessageAtTime(msg,eventTime);

    }
--5.ViewRoot继承自handle,在handleMessage函数的case-DISPATCH_POINTER会调用mView.dispatchTouchEvent(event),

mView是一个PhoneWindow.DecorView对象,在PhoneWindow.openPanel方法会创建一个ViewRoot对象,并设置ViewRoot对象的mView为一个PhoneWindow.decorView成员,PhoneWindow.DecorView是真正的root
view,它继承自FrameLayout,这样调用mView.dispatchTouchEvent(event)
其实就是调用PhoneWindow.decorView的dispatchTouchEvent方法:

        @Override

        publicboolean dispatchTouchEvent(MotionEvent ev) {

            final Callback cb = getCallback();

            return cb != null && mFeatureId < 0 ?cb.dispatchTouchEvent(ev) : super

                    .dispatchTouchEvent(ev);
        } 
--6.分析上面一段红色代码,可以写成return (cb != null)&& (mFeatureId < 0 ? cb.dispatchTouchEvent(ev) :super.dispatchTouchEvent(ev)).当cb不为null执行后面,如果mFeatureId<0,执行cb.dispatchTouchEvent(ev),否则执行super.dispatchTouchEvent(ev),也就是FrameLayout.dispatchTouchEvent(ev),那么callback
cb是什么呢?是Window类的一个成员mCallback,我下面给一个图你可以看到何时被赋值的:

setCallback(Callback) : void - android.view.Window

        -->attach(Context,ActivityThread, Instrumentation, IBinder, int, Application, Intent,ActivityInfo, CharSequence, Activity, String, Object, HashMap<String,Object>, Configuration) : void - android.app.Activity

               --> performLaunchActivity(ActivityRecord,Intent) : Activity - android.app.ActivityThread
performLaunchActivity我们很熟识,因为我前面在讲Activity启动过程详解时候讲过,在启动一个新的Activity会执行该方法,在该方法里面会执行attach方法,找到attach方法对应代码可以看到:

        mWindow= PolicyManager.makeNewWindow(this);

        mWindow.setCallback(this);
mWindow就是一个PhoneWindow,它是Activity的一个内部成员,通过调用mWindow的setCallback(this),把新建立的Activity设置为PhoneWindow一个mCallback成员,这样我们就清楚了,前面的cb就是拥有这个PhoneWindow的Activity,cb.dispatchTouchEvent(ev)也就是执行:Activity.dispatchTouchEvent
    public booleandispatchTouchEvent(MotionEvent ev) {

        if(ev.getAction() == MotionEvent.ACTION_DOWN) {

            onUserInteraction();

        }

        //getWindow()返回的就是PhoneWindow对象,执行superDispatchTouchEvent,就是执行PhoneWindow.superDispatchTouchEvent
        if(getWindow().superDispatchTouchEvent(ev)) {

            return true;

        }

        //执行Activity.onTouchEvent方法

        returnonTouchEvent(ev);

    }
--7.再看PhoneWindow.superDispatchTouchEvent:

    @Override

    public booleansuperDispatchTouchEvent(MotionEvent event) {

        return mDecor.superDispatchTouchEvent(event);
                -->        public booleansuperDispatchTouchEvent(MotionEvent event) {

                                    return super.dispatchTouchEvent(event);//FrameLayout.dispatchTouchEvent
        }

    }

superDispatchTouchEvent调用super.dispatchTouchEvent,我前面讲过mDector是一个PhoneWindow.DecorView,它是一个真正Activity的root
view,它继承了FrameLayout,通过super.dispatchTouchEvent他会把touchevent派发给各个activity的子view,也就是我们再Activity.onCreat方法中setContentView时设置的view,touch
event时间如何在Activity各个view中进行派发的我后面再作详细说明,但是从上面我们可以看出一点若Activity下面的子view拦截了touchevent事件(返回true),Activity.onTouchEvent就不会执行。
--8.这部分,我再画一个静态类结构图把前面讲到的一些类串起来看一下:
我用红色箭头线把整个消息派发过程过程给串起来,然后system_process进程和ap进程分别用虚线椭圆圈起,这样以后相信你更理解各个类之间关系。
对应的对象空间图如下,与上面图是对应的,只是从不同角度去看:
--9.其实上面所讲的大部分已经是在客户端ap中执行了,也就是在ap进程中,只是执行逻辑基本是框架代码中,还没有到达我们使用layout.xml布局的view中来,
这里我先在我们的一个view中onTouchEvent插入一个断点看一看消息从WindowManagerService到达Activity.PhoneWindow后执行堆栈情况(我插入的断点在Launcher2的HandleView中),
后面继续讲解:

Thread [<1> main] (Suspended (breakpoint at line 4280 inView))        

        HandleView(View).onTouchEvent(MotionEvent)line: 4280        

        HandleView.onTouchEvent(MotionEvent)line: 71        

        HandleView(View).dispatchTouchEvent(MotionEvent)line: 3766        

        RelativeLayout(ViewGroup).dispatchTouchEvent(MotionEvent)line: 863       

        DragLayer(ViewGroup).dispatchTouchEvent(MotionEvent)line: 863        

        FrameLayout(ViewGroup).dispatchTouchEvent(MotionEvent)line: 863        

        PhoneWindow$DecorView(ViewGroup).dispatchTouchEvent(MotionEvent)line: 863       

        PhoneWindow$DecorView.superDispatchTouchEvent(MotionEvent)line: 1671       

        PhoneWindow.superDispatchTouchEvent(MotionEvent)line: 1107        

        ForyouLauncher(Activity).dispatchTouchEvent(MotionEvent)line: 2086       

        PhoneWindow$DecorView.dispatchTouchEvent(MotionEvent)line: 1655        

        ViewRoot.handleMessage(Message)line: 1785        

        ViewRoot(Handler).dispatchMessage(Message)line: 99        

        Looper.loop()line: 123        

        ActivityThread.main(String[])line: 4634
三.Activity中View中的Touch事件派发
--1.首先我画一个Activity中的view层次结构图:
前面我讲过,来自windowManagerService的touch消息最终会派发到到Decorview,Decorview继承子FrameLayout,它只有一个子view就是mContentParent,我们写ap的view全部添加到到mContentParent。
--2.了解了Activity中的view的层次结构,那先从DecorView开始看touch事件是如何被派发的,前面讲过最终消息会派发到FrameLayout.dispatchTouchEvent也就是ViewGroup.dispatchTouchEvent(FrameLayout也没有覆盖该方法),
同样mContentParent也是执行ViewGroup.dispatchTouchEvent来派发touch消息,那我们就详细看一下ViewGroup.dispatchTouchEvent(若要很好掌握应用程序touch事件处理,这部分要重点看):

    public booleandispatchTouchEvent(MotionEvent ev) {

        ......

        booleandisallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;//计算是否禁止touch Intercept

        if(action == MotionEvent.ACTION_DOWN) {//按下事件,也就是touch开始
            if (mMotionTarget != null) {

                mMotionTarget = null;//清除mMotionTarget,也就是说每次touch开始,mMotionTarget要被重新设置
            }

            if (disallowIntercept ||!onInterceptTouchEvent(ev)) {//判断消息是否需要被viewGroup拦截

                // 消息不被viewGroup拦截,找到相应的子view进行touch事件派发
                ev.setAction(MotionEvent.ACTION_DOWN);//重新设置event
为action_down

               

                final int scrolledXInt = (int)scrolledXFloat;

                final int scrolledYInt = (int)scrolledYFloat;

                final View[] children = mChildren;//获取viewgroup所有的子view

                final int count = mChildrenCount;

                for (int i = count - 1; i >= 0;i--) {

                    final View child = children[i];

                    if ((child.mViewFlags &VISIBILITY_MASK) == VISIBLE

                            || child.getAnimation() != null) {//若子view可见或者有动画在执行的,才能够接收touch事件

                        child.getHitRect(frame);//获取子view的布局坐标区域

                        if (frame.contains(scrolledXInt,scrolledYInt)) {//若子view
区域包含当前touch点击区域
                            // offset the event to the view'scoordinate system

                            final float xc = scrolledXFloat -child.mLeft;

                            final float yc = scrolledYFloat -child.mTop;

                            ev.setLocation(xc, yc);

                            child.mPrivateFlags &=~CANCEL_NEXT_UP_EVENT;

                            if(child.dispatchTouchEvent(ev))  {//派发TouchEvent给包含这个touch区域的子view
                                //
若该子view消费了对应的touch事件
                                mMotionTarget = child;//设置viewgroup消息派发的目标子view
                                return true;//返回true,该touch事件被消费掉

                            }

                        }

                    }

                }

            }

          //若touch事件被拦截,mMotionTarget = null,后面touch消息不再派发给子view
        }
        boolean isUpOrCancel = (action ==MotionEvent.ACTION_UP) ||//计算是up或者cancel

                (action == MotionEvent.ACTION_CANCEL);
        if (isUpOrCancel) {

            mGroupFlags &=~FLAG_DISALLOW_INTERCEPT;

        }
       

        finalView target = mMotionTarget;

        if(target == null) {

            //target为null,意味着在ACTION_DOWN时没有找到能消费touch消息的子view或者在ACTION_DOWN时消息被拦截了,这个时候

            //调用父类view的dispatchTouchEvent消息进行派发,也就是说,此时viewgroup处理touch消息跟普通view一致。
            ev.setLocation(xf, yf);

            if ((mPrivateFlags &CANCEL_NEXT_UP_EVENT) != 0) {

                ev.setAction(MotionEvent.ACTION_CANCEL);

                mPrivateFlags &=~CANCEL_NEXT_UP_EVENT;

            }

            return super.dispatchTouchEvent(ev);

        }
        //target!=null,意味在ACTION_DOWN时touch消息没有被拦截,而且子view
target消费了ACTION_DOWN消息,需要再判断消息是否被拦截
        if (!disallowIntercept &&onInterceptTouchEvent(ev)) {

            //消息被拦截,而前面ACTION_DOWN时touch消息没有被拦截,所以需要发送ACTION_CANCEL通知子view
target
            final float xc = scrolledXFloat -(float) target.mLeft;

            final float yc = scrolledYFloat -(float) target.mTop;

            mPrivateFlags &=~CANCEL_NEXT_UP_EVENT;

            ev.setAction(MotionEvent.ACTION_CANCEL);

            ev.setLocation(xc, yc);

            if (!target.dispatchTouchEvent(ev)) {

                // 派发消息ACTION_CANCEL给子view target
            }

            // mMotionTarget=null,后面消息不再派发给子view
            mMotionTarget = null;

            return true;

        }
        if (isUpOrCancel) {

            //isUpOrCancel,设置mMotionTarget=null,后面消息不再派发给子view
            mMotionTarget = null;

        }
        ......

        //没有被拦截继续派发消息给子view target
        return target.dispatchTouchEvent(ev);

    }
--3.ViewGroup.dispatchTouchEvent我查看了一下所有子类,只有PhoneWindow.DecorView覆盖了该方法,该方法前面讲DecorView消息派发时提过,它会找到对应包含这个PhoneWindow.DecorView对象的Activity把消息交给Activity去处理,其它所有viewGroup的子类均没有覆盖dispatchTouchEvent,也就是说所有包含子view的父view对于touch消息派发均采用上面的逻辑,当然,必要的时候我们可以覆盖该方法实现自己的touch消息派发逻辑,如Launcher2中的workspace类就是重新实现的该dispatchTouchEvent方法,从上面的dispatchTouchEvent函数逻辑其实我们也可以总结几条touch消息派发逻辑:

(1).onInterceptTouchEvent用来定义是否截取touch消息逻辑,若在groupview中想截取touch消息,必须覆盖viewgroup中该方法。

(2).消息在整个dispatchTouchEvent过程中,若子view.dispatchTouchEvent返回true,父view中将不再处理该消息,但前提是该消息没有被父view截取,在整个touch消息处理过程中,若处理函数返回true,我们称之为消费了该touch事件,并且后面的父view将不再处理该消息。
(3).在整个touch事件过程中,从action_down到action_up,若父ViewGroup的函数onInterceptTouchEvent一旦返回true,消息将不再派发给子view,细分可为两种情况,若是在action_down时onInterceptTouchEvent返回true,不会派发任何消息给子view,并且后面onInterceptTouchEvent函数将不再会被执行,若是action_down时onInterceptTouchEvent返回false
,而后面touch过程中onInterceptTouchEvent==true,父viewGroup会把action_cancel派发给子view,也之后不再派发消息给子view,并且onInterceptTouchEvent函数后面将不再被执行。
--4.为了更清楚的理解viewGroup消息的派发流程,我画一个流程图如下:
--5.上面我只是讲了父view与子view之间当有touch事件的消息派发流程,对于view的消息是怎么派发的(也包裹viewGroup没有子view或者有子view但是不消费该touch消息情况),因为从继承结构上看viewgroup继承了view,viewgroup覆盖了view的dispatchTouchEvent方法,不过从上面流程图也可以看到当mMotionTarget为Null它会执行父类view.dispatchTouchEvent,其他view的子类都是执行view.dispatchTouchEvent派发touch事件,不过若我们自定义view是可以覆盖该方法的。下面就仔细研究一下view.dispatchTouchEvent方法的代码:

    public finalboolean dispatchTouchEvent(MotionEvent event) {

        //mOnTouchListener是被View.setOnTouchListener设置的,(mViewFlags &ENABLED_MASK)计算view是否可被点击
        //当view可被点击并且mOnTouchListener被设置,执行mOnTouchListener.onTouch
        if (mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED &&

                mOnTouchListener.onTouch(this, event)){

            return true;//若mOnTouchListener.onTouch返回true,函数返回true

        }

        returnonTouchEvent(event);//若mOnTouchListener.onTouch返回false,调用onToucheEvent
    }
函数逻辑很简单,前面的viewGroup touch事件流程图中我已经画出的,为区别我把它着色成青绿色,总结一句话若mOnTouchListener处理了touch消息,不执行onTouchEvent,否则交给onTouchEvent进行处理。
不知道是否讲清楚的,要清楚掌握估计还得写些例子测试一下是否是我上面所说的流程,不过我想了解事件的派发流程,对写应用的事件处理相信很有用,比如我以前碰到一个问题是手指点击屏幕到底是子view执行onclick还是执行父view的view移动,这个时候就需要深入了解viewde
touch事件派发流程,该响应点击的时候响应子view的点击,该父view移动的时候拦截touch事件交给父view进行处理。
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: