• 【android 9】【input】【9.发送按键事件3——Inputchannel的创建过程】


    系列文章

    本人系列文章-CSDN博客


    目录

    系列文章

    目录

    1.简介

    1.1 主要步骤

    1.2 时序图

    2.源码分析

    2.1 WindowManagerImpl的addView

    2.2 WindowManagerGlobal的addView

    2.3 ViewRootImpl

    2.4 getWindowSession

     2.5 WMS中的openSession

     2.6 Session

    2.7 class W

    2.8 setView

    2.9 addToDisplay

    2.10 addWindow

    2.11 openInputChannel

    2.12 Java层openInputChannelPair

    2.13 android_view_InputChannel_nativeOpenInputChannelPair

    2.14 openInputChannelPair

    2.15 transferTo

    2.16 WMS向IMS注册并监听socket

    2.17 nativeRegisterInputChannel

    2.18 android_view_InputChannel_getInputChannel

    2.19 android_server_InputWindowHandle_getHandle

    2.20 registerInputChannel

    2.21 InputDispatcher::registerInputChannel

    2.22 Connection

    2.23 android_view_InputChannel_setDisposeCallback

    2.24 WindowInputEventReceiver

    2.25 InputEventReceiver

    2.26 nativeInit

    2.27 NativeInputEventReceiver

    2.28 initialize

    2.29 setFdEvents

    2.30 Looper::addFd


    1.简介

    上一篇中,主要介绍了按键事件中inputdispatcher线程的分发流程,最后会通过sokcet对发送按键消息到应用端,那么这个socket对是什么时候创建的呢?是什么时候和IMS建立连接的呢?本文便主要解答一下这部分内容。

    1.1 主要步骤

    1.首先当Activity启动后,应用程序端会创建一个空的InputChannel对象。

    2.然后应用程序端会通过binder调用到WMS服务,WMS服务会通过openInputChannel 方法会创建一对 InputChannel,一个给到IMS,一个会通过binder调用返回给应用端。

    3.然后WMS会将其中一个socket注册到IMS服务中,IMS服务通过epoll机制来监听,是否有来自应用端发送的消息,当应用程序端通过sokcet发送消息时,IMS中的handleReceiveCallback回调函数会执行。

    4.然后此时应用端到WMS的binder调用函数返回,返回给应用程序端一个socket,应用程序端会创建一个NativeInputEventReceiver对象,同时应用程序端也会通过epoll机制来监听,是否有来自IMS发送的消息,当存在IMS发送事件到应用程序端时,会调用NativeInputEventReceiver的handleEvent函数。

    1.2 时序图

     (图片可保存到本地放大观看)

    2.源码分析

    首先当Activity启动后,最终会调用到WindowManagerImpl.addView()函数,我们便从WindowManagerImpl.addView()函数进行分析。

    2.1 WindowManagerImpl的addView

    主要作用:

    1.调用WindowManagerGlobal对象的addview对象。

    1. public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params)
    2. {
    3. applyDefaultToken(params);
    4. mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);//mGlobal就是WindowManagerGlobal对象
    5. }

    2.2 WindowManagerGlobal的addView

    主要作用:

    1.创建ViewRootImpl对象。

    2.调用ViewRootImpl的setView函数,此函数会创建空的InputChannel对象,然后传给WMS,WMS会返回一个和IMS连接好的socket给应用程序端。

    此时我们仍然在应用程序进程中。

    1. public void addView(View view, ViewGroup.LayoutParams params,
    2. Display display, Window parentWindow) {
    3. //view代表添加哪个窗口,此时view是DecorView
    4. //params窗口的参数
    5. //display显示到那块屏幕上
    6. //parentWindow父窗口是谁
    7. final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    8. ViewRootImpl root;
    9. View panelParentView = null;
    10. synchronized (mLock) {
    11. int index = findViewLocked(view, false);//从mViews中查找此view是否已经存在
    12. /*
    13. if (index >= 0) {
    14. if (mDyingViews.contains(view)) {
    15. // Don't wait for MSG_DIE to make it's way through root's queue.
    16. mRoots.get(index).doDie();
    17. } else {
    18. throw new IllegalStateException("View " + view
    19. + " has already been added to the window manager.");
    20. }
    21. // The previous removeView() had not completed executing. Now it has.
    22. }*/
    23. root = new ViewRootImpl(view.getContext(), display);
    24. view.setLayoutParams(wparams);
    25. mViews.add(view);//将此DecorView保存到mViews容器中
    26. mRoots.add(root); //将此ViewRootImpl保存到容器中
    27. mParams.add(wparams);//保存参数
    28. // do this last because it fires off messages to start doing things
    29. try {
    30. root.setView(view, wparams, panelParentView);
    31. } catch (RuntimeException e) {
    32. // BadTokenException or InvalidDisplayException, clean up.
    33. if (index >= 0) {
    34. removeViewLocked(index, true);
    35. }
    36. throw e;
    37. }
    38. }
    39. }

    2.3 ViewRootImpl

    主要作用:

    1.获取IWindowSession代理类,此类用于应用端和wms进行通信。

    2.new W(this);W 继承自 IWindow.Stub,用于wms服务端向应用端通信。在调用本类的setView时会将此W对象传递给WMS。

    1. //ViewRootImpl.Java
    2. public ViewRootImpl(Context context, Display display) {
    3. mContext = context;
    4. mWindowSession = WindowManagerGlobal.getWindowSession();//获取IWindowSession代理类,此类用于应用和wms进行通信
    5. mDisplay = display;//显示到那个display中
    6. mDirty = new Rect();
    7. mTempRect = new Rect();
    8. mVisRect = new Rect();
    9. mWinFrame = new Rect();
    10. mWindow = new W(this);//w继承自class W extends IWindow.Stub,用于wms服务端向应用端通信
    11. mFirst = true; // true代表此view第一次被添加
    12. mChoreographer = Choreographer.getInstance();
    13. mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
    14. }

    2.4 getWindowSession

    主要作用:

    1.获取和WMS通信用的Session对象。

    1. //WindowManagerGlobal.java
    2. public static IWindowSession getWindowSession() {
    3. synchronized (WindowManagerGlobal.class) {
    4. if (sWindowSession == null) {
    5. try {
    6. InputMethodManager imm = InputMethodManager.getInstance();//输入法
    7. IWindowManager windowManager = getWindowManagerService();//获取wms的binder代理对象
    8. sWindowSession = windowManager.openSession(
    9. new IWindowSessionCallback.Stub() {//传入了客户端实现的WindowSessionCallback回调类,用于wms通信到应用程序
    10. @Override
    11. public void onAnimatorScaleChanged(float scale) {
    12. ValueAnimator.setDurationScale(scale);
    13. }
    14. },
    15. imm.getClient(), imm.getInputContext());
    16. } catch (RemoteException e) {
    17. throw e.rethrowFromSystemServer();
    18. }
    19. }
    20. return sWindowSession;
    21. }
    22. }
    1. //aidl接口如下
    2. IWindowSession openSession(in IWindowSessionCallback callback, in IInputMethodClient client,
    3. in IInputContext inputContext);

     2.5 WMS中的openSession

    主要作用:

    1.此时会走到WMS中,WMS中会创建一个session对象

    1. //此时会调用到WMS中
    2. public class WindowManagerService extends IWindowManager.Stub
    3. implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs
    4. {
    5. public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
    6. IInputContext inputContext)
    7. {
    8. if (client == null) throw new IllegalArgumentException("null client");
    9. if (inputContext == null) throw new IllegalArgumentException("null inputContext");
    10. Session session = new Session(this, callback, client, inputContext);
    11. return session;//
    12. }
    13. }

     2.6 Session

    主要作用为:
    1.此类内部会保存WindowManagerService对象和客户端实现的IWindowSessionCallback类对象

    1. class Session extends IWindowSession.Stub implements IBinder.DeathRecipient
    2. {
    3. public Session(WindowManagerService service, IWindowSessionCallback callback,
    4. IInputMethodClient client, IInputContext inputContext) {
    5. mService = service;//此时service是WindowManagerService对象
    6. mCallback = callback;//callback是客户端实现的IWindowSessionCallback类对象,是一个binder对象
    7. mClient = client;//此时是输入法的客户端
    8. mUid = Binder.getCallingUid();
    9. mPid = Binder.getCallingPid();
    10. synchronized (mService.mWindowMap) {
    11. if (mService.mInputMethodManager == null && mService.mHaveInputMethods) {
    12. IBinder b = ServiceManager.getService(
    13. Context.INPUT_METHOD_SERVICE);
    14. mService.mInputMethodManager = IInputMethodManager.Stub.asInterface(b);//获取输入法的binder代理对象
    15. }
    16. }
    17. long ident = Binder.clearCallingIdentity();
    18. try {
    19. // Note: it is safe to call in to the input method manager
    20. // here because we are not holding our lock.
    21. if (mService.mInputMethodManager != null) {
    22. mService.mInputMethodManager.addClient(client, inputContext,
    23. mUid, mPid);//将此addClient添加到输入法中
    24. } else {
    25. client.setUsingInputMethod(false);
    26. }
    27. client.asBinder().linkToDeath(this, 0);
    28. } catch (RemoteException e) {
    29. // The caller has died, so we can just forget about this.
    30. try {
    31. if (mService.mInputMethodManager != null) {
    32. mService.mInputMethodManager.removeClient(client);
    33. }
    34. } catch (RemoteException ee) {
    35. }
    36. } finally {
    37. Binder.restoreCallingIdentity(ident);
    38. }
    39. }
    40. }

    2.7 class W

    主要作用:

    1.W 继承自 IWindow.Stub,用于wms服务端向应用端通信。在调用本类的setView时会将此W对象传递给WMS

    1. static class W extends IWindow.Stub {
    2. private final WeakReference mViewAncestor;
    3. private final IWindowSession mWindowSession;
    4. W(ViewRootImpl viewAncestor) {
    5. mViewAncestor = new WeakReference(viewAncestor);
    6. mWindowSession = viewAncestor.mWindowSession;//客户端保存IWindowSession session通信对象
    7. }
    8. }

    2.8 setView

    主要作用为:

    1.应用端创建空的InputChannel对象。

    2.应用端通过binder调用addToDisplay函数,此时会走到到wms服务中,在wms中会对mInputChannel赋值,并返回。返回的是一个已经和IMS连接的socket。

    3.当应用端拿到socket后,会new WindowInputEventReceiver对象,此对象内部最终后调用应用端ui主线程的Looper::addFd函数,通过epoll机制监听此socket,当存在IMS发送事件到应用程序端时,会调用NativeInputEventReceiver的handleEvent函数。

    1. //frmaework/base/core/java/android/view/ViewRootImpl.java
    2. public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    3. //view是view代表添加哪个窗口,此时view是DecorView
    4. //params窗口的参数
    5. //panelParentView,如果是子窗口,则子窗口存在父窗口,此时是普通窗口,则为null
    6. synchronized (this) {
    7. if (mView == null) {
    8. mView = view;
    9. mWindowAttributes.copyFrom(attrs);
    10. if (mWindowAttributes.packageName == null) {
    11. mWindowAttributes.packageName = mBasePackageName;//给mWindowAttributes添加了包名
    12. }
    13. attrs = mWindowAttributes;
    14. if ((mWindowAttributes.inputFeatures
    15. & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
    16. mInputChannel = new InputChannel();//客户端创建InputChannel对象,此时的InputChannel是空的对象,并没有赋值的内容
    17. }
    18. try {
    19. //将空的mInputChannel传入其中,应用端通过binder调用到wms中,在wms中会对mInputChannel赋值
    20. res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
    21. getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
    22. mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
    23. mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
    24. //mWindow继承自IWindow.Stub,用于wms服务端向应用端通信
    25. //mWindowAttributes窗口属性
    26. //mInputChannel本质是socket,用于应用程序和ims进行输入事件的通信
    27. }
    28. if (mInputChannel != null)
    29. {
    30. mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
    31. Looper.myLooper());
    32. }
    33. }
    34. }
    35. }

    2.9 addToDisplay

    此时会通过binder走到WMS的系统服务中。

    主要作用:

    1.调用Windowmanagerservice的addWindow函数。

    1. class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
    2. public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
    3. int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
    4. Rect outStableInsets, Rect outOutsets,
    5. DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) {
    6. //此时mWindow继承自IWindow.Stub,用于wms服务端向应用端通信
    7. //outInputChannel本质是socket,用于应用程序和ims进行输入事件的通信
    8. return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
    9. outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel);
    10. }
    11. }

    2.10 addWindow

    主要作用:

    1.调用WindowState类的openInputChannel函数。

    1. //Windowmanagerservice.java
    2. public int addWindow(Session session, IWindow client, int seq,
    3. LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
    4. Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
    5. DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel)
    6. //此时client客户端的Window,继承自IWindow.Stub,用于wms服务端向应用端通信
    7. //outInputChannel本质是socket,用于应用程序和ims进行输入事件的通信
    8. {
    9. //仅当窗口的inputFeatures未指定NO_INPUT_CHANNEL选项时才会为此窗口创建InputChannel对
    10. boolean openInputChannels = (outInputChannel != null&& (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
    11. if (callingUid != SYSTEM_UID)
    12. {
    13. Slog.e(TAG_WM,"App trying to use insecure INPUT_FEATURE_NO_INPUT_CHANNEL flag. Ignoring");
    14. openInputChannels = true;
    15. }
    16. if (openInputChannels)
    17. {
    18. win.openInputChannel(outInputChannel);//win1是一个WindowState类对象,调用其类的openInputChannel函数
    19. }
    20. }

    2.11 openInputChannel

    主要作用为:

    1.调用openInputChannelPair创建一个InputChannel对

    2.将0号inputChannel调用InputManager.registerInputChannel注册到IMS中

    3.将1号inputChannel传递给outInputChannel,即传递给应用端。

    1. // frmaework/base/services/core/java/com/android/server/wm/WindowState.java
    2. void openInputChannel(InputChannel outInputChannel)
    3. {
    4. if (mInputChannel != null) {
    5. throw new IllegalStateException("Window already has an input channel.");
    6. }
    7. String name = getName();
    8. InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);//会创建一个sokcet对
    9. mInputChannel = inputChannels[0];
    10. mClientChannel = inputChannels[1];
    11. mInputWindowHandle.inputChannel = inputChannels[0];//其中0号inputChannel交给InputWindowHandle保存
    12. if (outInputChannel != null) {
    13. mClientChannel.transferTo(outInputChannel);//将mClientChannel所持有的1号inputChannel传递给outInputChannel
    14. mClientChannel.dispose();
    15. mClientChannel = null;
    16. }
    17. /*else {
    18. // If the window died visible, we setup a dummy input channel, so that taps
    19. // can still detected by input monitor channel, and we can relaunch the app.
    20. // Create dummy event receiver that simply reports all events as handled.
    21. mDeadWindowEventReceiver = new DeadWindowEventReceiver(mClientChannel);
    22. }*/
    23. mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle);//将0号的InputChannel向IMS进行注册
    24. //mService定义是com.android.server.wm.WindowManagerService mService
    25. //故mInputManager是构造wms时传入的,mInputManager是InputManagerService对象
    26. }

    2.12 Java层openInputChannelPair

    1. //路径:frameworks\base\core\java\android\view\InputChannel.java
    2. public static InputChannel[] openInputChannelPair(String name)
    3. {
    4. if (name == null) {
    5. throw new IllegalArgumentException("name must not be null");
    6. }
    7. if (DEBUG) {
    8. Slog.d(TAG, "Opening input channel pair '" + name + "'");
    9. }
    10. return nativeOpenInputChannelPair(name);
    11. }

    2.13 android_view_InputChannel_nativeOpenInputChannelPair

    主要作用为:

    1.调用InputChannel::openInputChannelPair创建两个c++类型的InputChannel对象。

    2.将c++层的两个InputChannel对象转化为java层的InputChannel对象,并返回到java层。

    1. 路径:frameworks/base/core/jni/android_view_InputChannel.cpp
    2. static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env,
    3. jclass clazz, jstring nameObj) {
    4. const char* nameChars = env->GetStringUTFChars(nameObj, NULL);
    5. std::string name = nameChars;
    6. env->ReleaseStringUTFChars(nameObj, nameChars);
    7. sp serverChannel;
    8. sp clientChannel;
    9. status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);//调用c++层的openInputChannelPair
    10. if (result) {
    11. String8 message;
    12. message.appendFormat("Could not open input channel pair. status=%d", result);
    13. jniThrowRuntimeException(env, message.string());
    14. return NULL;
    15. }
    16. //创建两个存储gInputChannelClassInfo.clazz类型的对象的数组,默认值为null
    17. jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL);
    18. if (env->ExceptionCheck()) {
    19. return NULL;
    20. }
    21. //将c++类型的serverChannel对象转化为java类型的serverChannelObj
    22. jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,
    23. std::make_unique(serverChannel));
    24. if (env->ExceptionCheck()) {
    25. return NULL;
    26. }
    27. jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,
    28. std::make_unique(clientChannel));
    29. if (env->ExceptionCheck()) {
    30. return NULL;
    31. }
    32. env->SetObjectArrayElement(channelPair, 0, serverChannelObj);//将java类型的serverChannelObj放入数组
    33. env->SetObjectArrayElement(channelPair, 1, clientChannelObj);
    34. return channelPair;//返回一个存储java类型的ChannelObj数组
    35. }

    2.14 openInputChannelPair

    主要作用为:

    1.通过socketpair创建一个socket对。

    2.设置socket对的发送缓冲区和接受缓冲区的大小

    3.用c++层的InputChannel封装socket对,一个封装给IMS,一个封装最终会通过binder调用返回给应用端。

    1. // frameworks/native/libs/input/InputTransport.cpp
    2. status_t InputChannel::openInputChannelPair(const std::string& name,sp& outServerChannel, sp& outClientChannel) {
    3. int sockets[2];
    4. if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {//将sockets数组传入,通过socketpair函数创建sockets对,返回值为0代表成功
    5. status_t result = -errno;
    6. ALOGE("channel '%s' ~ Could not create socket pair. errno=%d",
    7. name.c_str(), errno);
    8. outServerChannel.clear();
    9. outClientChannel.clear();
    10. return result;
    11. }
    12. int bufferSize = SOCKET_BUFFER_SIZE;
    13. setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));//设置socket[0]的发送缓冲区大小
    14. setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));//设置socket[0]的接收缓冲区大小
    15. setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));//设置socket[1]的发送缓冲区大小
    16. setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));//设置socket[1]的接收缓冲区大小
    17. std::string serverChannelName = name;
    18. serverChannelName += " (server)";
    19. outServerChannel = new InputChannel(serverChannelName, sockets[0]);//用InputChannel封装sockets[0],给服务端
    20. std::string clientChannelName = name;
    21. clientChannelName += " (client)";
    22. outClientChannel = new InputChannel(clientChannelName, sockets[1]);//用InputChannel封装sockets[1],给客户端
    23. return OK;
    24. }

    2.15 transferTo

    主要作用是:

    1.转移socket给输入的参数。

    1. // frameworks\base\core\java\android\view\InputChannel.java
    2. public void transferTo(InputChannel outParameter) {
    3. if (outParameter == null) {
    4. throw new IllegalArgumentException("outParameter must not be null");
    5. }
    6. nativeTransferTo(outParameter);
    7. }
    1. static void android_view_InputChannel_nativeTransferTo(JNIEnv* env, jobject obj,jobject otherObj) {
    2. if (android_view_InputChannel_getNativeInputChannel(env, otherObj) != NULL) {
    3. jniThrowException(env, "java/lang/IllegalStateException",
    4. "Other object already has a native input channel.");
    5. return;
    6. }
    7. NativeInputChannel* nativeInputChannel = android_view_InputChannel_getNativeInputChannel(env, obj);
    8. android_view_InputChannel_setNativeInputChannel(env, otherObj, nativeInputChannel);//将调用者mClientChannel对应的NativeInputChannel对象,赋值给outInputChannel
    9. android_view_InputChannel_setNativeInputChannel(env, obj, NULL);//将调用者mClientChannel的置空
    10. }
    1. static NativeInputChannel* android_view_InputChannel_getNativeInputChannel(JNIEnv* env,jobject inputChannelObj) {
    2. jlong longPtr = env->GetLongField(inputChannelObj, gInputChannelClassInfo.mPtr);
    3. return reinterpret_cast(longPtr);
    4. }

    2.16 WMS向IMS注册并监听socket

    1. //InputManagerService.java
    2. //注册输入通道,以便将其用作输入事件目标。
    3. //@param inputChannel要注册的输入通道。
    4. //@param inputWindowHandle与输入通道关联的输入窗口句柄,如果没有,则为null。
    5. public void registerInputChannel(InputChannel inputChannel,InputWindowHandle inputWindowHandle) {
    6. if (inputChannel == null) {
    7. throw new IllegalArgumentException("inputChannel must not be null.");
    8. }
    9. nativeRegisterInputChannel(mPtr, inputChannel, inputWindowHandle, false);//mPtr是nativeInit返回的值,是指向NativeInputManager的指针。
    10. //inputChannel输入通道
    11. //inputWindowHandle窗口的句柄
    12. }

    2.17 nativeRegisterInputChannel

    主要作用为:

    1.将java层获取的inputChannel对象转化为c++层的inputChannel。

    2.将java层的窗口句柄inputWindowHandleObj对象转化为c++层的inputWindowHandle。

    3.向IMS注册此inputChannel对象和对应的窗口句柄inputWindowHandle

    4.设置一个回调函数handleInputChannelDisposed,此回调函数会在调用java层的InputChannel对象完成或者销毁时触发,其会调用unregisterInputChannel取消IMS中已经保存和监听的socket

    1. static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */,
    2. jlong ptr, jobject inputChannelObj, jobject inputWindowHandleObj, jboolean monitor) {
    3. NativeInputManager* im = reinterpret_cast(ptr);
    4. sp inputChannel = android_view_InputChannel_getInputChannel(env,
    5. inputChannelObj);//从java层获取native层的inputChannel对象
    6. if (inputChannel == NULL) {
    7. throwInputChannelNotInitialized(env);
    8. return;
    9. }
    10. sp inputWindowHandle =
    11. android_server_InputWindowHandle_getHandle(env, inputWindowHandleObj);//获取此窗口在input中的句柄
    12. status_t status = im->registerInputChannel(
    13. env, inputChannel, inputWindowHandle, monitor);//向ims注册监听此socket
    14. if (status) {
    15. std::string message;
    16. message += StringPrintf("Failed to register input channel. status=%d", status);
    17. jniThrowRuntimeException(env, message.c_str());
    18. return;
    19. }
    20. if (! monitor) {//此时是false
    21. android_view_InputChannel_setDisposeCallback(env, inputChannelObj,
    22. handleInputChannelDisposed, im);//设置一个回调函数,此回调函数会在调用java层的InputChannel对象完成或者销毁时,触发
    23. //主要是调用unregisterInputChannel取消IMS中已经保存和监听的socket
    24. }
    25. }

    2.18 android_view_InputChannel_getInputChannel

    主要作用为:

    1.通过java层的InputChannel对象获取指向c++层的InputChannel对象的指针。

    1. sp android_view_InputChannel_getInputChannel(JNIEnv* env, jobject inputChannelObj)
    2. {
    3. NativeInputChannel* nativeInputChannel = android_view_InputChannel_getNativeInputChannel(env, inputChannelObj);
    4. return nativeInputChannel != NULL ? nativeInputChannel->getInputChannel() : NULL;
    5. }
    6. static NativeInputChannel* android_view_InputChannel_getNativeInputChannel(JNIEnv* env,
    7. jobject inputChannelObj)
    8. {
    9. jlong longPtr = env->GetLongField(inputChannelObj, gInputChannelClassInfo.mPtr);
    10. return reinterpret_cast(longPtr);
    11. }
    12. inline sp getInputChannel() { return mInputChannel; }

    2.19 android_server_InputWindowHandle_getHandle

    主要作用为:

    1.获取inputWindowHandle类对象,这是WMS的一个窗口的句柄。

    1. //base/services/core/jni/com_android_server_input_InputWindowHandle.cpp
    2. sp android_server_InputWindowHandle_getHandle(
    3. JNIEnv* env, jobject inputWindowHandleObj) {//inputWindowHandleObj是java层传入的inputWindowHandle类对象
    4. if (!inputWindowHandleObj) {//如果为空,返回
    5. return NULL;
    6. }
    7. AutoMutex _l(gHandleMutex);
    8. jlong ptr = env->GetLongField(inputWindowHandleObj, gInputWindowHandleClassInfo.ptr);
    9. NativeInputWindowHandle* handle;
    10. if (ptr) {
    11. handle = reinterpret_cast(ptr);
    12. } else {
    13. jobject inputApplicationHandleObj = env->GetObjectField(inputWindowHandleObj,
    14. gInputWindowHandleClassInfo.inputApplicationHandle);
    15. sp inputApplicationHandle =
    16. android_server_InputApplicationHandle_getHandle(env, inputApplicationHandleObj);
    17. env->DeleteLocalRef(inputApplicationHandleObj);
    18. jweak objWeak = env->NewWeakGlobalRef(inputWindowHandleObj);
    19. handle = new NativeInputWindowHandle(inputApplicationHandle, objWeak);
    20. handle->incStrong((void*)android_server_InputWindowHandle_getHandle);
    21. env->SetLongField(inputWindowHandleObj, gInputWindowHandleClassInfo.ptr,
    22. reinterpret_cast(handle));
    23. }
    24. return handle;
    25. }

    2.20 registerInputChannel

    主要作用为:

    1.调用InputDispatcher的registerInputChannel函数,将窗口的inputChannel和其对应的窗口句柄inputWindowHandle注册到IMS中保存。

    1. //com_android_server_input_InputManagerService.cpp
    2. status_t NativeInputManager::registerInputChannel(JNIEnv* /* env */,
    3. const sp& inputChannel,
    4. const sp& inputWindowHandle, bool monitor) {
    5. ATRACE_CALL();
    6. return mInputManager->getDispatcher()->registerInputChannel(
    7. inputChannel, inputWindowHandle, monitor);//mInputManager是InputManager.cpp类的对象,
    8. //所以,mInputManager->getDispatcher()是一个InputDispatcher.cpp类的对象
    9. //inputChannel是IMS服务端的socket,用于接收来自应用端的消息
    10. //inputWindowHandle是对应的窗口的句柄。一个窗口对应一个socket对。
    11. }

    2.21 InputDispatcher::registerInputChannel

    主要作用为:

    1.new了一个Connection对象,此对象里面保存了inputChannel和其对应的窗口句柄inputWindowHandle

    2.将此socket的fd和此Connection对象,保存到一个容器中。当input事件发生时,会根据找到的目标窗口句柄取出对应的inputChannel,从中获取socket的fd,然后IMS会通过此socket将消息发送到应用端。

    3.通过 mLooper->addFd,监听此socket的fd,从上文我们知道,WMS会创建两个已经连接好的shocket对,其中一个给到应用程序端,一个给到IMS,此处的作用便是监听来自应用程序端发送给IMS的消息,当应用程序端通过sokcet发送消息时,IMS中的handleReceiveCallback回调函数会执行。

    1. status_t InputDispatcher::registerInputChannel(const sp& inputChannel,
    2. const sp& inputWindowHandle, bool monitor) {
    3. #if DEBUG_REGISTRATION
    4. ALOGD("channel '%s' ~ registerInputChannel - monitor=%s", inputChannel->getName().c_str(),
    5. toString(monitor));
    6. #endif
    7. { // acquire lock
    8. AutoMutex _l(mLock);
    9. if (getConnectionIndexLocked(inputChannel) >= 0) {//调用getConnectionIndexLocked方法,
    10. //根据inputChannel的fd值,查找mConnectionsByFd,看看是否此input channel已经注册
    11. ALOGW("Attempted to register already registered input channel '%s'",
    12. inputChannel->getName().c_str());
    13. return BAD_VALUE;
    14. }
    15. sp connection = new Connection(inputChannel, inputWindowHandle, monitor);//new了一个Connection对象,
    16. //此对象里面保存了inputChannel和其对应的窗口句柄inputWindowHandle
    17. int fd = inputChannel->getFd();
    18. mConnectionsByFd.add(fd, connection);//放入mConnectionsByFd中,表示已经注册过的inputChannel
    19. if (monitor) {//此时是false
    20. mMonitoringChannels.push(inputChannel);
    21. }
    22. mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
    23. //监听Inputchannel的可读性。
    24. //mLooper的pollOnce()本质上就是epoll_wait(),因此Looper对象具有监听文件描述符可读性事件的能力,在此注册Inputchannel可读性事件,
    25. //并在事件到来时通过handleReceiveCallback()回调进行处理
    26. } // release lock
    27. // Wake the looper because some connections have changed.
    28. mLooper->wake();//唤醒InputDispatcher线程
    29. return OK;
    30. }
    1. ssize_t InputDispatcher::getConnectionIndexLocked(const sp& inputChannel) {
    2. ssize_t connectionIndex = mConnectionsByFd.indexOfKey(inputChannel->getFd());
    3. if (connectionIndex >= 0) {
    4. sp connection = mConnectionsByFd.valueAt(connectionIndex);
    5. if (connection->inputChannel.get() == inputChannel.get()) {
    6. return connectionIndex;
    7. }
    8. }
    9. return -1;
    10. }

    2.22 Connection

    Connection类描述了从ImputDispatcher到目标窗口中的一个连接,其中保存了向窗口发送的事件的状态信息。
    在 Connection中,重要的成员有:
    1.mlnputPublisher,InputPublisher类的一个对象,它封装InputChannel并直接对其进行写入和读取。另外,它也负责ImputMessage结构体的封装与解析。
    2.outboundQueue,用于保存等待通过此Connection进行发送的事件队列。
    3.waitQueue,用于保存已经通过此Connection将事件发送给窗口,正在等待窗口反馈的事件队列。

    1. InputDispatcher::Connection::Connection(const sp& inputChannel,
    2. const sp& inputWindowHandle, bool monitor) :
    3. status(STATUS_NORMAL), inputChannel(inputChannel), inputWindowHandle(inputWindowHandle),
    4. monitor(monitor),
    5. inputPublisher(inputChannel), inputPublisherBlocked(false) {
    6. }
    1. class Connection : public RefBase {
    2. protected:
    3. virtual ~Connection();
    4. public:
    5. enum Status {
    6. // 连接状态正常
    7. STATUS_NORMAL,
    8. // 发生了不可恢复的通信错误
    9. STATUS_BROKEN,
    10. // input channel已注销。
    11. STATUS_ZOMBIE
    12. };
    13. Status status;
    14. sp inputChannel; //永不为空
    15. sp inputWindowHandle; // 可能为空
    16. bool monitor;
    17. InputPublisher inputPublisher;
    18. InputState inputState;
    19. //如果套接字已满,并且在应用程序使用某些输入之前无法发送其他事件,则为True。
    20. bool inputPublisherBlocked;
    21. // 事件队列需要发送到Connection
    22. Queue outboundQueue;
    23. //已发送到connection但尚未收到应用程序“完成”响应的事件队列。
    24. Queue waitQueue;
    25. explicit Connection(const sp& inputChannel,
    26. const sp& inputWindowHandle, bool monitor);
    27. inline const std::string getInputChannelName() const { return inputChannel->getName(); }
    28. const std::string getWindowName() const;
    29. const char* getStatusLabel() const;
    30. DispatchEntry* findWaitQueueEntry(uint32_t seq);
    31. };

    2.23 android_view_InputChannel_setDisposeCallback

    主要作用为:

    1.设置一个回调函数,此回调函数会在调用java层的InputChannel对象完成或者销毁时触发,主要是调用unregisterInputChannel取消IMS中已经保存和监听的socket,并清空此InputChannel对象对应的Connection对象的outboundQueue和waitQueue队列。

    1. void android_view_InputChannel_setDisposeCallback(JNIEnv* env, jobject inputChannelObj,
    2. InputChannelObjDisposeCallback callback, void* data) {
    3. NativeInputChannel* nativeInputChannel =
    4. android_view_InputChannel_getNativeInputChannel(env, inputChannelObj);//根据java层保存的native层的指针的值,获取nativeInputChannel指针
    5. ALOGW("Cannot set dispose callback because input channel object has not been initialized.");
    6. } else {
    7. nativeInputChannel->setDisposeCallback(callback, data);//此时callback是handleInputChannelDisposed
    8. //data是NativeInputManager
    9. }
    10. }
    11. void NativeInputChannel::setDisposeCallback(InputChannelObjDisposeCallback callback, void* data) {
    12. mDisposeCallback = callback;//此时callback是handleInputChannelDisposed
    13. mDisposeData = data;//data是NativeInputManager
    14. }

     handleInputChannelDisposed主要作用是取消注册inputchannel。

    1. static void handleInputChannelDisposed(JNIEnv* env,
    2. jobject /* inputChannelObj */, const sp& inputChannel, void* data) {
    3. NativeInputManager* im = static_cast(data);
    4. ALOGW("Input channel object '%s' was disposed without first being unregistered with "
    5. "the input manager!", inputChannel->getName().c_str());
    6. im->unregisterInputChannel(env, inputChannel);
    7. }

    那么这个回调函数什么时候会被调用呢?

    在Object类里面,有一个方法finalize()。
    当VM的垃圾收集器检测到这个对象不可达的时候,也就是说这个对象为垃圾可以被回收的时候,这个对象的finalize ()方法就会被执行,默认情况下,它不做任何处理,我们可以重写这个方法来进行资源的释放。当回收分配的Object对象的内存之前垃圾收集器会调用对象的finalize()方法。

    1. //InputChannel.java
    2. protected void finalize() throws Throwable {
    3. try {
    4. nativeDispose(true);
    5. } finally {
    6. super.finalize();
    7. }
    8. }

    查看对应关系

    { "nativeDispose", "(Z)V",(void*)android_view_InputChannel_nativeDispose },
    1. static void android_view_InputChannel_nativeDispose(JNIEnv* env, jobject obj, jboolean finalized) {
    2. NativeInputChannel* nativeInputChannel =
    3. android_view_InputChannel_getNativeInputChannel(env, obj);
    4. if (nativeInputChannel) {
    5. if (finalized) {
    6. ALOGW("Input channel object '%s' was finalized without being disposed!",
    7. nativeInputChannel->getInputChannel()->getName().c_str());
    8. }
    9. nativeInputChannel->invokeAndRemoveDisposeCallback(env, obj);
    10. android_view_InputChannel_setNativeInputChannel(env, obj, NULL);
    11. delete nativeInputChannel;
    12. }
    13. }
    1. static void android_view_InputChannel_nativeDispose(JNIEnv* env, jobject obj, jboolean finalized) {
    2. NativeInputChannel* nativeInputChannel =
    3. android_view_InputChannel_getNativeInputChannel(env, obj);//根据java层的InputChannel获取native层的NativeInputChannel
    4. if (nativeInputChannel) {
    5. if (finalized) {
    6. ALOGW("Input channel object '%s' was finalized without being disposed!",
    7. nativeInputChannel->getInputChannel()->getName().c_str());
    8. }
    9. nativeInputChannel->invokeAndRemoveDisposeCallback(env, obj);
    10. android_view_InputChannel_setNativeInputChannel(env, obj, NULL);//设置Java曾保存的指向native层的NativeInputChannel的指针为空
    11. delete nativeInputChannel;//delete
    12. }
    13. }
    1. void NativeInputChannel::invokeAndRemoveDisposeCallback(JNIEnv* env, jobject obj) {
    2. if (mDisposeCallback) {
    3. mDisposeCallback(env, obj, mInputChannel, mDisposeData);//会执行handleInputChannelDisposed回调函数
    4. mDisposeCallback = NULL;
    5. mDisposeData = NULL;
    6. }
    7. }

      handleInputChannelDisposed主要作用是取消注册inputchannel。

    1. static void handleInputChannelDisposed(JNIEnv* env,
    2. jobject /* inputChannelObj */, const sp& inputChannel, void* data) {
    3. NativeInputManager* im = static_cast(data);
    4. ALOGW("Input channel object '%s' was disposed without first being unregistered with "
    5. "the input manager!", inputChannel->getName().c_str());
    6. im->unregisterInputChannel(env, inputChannel);
    7. }

    查看unregisterInputChannel

    1. status_t NativeInputManager::unregisterInputChannel(JNIEnv* /* env */,
    2. const sp& inputChannel) {
    3. ATRACE_CALL();
    4. return mInputManager->getDispatcher()->unregisterInputChannel(inputChannel);
    5. }

    查看InputDispatcher的unregisterInputChannel函数。

    1. status_t InputDispatcher::unregisterInputChannel(const sp& inputChannel) {
    2. { // acquire lock
    3. AutoMutex _l(mLock);
    4. status_t status = unregisterInputChannelLocked(inputChannel, false /*notify*/);
    5. if (status) {
    6. return status;
    7. }
    8. } // release lock
    9. // Wake the poll loop because removing the connection may have changed the current
    10. // synchronization state.
    11. mLooper->wake();
    12. return OK;
    13. }

     主要作用为:

    1.从容器中删除此Connection对象

    2.从epoll_wait中取消此socket的监听

    3.清空此connection的等待发送队列outboundQueue和发送成功等待回应的消息队列

    1. status_t InputDispatcher::unregisterInputChannelLocked(const sp& inputChannel,
    2. bool notify) {
    3. ssize_t connectionIndex = getConnectionIndexLocked(inputChannel);//从保存所有inputChannel的容器中找到当前connection的索引
    4. if (connectionIndex < 0) {
    5. ALOGW("Attempted to unregister already unregistered input channel '%s'",
    6. inputChannel->getName().c_str());
    7. return BAD_VALUE;
    8. }
    9. sp connection = mConnectionsByFd.valueAt(connectionIndex);
    10. mConnectionsByFd.removeItemsAt(connectionIndex);//从容器中删除此Connection对象,Connection中保存了inputChannel和其对应的窗口句柄
    11. if (connection->monitor) {
    12. removeMonitorChannelLocked(inputChannel);
    13. }
    14. mLooper->removeFd(inputChannel->getFd());//mLooper本质是epoll_wait,从epoll_wait中取消此socket的监听,即不再监听来自应用程序端的消息
    15. nsecs_t currentTime = now();
    16. abortBrokenDispatchCycleLocked(currentTime, connection, notify);//清空此connection的等待发送队列outboundQueue和发送成功等待回应的消息队列
    17. connection->status = Connection::STATUS_ZOMBIE;
    18. return OK;
    19. }
    1. void InputDispatcher::abortBrokenDispatchCycleLocked(nsecs_t currentTime,
    2. const sp& connection, bool notify) {
    3. // Clear the dispatch queues.
    4. drainDispatchQueueLocked(&connection->outboundQueue);
    5. traceOutboundQueueLengthLocked(connection);
    6. drainDispatchQueueLocked(&connection->waitQueue);
    7. traceWaitQueueLengthLocked(connection);
    8. if (connection->status == Connection::STATUS_NORMAL) {
    9. connection->status = Connection::STATUS_BROKEN;
    10. /*此时是false
    11. if (notify) {
    12. // Notify other system components.
    13. onDispatchCycleBrokenLocked(currentTime, connection);
    14. }*/
    15. }
    16. }

    2.24 WindowInputEventReceiver

    此时我们已经知道了WMS将其中一个sokcet注册给了IMS。我们接下来看看第二个socket,应用端是如何处理的?

    在上文的ViewRootImpl的setView最后,应用端会new一个WindowInputEventReceiver,然后会调用InputEventReceiver构造函数。

    1. //ViewRootImpl.java
    2. public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    3. mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,Looper.myLooper());//mInputChannel是客户端的socket[1],Looper.myLooper主线程的looper
    4. }
    5. final class WindowInputEventReceiver extends InputEventReceiver {
    6. public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
    7. super(inputChannel, looper);//调用InputEventReceiver的构造方法
    8. }
    9. }

    2.25 InputEventReceiver

    主要作用:

    1.保存java层的inputChannel对象

    2.获取UI主线程,此处主要是需要将客户端的socket添加到Looper中监听,其实Looper的底层也是epoll_wait

    3.调用nativeInit函数,此函数内部会new一个NativeInputEventReceiver对象,然后调用此对象的初始化函数,初始化函数的内部会将此应用端的socket添加到Looper中监听。当存在IMS发送事件到应用程序端时,会调用NativeInputEventReceiver的handleEvent函数。

    1. public InputEventReceiver(InputChannel inputChannel, Looper looper) {
    2. mInputChannel = inputChannel;//socket[1]
    3. mMessageQueue = looper.getQueue();//UI 线程消息队列
    4. mReceiverPtr = nativeInit(new WeakReference(this),inputChannel, mMessageQueue);
    5. }

    2.26 nativeInit

    主要作用为:

    1.new了一个NativeInputEventReceiver对象。

    2.调用NativeInputEventReceiver的initialize函数。初始化函数的内部会将此应用端的socket添加到Looper中监听。当存在IMS发送事件到应用程序端时,会调用NativeInputEventReceiver的handleEvent函数。

    1. static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
    2. jobject inputChannelObj, jobject messageQueueObj) {
    3. sp inputChannel = android_view_InputChannel_getInputChannel(env,inputChannelObj);//获取c++层的inputChannel
    4. if (inputChannel == NULL) {
    5. jniThrowRuntimeException(env, "InputChannel is not initialized.");
    6. return 0;
    7. }
    8. sp messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);//获取消息队列
    9. if (messageQueue == NULL) {
    10. jniThrowRuntimeException(env, "MessageQueue is not initialized.");
    11. return 0;
    12. }
    13. sp receiver = new NativeInputEventReceiver(env,//new了一个NativeInputEventReceiver对象
    14. receiverWeak, inputChannel, messageQueue);
    15. status_t status = receiver->initialize();//初始化NativeInputEventReceiver对象
    16. /*
    17. if (status) {//如果初始化失败
    18. String8 message;
    19. message.appendFormat("Failed to initialize input event receiver. status=%d", status);
    20. jniThrowRuntimeException(env, message.string());
    21. return 0;
    22. }*/
    23. receiver->incStrong(gInputEventReceiverClassInfo.clazz); // retain a reference for the object
    24. return reinterpret_cast(receiver.get());
    25. }

    2.27 NativeInputEventReceiver

    里面创建了一个InputConsumer类对象,用来保存c++层的inputChannel

    1. NativeInputEventReceiver::NativeInputEventReceiver(JNIEnv* env,
    2. jobject receiverWeak, const sp& inputChannel,
    3. const sp& messageQueue) :
    4. mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)),
    5. mInputConsumer(inputChannel), mMessageQueue(messageQueue),//创建了一个InputConsumer类对象,用来保存c++层的inputChannel
    6. mBatchedInputEventPending(false), mFdEvents(0)
    7. {
    8. /*
    9. if (kDebugDispatchCycle) {//默认false
    10. ALOGD("channel '%s' ~ Initializing input event receiver.", getInputChannelName().c_str());
    11. }*/
    12. }
    1. InputConsumer::InputConsumer(const sp& channel) :
    2. mResampleTouch(isTouchResamplingEnabled()),//是否触摸重新采样
    3. mChannel(channel), mMsgDeferred(false)
    4. {
    5. }

    2.28 initialize

    主要作用:

    1.调用setFdEvents函数。

    1. status_t NativeInputEventReceiver::initialize()
    2. {
    3. setFdEvents(ALOOPER_EVENT_INPUT);//ALOOPER_EVENT_INPUT值是1
    4. return OK;
    5. }

    2.29 setFdEvents

    主要作用:

    1.调用了Looper的addfd函数,用于监听此fd,并传入了NativeInputEventReceiver对象,当InputManagerService发送消息到应用程序时,会调用NativeInputEventReceiver的handleEvent函数。

    1. void NativeInputEventReceiver::setFdEvents(int events) {
    2. if (mFdEvents != events) {//默认是0,此时为1
    3. mFdEvents = events;//设置为ALOOPER_EVENT_INPUT
    4. int fd = mInputConsumer.getChannel()->getFd();//获取socket[0]的fd
    5. if (events) {
    6. mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL);//调用了Looper的addfd函数,用于监听此fd,并传入了NativeInputEventReceiver对象
    7. } else {
    8. mMessageQueue->getLooper()->removeFd(fd);
    9. }
    10. }
    11. }

    2.30 Looper::addFd

    主要作用为:

    1.调用epoll_ctl监听此应用的socket,即监听来自IMS的输入事件。

    1. int Looper::addFd(int fd, int ident, int events,
    2. const sp& callback, void* data) {
    3. {
    4. AutoMutex _l(mLock);
    5. Request request;
    6. request.fd = fd;
    7. request.ident = ident;
    8. request.events = events;
    9. request.seq = mNextRequestSeq++;
    10. request.callback = callback; // 是指 NativeInputEventReceiver
    11. request.data = data;
    12. if (mNextRequestSeq == -1) mNextRequestSeq = 0;
    13. struct epoll_event eventItem;
    14. request.initEventItem(&eventItem);
    15. ssize_t requestIndex = mRequests.indexOfKey(fd);
    16. if (requestIndex < 0) {
    17. // 通过 epoll 监听 fd
    18. int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
    19. ......
    20. mRequests.add(fd, request); // 该fd 的 request 加入到 mRequests 队列
    21. } else {
    22. int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_MOD, fd, & eventItem);
    23. ......
    24. mRequests.replaceValueAt(requestIndex, request);
    25. }
    26. }
    27. return 1;
    28. }

  • 相关阅读:
    Milvus 介绍
    神经网络解决哪些问题,神经网络结果不稳定
    吴恩达机器学习笔记(一)
    顺丰快递商家寄件发货接口API类型和接入流程方案【快递100接口Demo】
    Java进阶笔记(中级)
    实战监听 Eureka client 的缓存更新
    hiberate核心API/配置文件/一级缓存详解
    关于飞桨UIE等模型预测推理时间很久的问题分析以及解决,蒸馏剪枝部署问题解决
    三维GIS可视化技术在城市管理中的作用
    Redis LFU缓存淘汰算法
  • 原文地址:https://blog.csdn.net/handsomethefirst/article/details/139449513