Android输入事件从读取到分发二:谁在循环监听事件的到来

来源:互联网 发布:samba python install 编辑:程序博客网 时间:2024/06/03 17:18
通过上一节初步阅读代码,已经找到了读写/dev/input/设备文件节点的位置。但是最后,我觉得应该有一个线程,一直循环监听这些输入设备,有事件的时候就去处理,没有事件的时候就睡眠,等待事件的到来。那么,这部分的代码是怎么样的呢?
上一节只是为了定位android系统在什么地方监听输入设备,所以很多地方没有仔细分析,这一节,带着文章开头提出的问题,再一次分析源码,而我们的入口,任然是系统启动后,注册服务的SystemServer.java文件。
[java] view plain copy
print?
  1. Slog.i(TAG, "Input Manager");  
  2. inputManager = new InputManagerService(context);  
  3.   
  4. Slog.i(TAG, "Window Manager");  
  5. wm = WindowManagerService.main(context, inputManager,  
  6.         mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,  
  7.         !mFirstBoot, mOnlyCore);  
  8. ServiceManager.addService(Context.WINDOW_SERVICE, wm);  
  9. ServiceManager.addService(Context.INPUT_SERVICE, inputManager);  
  10.   
  11. mActivityManagerService.setWindowManager(wm);  
  12.   
  13. inputManager.setWindowManagerCallbacks(wm.getInputMonitor());  
  14. inputManager.start();  
还是注册InputManagerService的这部分代码,上一节我们只是分析了InputManagerService的构造函数,没有分析最后调用的这个start方法,那这一次,就从这个start方法入手,再一次追踪android输入系统在系统开机后的一系列动作,从而明白输入系统是怎么一步步运行起来的。start函数的代码如下:
[java] view plain copy
print?
  1. public void start() {  
  2.      Slog.i(TAG, "Starting input manager");  
  3.      nativeStart(mPtr);  
  4.   
  5.      // Add ourself to the Watchdog monitors.  
  6.      Watchdog.getInstance().addMonitor(this);  
  7.   
  8.      registerPointerSpeedSettingObserver();  
  9.      registerShowTouchesSettingObserver();  
  10.   
  11.      mContext.registerReceiver(new BroadcastReceiver() {  
  12.          @Override  
  13.          public void onReceive(Context context, Intent intent) {  
  14.              updatePointerSpeedFromSettings();  
  15.              updateShowTouchesFromSettings();  
  16.          }  
  17.      }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);  
  18.   
  19.      updatePointerSpeedFromSettings();  
  20.      updateShowTouchesFromSettings();  
  21.  }  
start方法一开始就调用了nativeStart方法,记得上一节分析InputServiceManager的构造函数时,它调用了nativeInit()方法,从而展开了一系列输入事件管理的初始化动作。那么这里的nativeStart是不是就是在构造函数中调用nativeInit后,正式启动输入事件的管理框架的呢?感觉好像是的。nativeStart应该和nativeInit在同一个文件中:base/services/core/jni/com_android_server_input_InputManagerService.cpp,源码如下:
[java] view plain copy
print?
  1. static void nativeStart(JNIEnv* env, jclass clazz, jlong ptr) {  
  2.     NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);  
  3.   
  4.     status_t result = im->getInputManager()->start();  
  5.     if (result) {  
  6.         jniThrowRuntimeException(env, "Input manager could not be started.");  
  7.     }  
  8. }  
这里调用了NativeInputManager的getInputManager方法,这个方法很简单:
[java] view plain copy
print?
  1. inline sp<InputManager> getInputManager() const { return mInputManager; }  

它是一个内联方法,只是简单返回mInputManager变量,mInputManager是什么呢?
[java] view plain copy
print?
  1. mInputManager = new InputManager(eventHub, thisthis);  
可以看到它是一个InputManager的实例。所以,在nativeStart方法中,调用的start()是InputManager的start方法,这个方法源码如下:
[java] view plain copy
print?
  1. status_t InputManager::start() {  
  2.     status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);  
  3.     if (result) {  
  4.         ALOGE("Could not start InputDispatcher thread due to error %d.", result);  
  5.         return result;  
  6.     }  
  7.   
  8.     result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);  
  9.     if (result) {  
  10.         ALOGE("Could not start InputReader thread due to error %d.", result);  
  11.   
  12.         mDispatcherThread->requestExit();  
  13.         return result;  
  14.     }  
  15.   
  16.     return OK;  
  17. }  
这里只做了两件事情,分别启动一个线程,先看第一个线程:
[java] view plain copy
print?
  1. mDispatcherThread = new InputDispatcherThread(mDispatcher);  
从名字上看,是事件的分发线程,目前我关注的不是分发,而是事件的读取,所以这里先做个标记,之后研究分发事件的时候,再认真看这里,目前,这里略过。那么第二个线程是什么呢?
[java] view plain copy
print?
  1. mReaderThread = new InputReaderThread(mReader);  

从名字上看,它是一个读取事件的线程,好像关键点已经要到来了。nativeStart方法中调用了这个线程的run方法,这会导致thread_loop方法杯调用。InputReaderThread类定义在InputReader.h中,实现在InputRead.cpp中,源码如下:
[java] view plain copy
print?
  1. // --- InputReaderThread ---  
  2.   
  3. InputReaderThread::InputReaderThread(const sp<InputReaderInterface>& reader) :  
  4.         Thread(/*canCallJava*/ true), mReader(reader) {  
  5. }  
  6.   
  7. InputReaderThread::~InputReaderThread() {  
  8. }  
  9.   
  10. bool InputReaderThread::threadLoop() {  
  11.     mReader->loopOnce();  
  12.     return true;  
  13. }  
InputReaderThreader的构造函数和析构函数都为空,而thread_loop中也只是调用了mReader->loopOnce()方法。注意,这里是C++的线程定义方式,C++线程内建的函数在thread_loop函数返回true后,会不断重复调用thread_loop函数。所以这里可以看做一个死循环。但这里并不是轮询机制,因为loopOnce函数可能会休眠。这里很可能就是我们找的事件监听的循环线程,它不断重复的调用LoogOnce来监听输入事件。所以,现在看一下loopOnce()函数,这是个mReader指向的函数,mReader是一个InputReader的实例,所以这里就看一下InputReader类中的loopOnce方法:
[cpp] view plain copy
print?
  1. void InputReader::loopOnce() {  
  2.     int32_t oldGeneration;  
  3.     int32_t timeoutMillis;  
  4.     bool inputDevicesChanged = false;  
  5.     Vector<InputDeviceInfo> inputDevices;  
  6.     { // acquire lock  
  7.         AutoMutex _l(mLock);  
  8.   
  9.         oldGeneration = mGeneration;  
  10.         timeoutMillis = -1;  
  11.   
  12.         uint32_t changes = mConfigurationChangesToRefresh;  
  13.         if (changes) {  
  14.             mConfigurationChangesToRefresh = 0;  
  15.             timeoutMillis = 0;  
  16.             refreshConfigurationLocked(changes);  
  17.         } else if (mNextTimeout != LLONG_MAX) {  
  18.             nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);  
  19.             timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);  
  20.         }  
  21.     } // release lock  
  22.   
  23.     size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);  
  24.   
  25.     { // acquire lock  
  26.         AutoMutex _l(mLock);  
  27.         mReaderIsAliveCondition.broadcast();  
  28.   
  29.         if (count) {  
  30.             processEventsLocked(mEventBuffer, count);  
  31.         }  
  32.   
  33.         if (mNextTimeout != LLONG_MAX) {  
  34.             nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);  
  35.             if (now >= mNextTimeout) {  
  36. #if DEBUG_RAW_EVENTS  
  37.                 ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f);  
  38. #endif  
  39.                 mNextTimeout = LLONG_MAX;  
  40.                 timeoutExpiredLocked(now);  
  41.             }  
  42.         }  
  43.   
  44.         if (oldGeneration != mGeneration) {  
  45.             inputDevicesChanged = true;  
  46.             getInputDevicesLocked(inputDevices);  
  47.         }  
  48.     } // release lock  
  49.   
  50.     // Send out a message that the describes the changed input devices.  
  51.     if (inputDevicesChanged) {  
  52.         mPolicy->notifyInputDevicesChanged(inputDevices);  
  53.     }  
  54.   
  55.     // Flush queued events out to the listener.  
  56.     // This must happen outside of the lock because the listener could potentially call  
  57.     // back into the InputReader's methods, such as getScanCodeState, or become blocked  
  58.     // on another thread similarly waiting to acquire the InputReader lock thereby  
  59.     // resulting in a deadlock.  This situation is actually quite plausible because the  
  60.     // listener is actually the input dispatcher, which calls into the window manager,  
  61.     // which occasionally calls into the input reader.  
  62.     mQueuedListener->flush();  
  63. }  
这个函数就比较长了,主要也就两个代码块,中间夹了一个函数。这个函数有点特别,因为它是EventHub下的一个函数,而EventHub类,就是上节分析出来的,真正直接监听/dev/input/设备节点的类。这个函数就是:mEventHub->getEvents,源码当然在EventHub中:
[cpp] view plain copy
print?
  1. size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {  
  2.     ALOG_ASSERT(bufferSize >= 1);  
  3.   
  4.     AutoMutex _l(mLock);  
  5.   
  6.     struct input_event readBuffer[bufferSize];  
  7.   
  8.     RawEvent* event = buffer;  
  9.     size_t capacity = bufferSize;  
  10.     bool awoken = false;  
  11.     for (;;) {  
  12.         nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);  
  13.   
  14.         // Reopen input devices if needed.  
  15.         if (mNeedToReopenDevices) {  
  16.             mNeedToReopenDevices = false;  
  17.   
  18.             ALOGI("Reopening all input devices due to a configuration change.");  
  19.   
  20.             closeAllDevicesLocked();  
  21.             mNeedToScanDevices = true;  
  22.             break// return to the caller before we actually rescan  
  23.         }  
  24.   
  25.         // Report any devices that had last been added/removed.  
  26.         while (mClosingDevices) {  
  27.             Device* device = mClosingDevices;  
  28.             ALOGV("Reporting device closed: id=%d, name=%s\n",  
  29.                  device->id, device->path.string());  
  30.             mClosingDevices = device->next;  
  31.             event->when = now;  
  32.             event->deviceId = device->id == mBuiltInKeyboardId ? BUILT_IN_KEYBOARD_ID : device->id;  
  33.             event->type = DEVICE_REMOVED;  
  34.             event += 1;  
  35.             delete device;  
  36.             mNeedToSendFinishedDeviceScan = true;  
  37.             if (--capacity == 0) {  
  38.                 break;  
  39.             }  
  40.         }  
  41.   
  42.         if (mNeedToScanDevices) {  
  43.             mNeedToScanDevices = false;  
  44.             scanDevicesLocked();  
  45.             mNeedToSendFinishedDeviceScan = true;  
  46.         }  
  47.   
  48.         while (mOpeningDevices != NULL) {  
  49.             Device* device = mOpeningDevices;  
  50.             ALOGV("Reporting device opened: id=%d, name=%s\n",  
  51.                  device->id, device->path.string());  
  52.             mOpeningDevices = device->next;  
  53.             event->when = now;  
  54.             event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;  
  55.             event->type = DEVICE_ADDED;  
  56.             event += 1;  
  57.             mNeedToSendFinishedDeviceScan = true;  
  58.             if (--capacity == 0) {  
  59.                 break;  
  60.             }  
  61.         }  
  62.   
  63.         if (mNeedToSendFinishedDeviceScan) {  
  64.             mNeedToSendFinishedDeviceScan = false;  
  65.             event->when = now;  
  66.             event->type = FINISHED_DEVICE_SCAN;  
  67.             event += 1;  
  68.             if (--capacity == 0) {  
  69.                 break;  
  70.             }  
  71.         }  
  72.   
  73.         // Grab the next input event.  
  74.         bool deviceChanged = false;  
  75.         while (mPendingEventIndex < mPendingEventCount) {  
  76.             const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];  
  77.             if (eventItem.data.u32 == EPOLL_ID_INOTIFY) {  
  78.                 if (eventItem.events & EPOLLIN) {  
  79.                     mPendingINotify = true;  
  80.                 } else {  
  81.                     ALOGW("Received unexpected epoll event 0x%08x for INotify.", eventItem.events);  
  82.                 }  
  83.                 continue;  
  84.             }  
  85.   
  86.             if (eventItem.data.u32 == EPOLL_ID_WAKE) {  
  87.                 if (eventItem.events & EPOLLIN) {  
  88.                     ALOGV("awoken after wake()");  
  89.                     awoken = true;  
  90.                     char buffer[16];  
  91.                     ssize_t nRead;  
  92.                     do {  
  93.                         nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));  
  94.                     } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));  
  95.                 } else {  
  96.                     ALOGW("Received unexpected epoll event 0x%08x for wake read pipe.",  
  97.                             eventItem.events);  
  98.                 }  
  99.                 continue;  
  100.             }  
  101.   
  102.             ssize_t deviceIndex = mDevices.indexOfKey(eventItem.data.u32);  
  103.             if (deviceIndex < 0) {  
  104.                 ALOGW("Received unexpected epoll event 0x%08x for unknown device id %d.",  
  105.                         eventItem.events, eventItem.data.u32);  
  106.                 continue;  
  107.             }  
  108.   
  109.             Device* device = mDevices.valueAt(deviceIndex);  
  110.             if (eventItem.events & EPOLLIN) {  
  111.                 int32_t readSize = read(device->fd, readBuffer,  
  112.                         sizeof(struct input_event) * capacity);  
  113.                 if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {  
  114.                     // Device was removed before INotify noticed.  
  115.                     ALOGW("could not get event, removed? (fd: %d size: %" PRId32  
  116.                             " bufferSize: %zu capacity: %zu errno: %d)\n",  
  117.                             device->fd, readSize, bufferSize, capacity, errno);  
  118.                     deviceChanged = true;  
  119.                     closeDeviceLocked(device);  
  120.                 } else if (readSize < 0) {  
  121.                     if (errno != EAGAIN && errno != EINTR) {  
  122.                         ALOGW("could not get event (errno=%d)", errno);  
  123.                     }  
  124.                 } else if ((readSize % sizeof(struct input_event)) != 0) {  
  125.                     ALOGE("could not get event (wrong size: %d)", readSize);  
  126.                 } else {  
  127.                     int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;  
  128.   
  129.                     size_t count = size_t(readSize) / sizeof(struct input_event);  
  130.                     for (size_t i = 0; i < count; i++) {  
  131.                         struct input_event& iev = readBuffer[i];  
  132.                         ALOGV("%s got: time=%d.%06d, type=%d, code=%d, value=%d",  
  133.                                 device->path.string(),  
  134.                                 (int) iev.time.tv_sec, (int) iev.time.tv_usec,  
  135.                                 iev.type, iev.code, iev.value);  
  136.   
  137.                         // Some input devices may have a better concept of the time  
  138.                         // when an input event was actually generated than the kernel  
  139.                         // which simply timestamps all events on entry to evdev.  
  140.                         // This is a custom Android extension of the input protocol  
  141.                         // mainly intended for use with uinput based device drivers.  
  142.                         if (iev.type == EV_MSC) {  
  143.                             if (iev.code == MSC_ANDROID_TIME_SEC) {  
  144.                                 device->timestampOverrideSec = iev.value;  
  145.                                 continue;  
  146.                             } else if (iev.code == MSC_ANDROID_TIME_USEC) {  
  147.                                 device->timestampOverrideUsec = iev.value;  
  148.                                 continue;  
  149.                             }  
  150.                         }  
  151.                         if (device->timestampOverrideSec || device->timestampOverrideUsec) {  
  152.                             iev.time.tv_sec = device->timestampOverrideSec;  
  153.                             iev.time.tv_usec = device->timestampOverrideUsec;  
  154.                             if (iev.type == EV_SYN && iev.code == SYN_REPORT) {  
  155.                                 device->timestampOverrideSec = 0;  
  156.                                 device->timestampOverrideUsec = 0;  
  157.                             }  
  158.                             ALOGV("applied override time %d.%06d",  
  159.                                     int(iev.time.tv_sec), int(iev.time.tv_usec));  
  160.                         }  
  161.   
  162. #ifdef HAVE_POSIX_CLOCKS  
  163.                         // Use the time specified in the event instead of the current time  
  164.                         // so that downstream code can get more accurate estimates of  
  165.                         // event dispatch latency from the time the event is enqueued onto  
  166.                         // the evdev client buffer.  
  167.                         //  
  168.                         // The event's timestamp fortuitously uses the same monotonic clock  
  169.                         // time base as the rest of Android.  The kernel event device driver  
  170.                         // (drivers/input/evdev.c) obtains timestamps using ktime_get_ts().  
  171.                         // The systemTime(SYSTEM_TIME_MONOTONIC) function we use everywhere  
  172.                         // calls clock_gettime(CLOCK_MONOTONIC) which is implemented as a  
  173.                         // system call that also queries ktime_get_ts().  
  174.                         event->when = nsecs_t(iev.time.tv_sec) * 1000000000LL  
  175.                                 + nsecs_t(iev.time.tv_usec) * 1000LL;  
  176.                         ALOGV("event time %" PRId64 ", now %" PRId64, event->when, now);  
  177.   
  178.                         // Bug 7291243: Add a guard in case the kernel generates timestamps  
  179.                         // that appear to be far into the future because they were generated  
  180.                         // using the wrong clock source.  
  181.                         //  
  182.                         // This can happen because when the input device is initially opened  
  183.                         // it has a default clock source of CLOCK_REALTIME.  Any input events  
  184.                         // enqueued right after the device is opened will have timestamps  
  185.                         // generated using CLOCK_REALTIME.  We later set the clock source  
  186.                         // to CLOCK_MONOTONIC but it is already too late.  
  187.                         //  
  188.                         // Invalid input event timestamps can result in ANRs, crashes and  
  189.                         // and other issues that are hard to track down.  We must not let them  
  190.                         // propagate through the system.  
  191.                         //  
  192.                         // Log a warning so that we notice the problem and recover gracefully.  
  193.                         if (event->when >= now + 10 * 1000000000LL) {  
  194.                             // Double-check.  Time may have moved on.  
  195.                             nsecs_t time = systemTime(SYSTEM_TIME_MONOTONIC);  
  196.                             if (event->when > time) {  
  197.                                 ALOGW("An input event from %s has a timestamp that appears to "  
  198.                                         "have been generated using the wrong clock source "  
  199.                                         "(expected CLOCK_MONOTONIC): "  
  200.                                         "event time %" PRId64 ", current time %" PRId64  
  201.                                         ", call time %" PRId64 ".  "  
  202.                                         "Using current time instead.",  
  203.                                         device->path.string(), event->when, time, now);  
  204.                                 event->when = time;  
  205.                             } else {  
  206.                                 ALOGV("Event time is ok but failed the fast path and required "  
  207.                                         "an extra call to systemTime: "  
  208.                                         "event time %" PRId64 ", current time %" PRId64  
  209.                                         ", call time %" PRId64 ".",  
  210.                                         event->when, time, now);  
  211.                             }  
  212.                         }  
  213. #else  
  214.                         event->when = now;  
  215. #endif  
  216.                         event->deviceId = deviceId;  
  217.                         event->type = iev.type;  
  218.                         event->code = iev.code;  
  219.                         event->value = iev.value;  
  220.                         event += 1;  
  221.                         capacity -= 1;  
  222.                     }  
  223.                     if (capacity == 0) {  
  224.                         // The result buffer is full.  Reset the pending event index  
  225.                         // so we will try to read the device again on the next iteration.  
  226.                         mPendingEventIndex -= 1;  
  227.                         break;  
  228.                     }  
  229.                 }  
  230.             } else if (eventItem.events & EPOLLHUP) {  
  231.                 ALOGI("Removing device %s due to epoll hang-up event.",  
  232.                         device->identifier.name.string());  
  233.                 deviceChanged = true;  
  234.                 closeDeviceLocked(device);  
  235.             } else {  
  236.                 ALOGW("Received unexpected epoll event 0x%08x for device %s.",  
  237.                         eventItem.events, device->identifier.name.string());  
  238.             }  
  239.         }  
  240.   
  241.         // readNotify() will modify the list of devices so this must be done after  
  242.         // processing all other events to ensure that we read all remaining events  
  243.         // before closing the devices.  
  244.         if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) {  
  245.             mPendingINotify = false;  
  246.             readNotifyLocked();  
  247.             deviceChanged = true;  
  248.         }  
  249.   
  250.         // Report added or removed devices immediately.  
  251.         if (deviceChanged) {  
  252.             continue;  
  253.         }  
  254.   
  255.         // Return now if we have collected any events or if we were explicitly awoken.  
  256.         if (event != buffer || awoken) {  
  257.             break;  
  258.         }  
  259.   
  260.         // Poll for events.  Mind the wake lock dance!  
  261.         // We hold a wake lock at all times except during epoll_wait().  This works due to some  
  262.         // subtle choreography.  When a device driver has pending (unread) events, it acquires  
  263.         // a kernel wake lock.  However, once the last pending event has been read, the device  
  264.         // driver will release the kernel wake lock.  To prevent the system from going to sleep  
  265.         // when this happens, the EventHub holds onto its own user wake lock while the client  
  266.         // is processing events.  Thus the system can only sleep if there are no events  
  267.         // pending or currently being processed.  
  268.         //  
  269.         // The timeout is advisory only.  If the device is asleep, it will not wake just to  
  270.         // service the timeout.  
  271.         mPendingEventIndex = 0;  
  272.   
  273.         mLock.unlock(); // release lock before poll, must be before release_wake_lock  
  274.         release_wake_lock(WAKE_LOCK_ID);  
  275.   
  276.         int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);  
  277.   
  278.         acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);  
  279.         mLock.lock(); // reacquire lock after poll, must be after acquire_wake_lock  
  280.   
  281.         if (pollResult == 0) {  
  282.             // Timed out.  
  283.             mPendingEventCount = 0;  
  284.             break;  
  285.         }  
  286.   
  287.         if (pollResult < 0) {  
  288.             // An error occurred.  
  289.             mPendingEventCount = 0;  
  290.   
  291.             // Sleep after errors to avoid locking up the system.  
  292.             // Hopefully the error is transient.  
  293.             if (errno != EINTR) {  
  294.                 ALOGW("poll failed (errno=%d)\n", errno);  
  295.                 usleep(100000);  
  296.             }  
  297.         } else {  
  298.             // Some events occurred.  
  299.             mPendingEventCount = size_t(pollResult);  
  300.         }  
  301.     }  
  302.   
  303.     // All done, return the number of events we read.  
  304.     return event - buffer;  
  305. }  
这个函数很大,很长,挺复杂的,但它的确就是直接读写/dev/input/设备文件节点的函数:
[html] view plain copy
print?
  1. int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);  

这里监听有没有事件到来,没有就休眠,有的话就返回......
至此,文章一开始提出的问题就得到了解决,问题是:”哪个线程一直监听着事件的到来,是怎么监听的?“
答案显然是:InputReaderThread线程一直监听着事件的到来,最终使用epoll_wait监听这些/dev/input/下的文件节点的文件描述符。
得到了这个答案后,再结合上一节得到的结论,事件的输入监听部分基本框架弄明白了,那下一步,就是继续分析事件的分发,毕竟事件最终要分发道view或着activity这些ui组件上。
阅读全文
0 0
原创粉丝点击