framework学习笔记16. Input 输入事件(2)

2021-01-26  本文已影响0人  加个标志位

原计划 input 输入事件的学习分为两节内容学习并记录,经学习发现并远不止这些内容,所以决定重新写 input 输入事件番外篇,如需参考,请阅读 input 输入事件番外篇;造成的不便,深表抱歉。

1. Window 的创建和 WMS的绑定
上一节中讲到 dispatchMotionLocked() 向目标窗口分发事件,这里简单介绍一下目标窗口是如何获取和绑定的;在 Activity 的启动流程中(具体可以参考 笔记14):
(1)handleLaunchActivity() 调用了 performLaunchActivity() ->
(2)performLaunchActivity() 中调用了 activity.attach()方法,之前就说过这个是对 activity进行绑定,完成这一步activity才成为四大组件之一,未完成时都只能算一个对象(创建activity的第一步) ->
(3)handleLaunchActivity() 调用了 handleResumeActivity() 方法(第二步:onResume()并渲染);
窗口的获取和绑定就在 performLaunchActivity() 调用的 activity.attach() 中完成的,进入源码中看看:

    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, IVoiceInteractor voiceInteractor) {
        attachBaseContext(context);

        mFragments.attachActivity(this, mContainer, null);
        // 创建 Window
        // 这里的 mWindow 时 PhoneWindow,后续版本的 mWindow 初始化如下,更加直观:
        // mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow = PolicyManager.makeNewWindow(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        // ...
        // 在window中创建时 mWindowManager 其实是 WindowManagerImpl,代码不复杂,可以跟进去看看
        mWindow.setWindowManager(  // 设置 WindowManager
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();  // 这里就可以获取 WindowManager 了
        mCurrentConfig = config;
    }

在这里记住三个知识点(后续讲setContentView时再详细分析):
a. Window 类是一个抽象类,它的唯一实现类是 PhoneWindow;
b. PhoneWindow 有一个内部类 DecorView,DecorView 是 Activity 的根 View;
c. DecorView 继承自 FramLayout;

关于创建Window对象:
PolicyManager为策略类,其实现类Policy 的makeNewWindow内部创建了window对象;

// mWindow = PolicyManager.makeNewWindow(this):
public final class PolicyManager {
    private static final String POLICY_IMPL_CLASS_NAME =
        "com.android.internal.policy.impl.Policy";

    private static final IPolicy sPolicy;

    static {  // 通过反射创建 sPolicy
        try {
            Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME); 
            sPolicy = (IPolicy)policyClass.newInstance();
        } catch (ClassNotFoundException ex) {
            // ...
        }
    }

    private PolicyManager() {}

    // 创建PhoneWindow
    public static Window makeNewWindow(Context context) {
        return sPolicy.makeNewWindow(context);
    }

    public static LayoutInflater makeNewLayoutInflater(Context context) {
        return sPolicy.makeNewLayoutInflater(context);
    }

    public static WindowManagerPolicy makeNewWindowManager() {
        return sPolicy.makeNewWindowManager();
    }

    public static FallbackEventHandler makeNewFallbackEventHandler(Context context) {
        return sPolicy.makeNewFallbackEventHandler(context);
    }
}

// Policy.java 类中的 makeNewWindow() 方法:
public window makeNewWindow(Context context){
        return new PhoneWindow(context);
}

2. ViewRootImpl 与 WMS 的通信

    // 第二步:
    final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume) {
        
        // 主要作用是调用performResumeActivity()到activity的onResume状态,然后获取
        // DecorView,创建一个关联的ViewRootImpl对象,用来配合WindowManagerService
        // 服务来管理该Activity组件的窗口状态,最后addView
        ActivityClientRecord r = performResumeActivity(token, clearHide);

        if (r != null) {
            final Activity a = r.activity;
            // ...

            //activity创建成功,window此时为空,进入此分支;
            if (r.window == null && !a.mFinished && willBeVisible) { 
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    // 一层层的看最终调用的是:WindowManagerGlobal.java -> addView()
                    wm.addView(decor, l);  // 这里就是测量,摆放,绘制
                }

            } else if (!willBeVisible) {
                r.hideForNow = true;
            }
            // Get rid of anything left hanging around.
            cleanUpPendingRemoveWindows(r);

            // ...
        } 
    }

2.1 wm.addView(decor, l):这里的 wm 是 WindowManagerImpl,查看一下 addView() 方法:

// WindowManagerImpl.addView():
    // 单例获取 WindowManagerGlobal
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    @Override
    public void addView(View view, ViewGroup.LayoutParams params) {
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }


//WindowManagerGlobal.addView():
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {

        // ... 省略部分代码:参数的校验
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
        // ...
        ViewRootImpl root;
        View panelParentView = null;

            //...
     
            // 实例化ViewRootImpl
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);
            // 添加 view 到全局集合中
            // 如果想 hook 全部的 view 时,可以通过反射获取 WindowManagerGlobal -> mViews;
            mViews.add(view);  
            mRoots.add(root);
            mParams.add(wparams);
        }
       
        // do this last because it fires off messages to start doing things
        try {  //将view添加到ViewRootImpl中去
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
           // ...
        }
    }

ViewRootImpl.setView():

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
        // 这里先将 mView 保存了 DecorView 的实例,
        // 然后调用 requestLayout() 方法,以完成应用程序用户界面的初次布局。
        if (mView == null) {
            mView = view;
            // mWindowAttributes保存了窗口所对应的LayoutParams
           mWindowAttributes.copyFrom(attrs);
            /**
            * 在添加窗口之前,先通过requestLayout方法在主线程上安排一次“遍历”。
            * 所谓“遍历”是指ViewRootImpl中的核心方法performTraversal()。
            * 这个方法实现对控件树进行测量、布局、向WMS申请修改窗口属性以及重绘的所有工作。
            */
           requestLayout();  
           /***初始化mInputChannel。InputChannel是窗口接受来自InputDispatcher 的输入事件的管道。 
             注意,仅当窗口的属性inputFeatures不含有 INPUT_FEATURE_NO_INPUT_CHANNEL时才
             会创建 InputChannel,否则mInputChannel 为空,从而导致此窗口无法接受任何输入事件 */
           if ((mWindowAttributes.inputFeatures
                   & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
               mInputChannel = new InputChannel();  // 见 2.2
           }
           try {
             // ...
             /* 将窗口添加到WMS中。完成这个操作之后,mWindow已经被添加到指定的Display中去
               而且mInputChannel(如果不为空)已经准备好接受事件了。只是由于这个窗口没有进行
               过relayout(),因此它还没有有效的Surface可以进行绘制 */
             res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                       getHostVisibility(), mDisplay.getDisplayId(),
                       mAttachInfo.mContentInsets, mInputChannel);  // 见2.3 
              // 这里的 mWindowSession 是向WMS跨进程请求获取的 
           } catch (RemoteException e) {  
              // ... 
           } finally { 
              // ... 
           }

        }
    }

2.2 InputChannel的构造函数:此时 mInputChannel = new InputChannel() 这里还是一个 Java 的对象;

public final class InputChannel implements Parcelable {
    private static final String TAG = "InputChannel";
    
    @SuppressWarnings("unused")
    private long mPtr; // used by native code
    private static native InputChannel[] nativeOpenInputChannelPair(String name);
    
    private native void nativeDispose(boolean finalized);
    private native void nativeTransferTo(InputChannel other);
    private native void nativeReadFromParcel(Parcel parcel);
    private native void nativeWriteToParcel(Parcel parcel);
    private native void nativeDup(InputChannel target);
    
    private native String nativeGetName();

    // 构造函数中没有任何操作,此时 mInputChannel = new InputChannel() 只是一个普通的java对象;
    // 那么要想具有 c++ 的属性,唯一的方法就是持有c++对象的指针,也就是将 mPtr 赋值;
    public InputChannel() {
    }
    // ...
  }

2.3 mWindowSession.addToDisplay():
首先,mWindowSession 是如何获取到的:

// mWindowSession 的初始化:ViewRootImpl 的构造函数中进行初始化的;
public ViewRootImpl(Context context, Display display) {
        mContext = context;
        mWindowSession = WindowManagerGlobal.getWindowSession();  // 通过 WMS 获取
}


// 通过 WMS 获取 mWindowSession:WindowManagerGlobal类中的 getWindowSession() 方法
public static IWindowSession getWindowSession() {
    synchronized (WindowManagerGlobal.class) {
        if (sWindowSession == null) {
            try {
                InputMethodManager imm = InputMethodManager.getInstance();
                IWindowManager windowManager = getWindowManagerService();
                sWindowSession = windowManager.openSession(
                        new IWindowSessionCallback.Stub() {
                            @Override
                            public void onAnimatorScaleChanged(float scale) {
                                ValueAnimator.setDurationScale(scale);
                            }
                        },
                        imm.getClient(), imm.getInputContext());
                ValueAnimator.setDurationScale(windowManager.getCurrentAnimatorScale());
            } catch (RemoteException e) {
                Log.e(TAG, "Failed to open window session", e);
            }
        }
        return sWindowSession;
    }
}


// WMS 中openSession():
//IWindowSession :一个aidl接口,它的真的实现类是Session,它是一个Binder对象,用来和
//WindowManagerService建立连接,在ViewRootImpl的setView中最终也是通过它和WindowManagerService
//通信完成了Window的添加的。这个Session是应用唯一的,它的创建时在WindowManagerGloable中通过getWindowSession获取的
@Override
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
        IInputContext inputContext) {
    if (client == null) throw new IllegalArgumentException("null client");
    if (inputContext == null) throw new IllegalArgumentException("null inputContext");
    Session session = new Session(this, callback, client, inputContext);
    return session;
}

这里创建了 Session 对象,其参数 this 就是 WMS;
mWindowSession.addToDisplay() 就是 调用了Session的 addToDisplay() 方法:

// Session 的 addToDisplay() 方法:又是跨进程通讯
    @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets,
            InputChannel outInputChannel) {
        // outInputChannel 是 2.2 中 mInputChannel = new InputChannel(),此时的指针还未赋值
        // mService 就是 new Session 时传入的 WMS
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outInputChannel); 
    }

WMS的addWindow() 方法:

public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, InputChannel outInputChannel) {
        int[] appOp = new int[1];
        int res = mPolicy.checkAddPermission(attrs, appOp);
        if (res != WindowManagerGlobal.ADD_OKAY) {
            return res;
        }

        boolean reportNewConfig = false;
        WindowState attachedWindow = null;
        WindowState win = null;  // window 对象的信息
        long origId;
        final int type = attrs.type;

        synchronized(mWindowMap) {
            // ...
            win = new WindowState(this, session, client, token, 
                    attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
            // ...
            if (outInputChannel != null && (attrs.inputFeatures
                    & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                String name = win.makeInputChannelName();
                // 关键代码,打开一对 InputChannel,客户端和服务端 见 2.3.1
                InputChannel[] inputChannels = InputChannel.openInputChannelPair(name); 
                // WMS 设置 Channel 为 inputChannels[0] 
                win.setInputChannel(inputChannels[0]); 
                // 客户端设置 Channel 为 inputChannels[1],此时给上面 Java层的 mInputChannel 中 mPtr 赋值
                // 经过赋值后,Java 层 mInputChannel 才具有 c++ 的对象
                inputChannels[1].transferTo(outInputChannel);  

                // 将服务端的socket注册到InputDispatcher中 见 2.3.2
                // 这里的 win.mInputWindowHandle 是在 win 初始化的时候 new 出来的;
                mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
            }

            // ...
            // 每次添加都会更新,见 2.3.3
            mInputMonitor.updateInputWindowsLw(false /*force*/);
            // ...
        }
        // ...
}

2.3.1 建立socket 通信:
InputChannel.openInputChannelPair(name) 是一个native方法,在 frameworks/base/core/jni/
android_view_InputChannel.cpp中:

static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env,
        jclass clazz, jstring nameObj) {
    const char* nameChars = env->GetStringUTFChars(nameObj, NULL);
    String8 name(nameChars);
    env->ReleaseStringUTFChars(nameObj, nameChars);

    sp<InputChannel> serverChannel;
    sp<InputChannel> clientChannel;
    // 创建一对 socket 通信
    status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);

    if (result) {
        String8 message;
        message.appendFormat("Could not open input channel pair.  status=%d", result);
        jniThrowRuntimeException(env, message.string());
        return NULL;
    }

    // 封装成java对象    
    jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL);
    if (env->ExceptionCheck()) {
        return NULL;
    }

    jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,
            new NativeInputChannel(serverChannel));
    if (env->ExceptionCheck()) {
        return NULL;
    }

    jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,
            new NativeInputChannel(clientChannel));
    if (env->ExceptionCheck()) {
        return NULL;
    }

    env->SetObjectArrayElement(channelPair, 0, serverChannelObj);
    env->SetObjectArrayElement(channelPair, 1, clientChannelObj);
    return channelPair;
}

InputChannel.cpp中:

status_t InputChannel::openInputChannelPair(const String8& name,
        sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
    int sockets[2];
    if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
        status_t result = -errno;
    // ...    
    String8 serverChannelName = name;
    serverChannelName.append(" (server)");
    outServerChannel = new InputChannel(serverChannelName, sockets[0]);

    String8 clientChannelName = name;
    clientChannelName.append(" (client)");
    outClientChannel = new InputChannel(clientChannelName, sockets[1]);
    return OK;
}

socketpair()函数用于创建一对无名的、相互连接的套接字。
如果函数成功,则返回0,创建好的套接字分别是sv[0]和sv[1];否则返回-1,错误码保存于errno中。

用法:
(1) 这对套接字可以用于全双工通信,每一个套接字既可以读也可以写。例如,可以往sv[0]中写,从sv[1]中读;或者从sv[1]中写,从sv[0]中读;
(2) 如果往一个套接字(如sv[0])中写入后,再从该套接字读时会阻塞,只能在另一个套接字中(sv[1])上读成功;
(3)读、写操作可以位于同一个进程,也可以分别位于不同的进程,如父子进程。如果是父子进程时,一般会功能分离,一个进程用来读,一个用来写。因为文件描述符sv[0]和sv[1]是进程共享的,所以读的进程要关闭写描述符, 反之,写的进程关闭读描述符。

2.3.2 注册:frameworks/base/services/core/jni/
com_android_server_input_InputManagerService.cpp 中:

// nativeRegisterInputChannel():
static void nativeRegisterInputChannel(JNIEnv* env, jclass clazz,
        jlong ptr, jobject inputChannelObj, jobject inputWindowHandleObj, jboolean monitor) {
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);

    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
            inputChannelObj);
    if (inputChannel == NULL) {
        throwInputChannelNotInitialized(env);
        return;
    }
    // window 的一些信息
    sp<InputWindowHandle> inputWindowHandle =
            android_server_InputWindowHandle_getHandle(env, inputWindowHandleObj);  

    status_t status = im->registerInputChannel(  // 注册的方法,如下 registerInputChannel()方法:
            env, inputChannel, inputWindowHandle, monitor);
    if (status) {
        String8 message;
        message.appendFormat("Failed to register input channel.  status=%d", status);
        jniThrowRuntimeException(env, message.string());
        return;
    }

    if (! monitor) {
        android_view_InputChannel_setDisposeCallback(env, inputChannelObj,
                handleInputChannelDisposed, im);
    }
}


// registerInputChannel()方法:
status_t NativeInputManager::registerInputChannel(JNIEnv* env,
        const sp<InputChannel>& inputChannel,
        const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
    // 终于看到我们 InputDispatcher 了
    return mInputManager->getDispatcher()->registerInputChannel(
            inputChannel, inputWindowHandle, monitor);
}

InputDispatcher.cpp 中的注册方法:registerInputChannel()

status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel,
        const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {

    { // acquire lock
        AutoMutex _l(mLock);

        // 连接的媒介,只是一个对象,没有跨进程等操作;
        sp<Connection> connection = new Connection(inputChannel, inputWindowHandle, monitor);

        int fd = inputChannel->getFd();
        // 传入 fd 和 connection,关联 fd 和 connection;
        mConnectionsByFd.add(fd, connection);

        if (monitor) {
            mMonitoringChannels.push(inputChannel);
        }

        mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
    } // release lock

    // Wake the looper because some connections have changed.
    mLooper->wake();
    return OK;
}

2.3.3 InputMonitor
简介:实现了 WindowManagerCallbacks接口,在 WindowManagerService 的构造函数中创建了InputMonitor 对象,并以 mInputMonitor 作为参数创建 InputManagerService 的对象,在 InputManagerService 构造函数中,将 mInputMonitor 作为参数调用了 JNI 函数 nativeInit() ,将回调接口传到JNI层,在需要的时候,JNI 再回调 mInputMonitor中 的函数,实现数据才传递。

    public interface WindowManagerCallbacks {
        public void notifyConfigurationChanged();

        // 输入设备的配置变更
        public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen);        
        public void notifyCameraLensCoverSwitchChanged(long whenNanos, boolean lensCovered);

        // 连接InputDispatcher 与应用程序的 socket 通道
        public void notifyInputChannelBroken(InputWindowHandle inputWindowHandle);
        // ANR
        public long notifyANR(InputApplicationHandle inputApplicationHandle,
                InputWindowHandle inputWindowHandle, String reason);

        // 以下三个回调,是WMS在消息处理中有优先权处理
        public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags);
        public int interceptMotionBeforeQueueingNonInteractive(long whenNanos, int policyFlags);
        public long interceptKeyBeforeDispatching(InputWindowHandle focus,
                KeyEvent event, int policyFlags);

        // 按键事件在整个事件处理过程中没有任何处理时,发送给 WMS 
        public KeyEvent dispatchUnhandledKey(InputWindowHandle focus,
                KeyEvent event, int policyFlags);
        public int getPointerLayer();
    }
// 简化代码:
final class InputMonitor implements InputManagerService.WindowManagerCallbacks {
    private final WindowManagerService mService;
    private WindowState mInputFocus;
    private boolean mUpdateInputWindowsNeeded = true;
    private InputWindowHandle[] mInputWindowHandles;


    private final Object mInputDevicesReadyMonitor = new Object();
    private boolean mInputDevicesReady;
    Rect mTmpRect = new Rect();
    public InputMonitor(WindowManagerService service) {
        mService = service;
    }

    public void updateInputWindowsLw(boolean force) {
        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
            WindowList windows = mService.mDisplayContents.valueAt(displayNdx).getWindowList();
            for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
                // ...
                // 加入到 mInputWindowHandles 数组中
                if (child.mWinAnimator != universeBackground) {
                    addInputWindowHandleLw(inputWindowHandle, child, flags, privateFlags, type,
                            isVisible, hasFocus, hasWallpaper);
                }
        }
            
        // 发送窗口到本地方法;
        mService.mInputManager.setInputWindows(mInputWindowHandles);
        // Clear the list in preparation for the next round.
        clearInputWindowHandlesLw();
    }
}


// InputServiceManager.java中:调用的是 native 方法;
  public void setInputWindows(InputWindowHandle[] windowHandles) {
      nativeSetInputWindows(mPtr, windowHandles);
  }

frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp 中 nativeSetInputWindows() 方法:

// com_android_server_input_InputManagerService.cpp 中:
static void nativeSetInputWindows(JNIEnv* env, jclass clazz,
        jlong ptr, jobjectArray windowHandleObjArray) {
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
    im->setInputWindows(env, windowHandleObjArray);
}


// com_android_server_input_InputManagerService.cpp 中:
void NativeInputManager::setInputWindows(JNIEnv* env, jobjectArray windowHandleObjArray) {
    Vector<sp<InputWindowHandle> > windowHandles;
    // ...   
    // 调用InputDispatcher 中 setInputWindows()
    mInputManager->getDispatcher()->setInputWindows(windowHandles);
    // ...
}

// InputDispatcher.cpp 中的 setInputWindows():
void InputDispatcher::setInputWindows(const Vector<sp<InputWindowHandle> >& inputWindowHandles) {
    { // acquire lock
        Vector<sp<InputWindowHandle> > oldWindowHandles = mWindowHandles;
        // 把 inputWindowHandles 赋值给 mWindowHandles,这里就知道分发的目标窗口是哪个窗口了;
        // input 事件就是根据匹配 InputWindowHandle 来进行分发的;
        mWindowHandles = inputWindowHandles;
    }    

    // ... 一些窗口的更新等操作
    // Wake up poll loop since it may need to make new input dispatching choices.
    mLooper->wake();
}

由于篇幅过长,input 输入事件篇未完成,请参考更为详细的 input 输入事件番外篇;造成的不便,深表抱歉;

上一篇 下一篇

猜你喜欢

热点阅读