English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

تحليل ميكانيكية نظام الرسائل لـ Android6.0

الرسائل يتم تخزينها في قائمة رسائل، والسطر المتكرر يحيط بقائمة الرسائل ويغلق في حلقة لا نهائية حتى يخرج السطر. إذا كانت هناك رسائل في القائمة، فإن سطر الرسائل المتكرر سيأخذها ويوزعها على المعالج المطلوب لمعالجتها؛ إذا لم يكن هناك رسائل في القائمة، فإن سطر الرسائل المتكرر سيغلق في حالة إنتظار راحة، بانتظار وصول الرسالة التالية. عند كتابة تطبيق أندرويد، عندما تكون المهام التي تنفذها برنامج متعبة، فإن الطريقة العادية التي نستخدمها لتجنب حظر سطر UI الرئيسي وحدوث ANR هي إنشاء سطر فرعي لإنجاز المهمة المحددة. عند إنشاء سطر فرعي، هناك خياران، واحد هو إنشاء سطر فرعي بلا حلقة رسائل من خلال إنشاء كائن Thread؛ والآخر هو إنشاء سطر فرعي يحتوي على حلقة رسائل، وإنشاء سطر فرعي يحتوي على حلقة رسائل بسبب طريقتين، الأولى هي استخدام كائن HandlerThread الذي قدمه لنا أندرويد لإنشاء كائن سطر فرعي يحتوي على حلقة رسائل مباشرة، والثانية هي بدء حلقة رسائل باستخدام الطريقة التالية داخل طريقة run للسطر الفرعي: 

أولاً: استخدام آلية الرسائل 

عادة تكون الرسائل تتكون من نواة رسائل ومعالج، لذا سنجد في PowerManagerService معالج رسائل:        

 mHandlerThread = new ServiceThread(TAG,
        Process.THREAD_PRIORITY_DISPLAY, false /*allowIo*/);
    mHandlerThread.start();
    mHandler = new PowerManagerHandler(mHandlerThread.getLooper()); 

هناك ServiceThread هو HandlerThread، عند إنشاء معالج، يجب نقل looper لـ HandlerThread، وإلا سيكون looper الحالي هو looper الافتراضي. 

وكل معالج، بشكل عام كالتالي:

   private final class PowerManagerHandler extends Handler {
    public PowerManagerHandler(Looper looper) {
      super(looper, null, true /*async*/);
    }
    @Override
    public void تعامل مع رسالة (Message msg) {
      الانتقال (msg.what) {
        حالة MSG_USER_ACTIVITY_TIMEOUT:
          تعامل مع تعامل المستخدم مع زمن الصمت();
          break;
        case MSG_SANDMAN:
          handleSandman();
          break;
        case MSG_SCREEN_BRIGHTNESS_BOOST_TIMEOUT:
          handleScreenBrightnessBoostTimeout();
          break;
        case MSG_CHECK_WAKE_LOCK_ACQUIRE_TIMEOUT:
          checkWakeLockAquireTooLong();
          Message m = mHandler.obtainMessage(MSG_CHECK_WAKE_LOCK_ACQUIRE_TIMEOUT);
          m.setAsynchronous(true);
          mHandler.sendMessageDelayed(m, WAKE_LOCK_ACQUIRE_TOO_LONG_TIMEOUT);
          break;
      }
    }
  }

مبدأ ميكانيكية الرسائل
دعنا نبدأ بفهم دالة run الرئيسية لـ HandlerThread: 

 public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    synchronized (this) {
      mLooper = Looper.myLooper(); // بعد ال赋ة notifyall، حيث أن getLooper يعود بـ mLooper
      notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    Looper.loop();
    mTid = -1;
  }

دعنا نرى أيضًا دالة prepare الخاصة بلوبر، حيث تم إنشاء كائن Looper جديد ويتم وضعها في متغيرات المحلية للخط

public static void prepare() {
    prepare(true);
  }
  private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
      throw new RuntimeException("لا يمكن إنشاء Looper واحد فقط لكل thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
  } 

في بناء Looper تم إنشاء MessageQueue

   private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
  } 

نحن ننظر مرة أخرى إلى بناء MessageQueue، حيث أن nativeInit هو طريقة native، ويتم حفظ القيمة العائدة في mPtr بشكل واضح باستخدام متغير النقاط long كدالة للإشارة

MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
  } 

في الدالة المحلية يتم إنشاء مكون NativeMessageQueue ويعاد رجوع مؤشر المتغير الديناميكي.

 static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
  NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
  if (!nativeMessageQueue) {
    jniThrowRuntimeException(env, "Unable to allocate native queue");
    return 0;
  }
  nativeMessageQueue->incStrong(env);
  return reinterpret_cast<jlong>(nativeMessageQueue);
} 

مكون بناء NativeMessageQueue هو للحصول على mLooper، وإذا لم يكن موجودًا فيجب إنشاء Looper جديد 

NativeMessageQueue::NativeMessageQueue() :
    mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
  mLooper = Looper::getForThread();
  if (mLooper == NULL) {
    mLooper = new Looper(false);
    Looper::setForThread(mLooper);
  }
}

ثم ننظر إلى مكون بناء Looper، حيث يتم استدعاء eventfd لإنشاء fd، وهو يستخدم بشكل رئيسي للتواصل بين العمليات أو الأطراف المختلفة، يمكننا النظر إلى هذا المقال شرح eventfd

 Looper::Looper(bool allowNonCallbacks) :
    mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
    mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),}
    mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
  mWakeEventFd = eventfd(0, EFD_NONBLOCK);
  LOG_ALWAYS_FATAL_IF(mWakeEventFd < 0, "لم يتم إنشاء fd للحدث الاستيقاظ. errno=%d", errno);
  AutoMutex _l(mLock);
  rebuildEpollLocked();
}

2.1 إنشاء epoll في الطبقة c 

دعنا نرى الآن وظيفة rebuildEpollLocked، حيث تم إنشاء epoll وإضافة mWakeEventFd إلى epoll، بالإضافة إلى إضافة fd من mRequests إلى epoll

 void Looper::rebuildEpollLocked() {
  // إغلاق مثيل epoll القديم إذا كان لدينا واحد.
  إذا (mEpollFd >= 0) {
#if DEBUG_CALLBACKS
    ALOGD("%p ~ rebuildEpollLocked - إعادة بناء مجموعة epoll", هذا);
#endif
    close(mEpollFd);
  }
  // تخصيص مثيل جديد لإصدار epoll وتسجيل أنبوب الاستيقاظ.
  mEpollFd = epoll_create(EPOLL_SIZE_HINT);
  LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "لم يتم إنشاء مثيل epoll. errno=%d", errno);
  struct epoll_event eventItem;
  memset(&eventItem, 0, sizeof(epoll_event)); // تقديم أعضاء غير المستخدمين من حقل البيانات كتحالف صفر
  eventItem.events = EPOLLIN;
  eventItem.data.fd = mWakeEventFd;
  نتيجة = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, &eventItem);
  LOG_ALWAYS_FATAL_IF(result != 0, "لم يتم إضافة fd للحدث الاستيقاظ إلى مثيل epoll. errno=%d",
      errno);
  for (size_t i = 0; i < mRequests.size(); i++) {
    const Request& request = mRequests.valueAt(i);
    struct epoll_event eventItem;
    request.initEventItem(&eventItem);
    int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, request.fd, & eventItem);
    if (epollResult < 0) {
      ALOGE("خطأ في إضافة أحداث epoll للfd %d أثناء إعادة بناء مجموعة epoll، errno=%d",
          request.fd, errno);
    }
  }
} 

نحن نعود إلى دالة run في HandlerThread، ونستمر في تحليل دالة loop في Looper

public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    synchronized (this) {
      mLooper = Looper.myLooper();
      notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    Looper.loop();
    mTid = -1;
  } 

لننظر في دالة loop في Looper:

public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
      throw new RuntimeException("لا يوجد Looper; لم يتم استدعاء Looper.prepare() في هذا السطر.");
    }
    final MessageQueue queue = me.mQueue; // تحصل على Looper.mQueue
    // تأكد من هوية هذا السطر هي من عملية المحلية;
    // ومراقبة ما هو حقًا هو ذلك بطاقة هوية الهوية.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
    للدوران {
      Message msg = queue.next(); // قد يوقف هذا الدالة، والوقف يحدث بشكل رئيسي بسبب epoll_wait
      إذا (msg == null) {
        // لا يوجد رسالة يعني أن قائمة الرسائل تترك.
        return;
      }
      // يجب أن يكون هذا في متغير محلي، في حالة إعداد logger بواسطة أحداث واجهة المستخدم
      Printer logging = me.mLogging; // الطباعة الخاصة بي
      إذا (logging != null) {
        logging.println(">>>>> إرسال إلى " + msg.target + " " +
            msg.callback + ": " + msg.what);
      }
      msg.target.dispatchMessage(msg);
      إذا (logging != null) {
        logging.println("<<<<< انتهاء إلى " + msg.target + " " + msg.callback);
      }
      // تأكد أن خلال عملية إرسال
      // هوية السطر لم تُتلف.
      final long newIdent = Binder.clearCallingIdentity();
      إذا (ident != newIdent) {
        Log.wtf(TAG, "Thread identity changed from 0x"
            + Long.toHexString(ident) + " to 0x"
            + Long.toHexString(newIdent) + " while dispatching to "
            + msg.target.getClass().getName() + " "
            + msg.callback + " what=" + msg.what);
      }
      msg.recycleUnchecked();
    }
  }

دالة next في فئة MessageQueue تستدعي دالة nativePollOnce المحلية، ثم يتم استخراج Message من حقيبة الرسائل

Message next() {
    // عودة هنا إذا كان حلقة الرسائل قد خلت وأُزيلت.
    // يمكن أن يحدث هذا إذا حاول التطبيق إعادة بدء looper بعد الخروج
    // هذا غير مدعوم.
    final long ptr = mPtr; // السهم المحفوظ مسبقًا
    إذا (ptr == 0) {
      return null;
    }
    int pendingIdleHandlerCount = -1; // -1 فقط أثناء الدورة الأولى
    int nextPollTimeoutMillis = 0;
    للدوران {
      إذا (nextPollTimeoutMillis != 0) {
        Binder.flushPendingCommands();
      }
      nativePollOnce(ptr, nextPollTimeoutMillis); 

نحن نرى الآن الدالة المحلية nativePollOnce، نقوم بتحويل السهم السابق إلى NativeMessageQueue، ثم نستدعي دالة pollOnce الخاصة بها

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
    jlong ptr, jint timeoutMillis) {
  NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
  nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

2.2 حظر epoll_wait في الطبقة c 

func pollOnce، العادة ما لا يحتوي هذا func على while، بل يتم التعامل فقط مع حالة indent > 0، والذي لا يحدث عادةً، لذا يمكننا النظر في func pollInner مباشرة

 int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
  int result = 0;
  للدوران {
    while (mResponseIndex < mResponses.size()) {
      const Response& response = mResponses.itemAt(mResponseIndex++);
      int ident = response.request.ident;
      إذا كان ident >= 0 {
        int fd = response.request.fd;
        int events = response.events;
        void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE
        ALOGD("%p ~ pollOnce - returning signalled identifier %d: "
            "fd=%d, events=0x%x, data=%p",
            this, ident, fd, events, data);
#endif
        إذا كان outFd غير صحيح، ف*outFd = fd;
        إذا كان outEvents غير صحيح، ف*outEvents = events;
        إذا كان outData غير صحيح، ف*outData = data;
        return ident;
      }
    }
    إذا كان result غير صحيح {
#if DEBUG_POLL_AND_WAKE
      ALOGD("%p ~ pollOnce - returning result %d", this, result);
#endif
      إذا كان outFd غير صحيح، ف*outFd = 0;
      إذا كان outEvents غير صحيح، ف*outEvents = 0;
      إذا كان outData غير صحيح، ف*outData = NULL;
      return result;
    }
    result = pollInner(timeoutMillis);
  }
} 

func pollInner يُستدعى بشكل رئيسي لتحقيق epoll_wait بالحظر، وسيحسب الطبقة java مدة الحظر في كل مرة وتنقلها إلى الطبقة c، وينتظر حتى يأتي حدث mWakeEventFd أو حتى يكون fd المضاف مسبقًا له حدثًا، قبل أن يعود epoll_wait. 

int Looper::pollInner(int timeoutMillis) {
#if DEBUG_POLL_AND_WAKE
  ALOGD("%p ~ pollOnce - الإنتظار: timeoutMillis=%d", this, timeoutMillis);
#endif
  // يتم تعديل وقت الإنتظار بناءً على وقت الرسالة التالية المقرر.
  if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {
    nsecs_t الآن = وقت_النظام(SYSTEM_TIME_MONOTONIC);
    int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);
    if (messageTimeoutMillis >= 0
        && (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {
      timeoutMillis = messageTimeoutMillis;
    }
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ pollOnce - الرسالة التالية في %" PRId64 "ns، وقت الإنتظار المعدل: timeoutMillis=%d",
        this, mNextMessageUptime - now, timeoutMillis);
#endif
  }
  // استطلاع.
  int result = POLL_WAKE;
  mResponses.clear(); // يتم تنظيف mResponses
  mResponseIndex = 0;
  // نحن على وشك أن نكون مُتركةً.
  mPolling = true;
  struct epoll_event eventItems[EPOLL_MAX_EVENTS];
  int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis); // epoll_wait يُحتجز هنا في الشبكة الأساسية، ووقت الحجز هذا يتم من خلال طبقة java
  // لا يعد الآن مُتركةً.
  mPolling = false;
  // اكتساب القفل.
  mLock.lock();
  // إعادة بناء مجموعة epoll إذا لزم الأمر.
  if (mEpollRebuildRequired) {
    mEpollRebuildRequired = false;
    rebuildEpollLocked();
    goto Done;
  }
  // التحقق من خطأ الاستطلاع.
  if (eventCount < 0) {
    if (errno == EINTR) {
      goto Done;
    }
    ALOGW("فشل الاستطلاع بخطأ غير متوقع، errno=%d", errno);
    result = POLL_ERROR;
    goto Done;
  }
  // التحقق من انتهاء زمن الاستطلاع.
  if (eventCount == 0) {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ pollOnce - timeout", this);
#endif
    result = POLL_TIMEOUT;
    goto Done;
  }
  // معالجة جميع الأحداث.
#if DEBUG_POLL_AND_WAKE
  ALOGD("%p ~ pollOnce - معالجة أحداث من %d fds", this, eventCount);
#endif
  for (int i = 0; i < eventCount; i++) {
    int fd = eventItems[i].data.fd;
    uint32_t epollEvents = eventItems[i].events;
    if (fd == mWakeEventFd) { // حدث إعلام الت唤醒
      if (epollEvents & EPOLLIN) {
        awoken();
      } else {
        ALOGW("تجاهل أحداث epoll غير المتوقعة 0x%x على حدث الت��ية fd.", epollEvents);
      }
    } else {
      ssize_t requestIndex = mRequests.indexOfKey(fd); // قبل إضافة fd إلى الحدث
      ssize_t requestIndex = mRequests.indexOfKey(fd);
        int events = 0;
        إذا (requestIndex >= 0) {
        إذا (epollEvents & EPOLLIN) events |= EVENT_INPUT;
        إذا (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT;
        إذا (epollEvents & EPOLLERR) events |= EVENT_ERROR;
        pushResponse(events, mRequests.valueAt(requestIndex)); // وضع في mResponses
      } else {
        إدراج استجابة (events، mRequests.valueAt(requestIndex));
            ALOGW("تجاهل أحداث epoll غير المتوقعة 0x%x على fd %d التي هي "
      }
    }
  }
تمام: ;
  // تنفيذ استدعاء مكالمات الرسائل المؤجلة.
  mNextMessageUptime = LLONG_MAX;
  while (mMessageEnvelopes.size() != 0) {// هذه القسمة تتعلق بمسائل الطبقة c، مسائل الطبقة java تتم إدارتها ذاتياً
    nsecs_t الآن = وقت_النظام(SYSTEM_TIME_MONOTONIC);
    const رسالة_المحاطة& رسالة_المحاطة = مجموعة_الرسائل_المحاطة.العدد_المسجل(0);
    إذا (رسالة_المحاطة.وقت_الإطلاق <= الآن) {
      // قم بإزالة المحاطة من القائمة.
      // نحافظ على مرجع قوي للمستخدم حتى دعوة handleMessage
      // ينتهي. ثم نضعه بعيدًا حتى يتمكن المستخدم من الحذف *قبل*
      // نعيد الحصول على قفلنا.
      { // الحصول على المستخدم
        sp<مستخدم_معالجة_الرسائل> مستخدم = رسالة_المحاطة.المستخدم;
        رسالة رسالة = رسالة_المحاطة.الرسالة;
        مجموعة_الرسائل_المحاطة.إزالة_العدد(0);
        مستخدم_إرسال_الرسالة = صحيح;
        مقفل_m.إطلاق();
#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
        ALOGD("%p ~ pollOnce - إرسال رسالة: مستخدم=%p, what=%d",
            هذا، مستخدم.الحصول_على_المستخدم(، رسالة.what);
#endif
        مستخدم->تعامل_الرسالة(الرسالة);
      } // release handler
      mLock.lock();
      mSendingMessage = false;
      result = POLL_CALLBACK;
    } else {
      // يحدد الرسالة الأخيرة المتبقية في رأس الخط الرمز الزمني للإيقاظ التالي.
      mNextMessageUptime = messageEnvelope.uptime;
      break;
    }
  }
  // أطلق القفل.
  مقفل_m.إطلاق();
  // أطلق جميع مكالمات العودة للإجابة.
  لـ for (size_t i = 0; i < mResponses.size(); i++) {// هذه هي معالجة أحداث addFd السابقة، وهي تمرير mResponses ثم إطلاق مكالمة العودة
    Response& response = mResponses.editItemAt(i);
    إذا (response.request.ident == POLL_CALLBACK) {
      int fd = response.request.fd;
      int events = response.events;
      void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
      ALOGD("%p ~ pollOnce - استدعاء استدعاء الحدث fd %p: fd=%d، events=0x%x، data=%p"
          هذا، استدعاء response.request.callback.get()، fd، events، data);
#endif
      // اطرح الرد. يرجى ملاحظة أن ملف التعريف قد يُغلق بواسطة
      // الرد (وقد يستخدم مرة أخرى) قبل أن يعود الدالة.
      // يجب أن نكون حذرين بعض الشيء عند إزالة ملف التعريف بعد ذلك.
      int callbackResult = response.request.callback->handleEvent(fd, events, data);
      if (callbackResult == 0) {
        removeFd(fd, response.request.seq);
      }
      // قم بإزالة مرجع الرد في هيكل الرد بشكل سريع لأننا
      // لن يتم إزالة محتوى vector الرد حتى التحقق التالي.
      response.request.callback.clear();
      result = POLL_CALLBACK;
    }
  }
  return result;
} 

استمر في تحليل وظيفة loop في Looper، يمكنك إضافة طباعة خاصة بك لتتبع الكود، قبل ذلك قمت بتشغيل dispatchMessage للرسالة target لتحديد الرسالة

     للدوران {
      Message msg = queue.next(); // قد يتحكم
      إذا (msg == null) {
        // لا يوجد رسالة يعني أن قائمة الرسائل تترك.
        return;
      }
      // يجب أن يكون هذا في متغير محلي، في حالة إعداد logger بواسطة أحداث واجهة المستخدم
      Printer logging = me.mLogging;//هذا هو الطباعة الخاصة بي
      إذا (logging != null) {
        logging.println(">>>>> إرسال إلى " + msg.target + " " +
            msg.callback + ": " + msg.what);
      }
      msg.target.dispatchMessage(msg);
      إذا (logging != null) {
        logging.println("<<<<< انتهاء إلى " + msg.target + " " + msg.callback);
      }
      // تأكد أن خلال عملية إرسال
      // هوية السطر لم تُتلف.
      final long newIdent = Binder.clearCallingIdentity();
      إذا (ident != newIdent) {
        Log.wtf(TAG, "Thread identity changed from 0x"
            + Long.toHexString(ident) + " to 0x"
            + Long.toHexString(newIdent) + " while dispatching to "
            + msg.target.getClass().getName() + " "
            + msg.callback + " what=" + msg.what);
      }
      msg.recycleUnchecked();
    }
  }

2.3 增加调试打印 

我们先来看自己添加打印,可以通过Lopper的setMessageLogging函数来打印

public void setMessageLogging(@Nullable Printer printer) {
    mLogging = printer;
  } 
Printer就是一个interface
public interface Printer {
  /**
   * Write a line of text to the output. There is no need to terminate
   * the given string with a newline.
   */
  void println(String x);
}

2.4 java层消息分发处理 

再来看消息的分发,先是调用Handler的obtainMessage函数               

 Message msg = mHandler.obtainMessage(MSG_CHECK_WAKE_LOCK_ACQUIRE_TIMEOUT);
 msg.setAsynchronous(true);
 mHandler.sendMessageDelayed(msg, WAKE_LOCK_ACQUIRE_TOO_LONG_TIMEOUT); 

先看obtainMessage调用了Message的obtain函数

public final Message obtainMessage(int what)
  {
    return Message.obtain(this, what);
  } 

Message的obtain函数就是新建一个Message,然后其target就是设置成其Handler

public static Message obtain(Handler h, int what) {
    Message m = obtain();// إنشاء رسالة جديدة
    m.target = h;
    m.what = what;
    return m;
  }

دعنا نربط بين كيفية توزيع الرسائل من قبلنا 

msg.target.dispatchMessage(msg); في النهاية يتم استدعاء دالة dispatchMessage في Handler، وفي النهاية، سيتم معالجة الرسائل بناءً على الحالات المختلفة.

   public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
      handleCallback(msg);//هذا عندما يتم إرسال الرسالة بشكل post مع Runnable
    } else {
      if (mCallback != null) {//هذا عندما يتم إرسال المعالج مع المعلومات المراد معالجتها
        if (mCallback.handleMessage(msg)) {
          return;
        }
      }
      handleMessage(msg);//في النهاية يتم معالجة الرسالة في handleMessage المكتوب من قبل المستخدم
    }
  }

2.3 إرسال الرسائل في طبقة Java 

دعنا نرى كيفية إرسال الرسائل في طبقة Java أيضًا، حيث يتم دائمًا استدعاء دالة sendMessage أو post وما إلى ذلك، وستستدعي هذه الدالة في النهاية

   public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
      RuntimeException e = new RuntimeException(
          this + " sendMessageAtTime() called with no mQueue\
      Log.w("Looper", e.getMessage(), e);
      return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
  } 

نحن سنتحدث عن كيفية إرسال الرسائل في طبقة Java، حيث يتم دائمًا استدعاء دالة enqueueMessage

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
      msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
  } 

في نهاية الأمر، في enqueueMessage، يتم إضافة الرسالة إلى قائمة الرسائل، ثم يتم استدعاء دالة nativeWake في الطبقة c إذا لزم الأمر

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
      throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) {
      throw new IllegalStateException(msg + " This message is already in use.");
    }
    synchronized (this) {
      if (mQuitting) {
        IllegalStateException e = new IllegalStateException(
            msg.target + " sending message to a Handler on a dead thread");
        Log.w(TAG, e.getMessage(), e);
        msg.recycle();
        return false;
      }
      msg.markInUse();
      msg.when = when;
      Message p = mMessages;
      boolean needWake;
      if (p == null || when == 0 || when < p.when) {
        // New head, wake up the event queue if blocked.
        msg.next = p;
        mMessages = msg;
        needWake = mBlocked;
      } else {
        // Inserted within the middle of the queue. Usually we don't have to wake
        // up the event queue unless there is a barrier at the head of the queue
        // و الرسالة هي أحدث رسالة متزامنة في الصف.
        needWake = mBlocked && p.target == null && msg.isAsynchronous();
        Message prev;
        للدوران {
          prev = p;
          p = p.next;
          إذا (p == null || when < p.when) {
            break;
          }
          إذا (needWake && p.isAsynchronous()) {
            needWake = false;
          }
        }
        msg.next = p; // مجردة: p == prev.next
        prev.next = msg;
      }
      // يمكننا افتراض أن mPtr != 0 لأن mQuitting هو خطأ.
      if (needWake) {
        nativeWake(mPtr);
      }
    }
    return true;
  } 

لنلقي نظرة على هذه الطريقة الناتجة، وهي أيضًا تستدعي وظيفة wake من صنف Looper

 static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
  NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
  nativeMessageQueue->wake();
}
void NativeMessageQueue::wake() {
  mLooper->wake();
} 

وظيفة wake من صنف Looper، تضيف بعض المحتويات إلى mWakeEventfd، هذا fd يهدف فقط إلى إعلام، يشبه pipe، ويجب أن يقوم epoll_wait بالاستيقاظ، حتى لا يبقى السطر معطلًا، يرسل الرسائل من الطبقة c أولاً، ثم يعالج أحداث addFd السابقة، ثم يعالج الرسائل من الطبقة java. 

void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
  ALOGD("%p ~ wake", this);
#endif
  uint64_t inc = 1;
  ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
  إذا (nWrite != sizeof(uint64_t)) {
    if (errno != EAGAIN) {
      ALOGW("Could not write wake signal, errno=%d", errno);
    }
  }
}

2.4 إرسال رسائل الطبقة c 

يمكن أيضًا إرسال رسائل في الطبقة c، حيث يتم استدعاء وظيفة sendMessageAtTime في Looper، وهي تحتوي على معامل one handler ك回调، ونضع الرسالة في mMessageEnvelopes.

 void Looper::sendMessageAtTime(nsecs_t uptime, const sp<MessageHandler>& handler,
    const Message& message) {
#if DEBUG_CALLBACKS
  ALOGD("%p ~ sendMessageAtTime - uptime=%" PRId64 ", handler=%p, what=%d",
      this, uptime, handler.get(), message.what);
#endif
  size_t i = 0;
  { // acquire lock
    AutoMutex _l(mLock);
    size_t messageCount = mMessageEnvelopes.size();
    while (i < messageCount && uptime >= mMessageEnvelopes.itemAt(i).uptime) {
      i += 1;
    }
    MessageEnvelope messageEnvelope(uptime, handler, message);
    mMessageEnvelopes.insertAt(messageEnvelope, i, 1);
    // تحسين: إذا كان Looper يرسل حاليًا رسالة، فإننا يمكننا تجاوز
    // استدعاء wake() لأن الشيء التالي الذي سيقوم به Looper بعد معالجة
    // messages لتحديد متى يجب أن يكون وقت الاستيقاظ التالي. في الواقع، يفعل ذلك
    // لا يهم حتى ما إذا كان هذا الكود يعمل على نواة Looper.
    إذا (مستخدم_إرسال_الرسالة) {
      return;
    }
  }
  // استيقظ دائرة الاستطلاع فقط عند وضع رسالة جديدة في رأس الحقل.
  إذا (i == 0) {
    استيقظ();
  }
} 

عندما يكون في pollOnce، بعد epoll_wait، سيتم استعراض الرسائل في مجموعة_الرسائل_المحاطة، ثم استدعاء دالة handleMessage للمستخدم

   while (مجموعة_الرسائل_المحاطة.الحجم() != 0) {
    nsecs_t الآن = وقت_النظام(SYSTEM_TIME_MONOTONIC);
    const رسالة_المحاطة& رسالة_المحاطة = مجموعة_الرسائل_المحاطة.العدد_المسجل(0);
    إذا (رسالة_المحاطة.وقت_الإطلاق <= الآن) {
      // قم بإزالة المحاطة من القائمة.
      // نحافظ على مرجع قوي للمستخدم حتى دعوة handleMessage
      // ينتهي. ثم نضعه بعيدًا حتى يتمكن المستخدم من الحذف *قبل*
      // نعيد الحصول على قفلنا.
      { // الحصول على المستخدم
        sp<مستخدم_معالجة_الرسائل> مستخدم = رسالة_المحاطة.المستخدم;
        رسالة رسالة = رسالة_المحاطة.الرسالة;
        مجموعة_الرسائل_المحاطة.إزالة_العدد(0);
        مستخدم_إرسال_الرسالة = صحيح;
        مقفل_m.إطلاق();
#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
        ALOGD("%p ~ pollOnce - إرسال رسالة: مستخدم=%p, what=%d",
            هذا، مستخدم.الحصول_على_المستخدم(، رسالة.what);
#endif
        مستخدم->تعامل_الرسالة(الرسالة);
      } // release handler
      mLock.lock();
      mSendingMessage = false;
      result = POLL_CALLBACK;
    } else {
      // يحدد الرسالة الأخيرة المتبقية في رأس الخط الرمز الزمني للإيقاظ التالي.
      mNextMessageUptime = messageEnvelope.uptime;
      break;
    }
  } 

هناك ملف Looper_test.cpp، حيث يتم شرح العديد من طرق استخدام Looper، لننظر في

   sp<StubMessageHandler> handler = new StubMessageHandler();
  mLooper->sendMessageAtTime(now + ms2ns(100), handler, Message(MSG_TEST1)); 
يجب على StubMessageHandler تنفيذ طريقة handleMessage عند توريثها من MessageHandler
class StubMessageHandler : public MessageHandler {
public:
  Vector<Message> messages;
  virtual void handleMessage(const Message& message) {
    messages.push(message);
  }
}; 

نحن ننظر أيضًا في كلا Message و MessageHandler

 struct Message {
  Message() : what(0) { }
  Message(int what) : what(what) { }
  /* نوع الرسالة. (التفسير يترك للمعالج) */
  int what;
};
/**
 * واجهة لمعالج رسائل Looper.
 *
 * عندما يحتوي Looper على مرجع قوي إلى معالج الرسائل
 * رسالة لتحويلها. تأكد من الاتصال بـ Looper::removeMessages
 * to remove any pending messages destined for the handler so that the handler
 * can be destroyed.
 */
class MessageHandler : public virtual RefBase {
protected:
  virtual ~MessageHandler() { }
public:
  /**
   * Handles a message.
   */
  virtual void handleMessage(const Message& message) = 0;
};

2.5 c layer addFd 

We can also add fd to the epoll thread in Looper.cpp's addFd, and when fd has data, we can also handle the corresponding data. Let's first take a look at the addFd function, and we notice that there is a callBack callback

 int Looper::addFd(int fd, int ident, int events, Looper_callbackFunc callback, void* data) {
  return addFd(fd, ident, events, callback ? new SimpleLooperCallback(callback) : NULL, data);
}
int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data) {
#if DEBUG_CALLBACKS
  ALOGD("%p ~ addFd - fd=%d, ident=%d, events=0x%x, callback=%p, data=%p", this, fd, ident,
      events, callback.get(), data);
#endif
  if (!callback.get()) {
    if (! mAllowNonCallbacks) {
      ALOGE("Invalid attempt to set NULL callback but not allowed for this looper.");
      return -1;
    }
    if (ident < 0) {
      ALOGE("Invalid attempt to set NULL callback with ident < 0.");
      return -1;
    }
  } else {
    ident = POLL_CALLBACK;
  }
  { // acquire lock
    AutoMutex _l(mLock);
    Request request;
    request.fd = fd;
    request.ident = ident;
    request.events = events;
    request.seq = mNextRequestSeq++;
    request.callback = callback;
    request.data = data;
    if (mNextRequestSeq == -1) mNextRequestSeq = 0; // احتياطي رقم التسلسل -1
    struct epoll_event eventItem;
    request.initEventItem(&eventItem);
    ssize_t requestIndex = mRequests.indexOfKey(fd);
    if (requestIndex < 0) {
      int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem); // إضافة إلى epoll
      if (epollResult < 0) {
        ALOGE("خطأ في إضافة أحداث epoll للـ fd %d, errno=%d", fd, errno);
        return -1;
      }
      mRequests.add(fd, request); // إدخال في mRequests
    } else {
      int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_MOD, fd, & eventItem); // تحديث
      if (epollResult < 0) {
        if (errno == ENOENT) {
          // تقبّل ENOENT لأنه يعني أن ملف تعريف قديم كان
          // إغلاقه قبل إلغاء تسجيل نداءه وفي نفس الوقت تم إنشاء
          // تم إنشاء ملف تعريف بذات الرقم وقد تم الآن
          // مسجلاً للمرة الأولى. قد يحدث هذا الخطأ بشكل طبيعي
          // عندما يكون لنداء إضافي تأثير إغلاق ملف التعريف
          // قبل العودة وإلغاء التسجيل الذاتي. رقم سلسلة النداء
          // يتحقق بشكل إضافي من أن العرق حميد.
          //}}
          // Unfortunately due to kernel limitations we need to rebuild the epoll
          // set from scratch because it may contain an old file handle that we are
          // now unable to remove since its file descriptor is no longer valid.
          // No such problem would have occurred if we were using the poll system
          // call instead, but that approach carries others disadvantages.
#if DEBUG_CALLBACKS
          ALOGD("%p ~ addFd - EPOLL_CTL_MOD failed due to file descriptor "
              "being recycled, falling back on EPOLL_CTL_ADD, errno=%d",
              this, errno);
#endif
          epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
          if (epollResult < 0) {
            ALOGE("Error modifying or adding epoll events for fd %d, errno=%d",
                fd, errno);
            return -1;
          }
          scheduleEpollRebuildLocked();
        } else {
          ALOGE("Error modifying epoll events for fd %d, errno=%d", fd, errno);
          return -1;
        }
      }
      mRequests.replaceValueAt(requestIndex, request);
    }
  }
  return 1;
} 

في دالة pollOnce، نبدأ بالبحث عن fd المتطابق في mRequests، ثم ننشئ Response جديد في دالة pushResponse، ونقوم بمطابقة Response مع Request.

     } else {
      ssize_t requestIndex = mRequests.indexOfKey(fd);
      ssize_t requestIndex = mRequests.indexOfKey(fd);
        int events = 0;
        إذا (requestIndex >= 0) {
        إذا (epollEvents & EPOLLIN) events |= EVENT_INPUT;
        إذا (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT;
        إذا (epollEvents & EPOLLERR) events |= EVENT_ERROR;
        إذا (epollEvents & EPOLLHUP) events |= EVENT_HANGUP;
      } else {
        إدراج استجابة (events، mRequests.valueAt(requestIndex));
            ALOGW("تجاهل أحداث epoll غير المتوقعة 0x%x على fd %d التي هي "
      }
    } 

"لم يعد مسجلاً.", epollEvents، fd);

   للأسفل نبدأ في مرور mResponses من Response، ثم استدعاء استدعاء request فيها
    Response& response = mResponses.editItemAt(i);
    إذا (response.request.ident == POLL_CALLBACK) {
      int fd = response.request.fd;
      int events = response.events;
      void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
      ALOGD("%p ~ pollOnce - استدعاء استدعاء الحدث fd %p: fd=%d، events=0x%x، data=%p"
          هذا، استدعاء response.request.callback.get()، fd، events، data);
#endif
      // اطرح الرد. يرجى ملاحظة أن ملف التعريف قد يُغلق بواسطة
      // الرد (وقد يستخدم مرة أخرى) قبل أن يعود الدالة.
      // يجب أن نكون حذرين بعض الشيء عند إزالة ملف التعريف بعد ذلك.
      int callbackResult = response.request.callback->handleEvent(fd, events, data);
      if (callbackResult == 0) {
        removeFd(fd, response.request.seq);
      }
      // قم بإزالة مرجع الرد في هيكل الرد بشكل سريع لأننا
      // لن يتم إزالة محتوى vector الرد حتى التحقق التالي.
      response.request.callback.clear();
      result = POLL_CALLBACK;
    }
  } 

لننظر أيضًا كيف يستخدم Looper_test.cpp؟

   Pipe pipe;
  StubCallbackHandler handler(true);
  handler.setCallback(mLooper, pipe.receiveFd, Looper::EVENT_INPUT); 

لننظر في وظيفة setCallback الخاصة بـhandler

class CallbackHandler {
public:
  void setCallback(const sp<Looper>& looper, int fd, int events) {
    looper->addFd(fd, 0, events, staticHandler, this); // هذا هو الت 호출 للوظيفة addFd الخاصة بلوبر، ويعمل ك回调
  }
protected:
  فيirtual ~CallbackHandler() { }
  virtual int handler(int fd, int events) = 0;
private:
  static int staticHandler(int fd, int events, void* data) {// هذا هو الدالة التدعيمية
    return static_cast<CallbackHandler*>(data)->handler(fd, events);
  }
};
class StubCallbackHandler : public CallbackHandler {
public:
  int nextResult;
  int callbackCount;
  int fd;
  int events;
  :nextResult(nextResult),
      callbackCount(0), fd(-1), events(-1) {
  }
protected:
  virtual int handler(int fd, int events) {// هذا هو من خلال الدالة التدعيمية هنا
    callbackCount += 1;
    this->fd = fd;
    this->events = events;
    return nextResult;
  }
}; 

نحن نرى معًا addFd Looper، عندما يكون callback موجودًا، ننشئ SimpleLooperCallback جديدة

 int Looper::addFd(int fd, int ident, int events, Looper_callbackFunc callback, void* data) {
  return addFd(fd, ident, events, callback ? new SimpleLooperCallback(callback) : NULL, data);
} 

هناك Looper_callbackFunc هي typedef
typedef int (*Looper_callbackFunc)(int fd, int events, void* data);

نحن نأخذ نظرة أخرى على SimpleLooperCallback

 :public LooperCallback {
protected:
  virtual ~SimpleLooperCallback();
public:
  SimpleLooperCallback(Looper_callbackFunc callback);
  virtual int handleEvent(int fd, int events, void* data);
private:
  Looper_callbackFunc mCallback;
};SimpleLooperCallback::SimpleLooperCallback(Looper_callbackFunc callback) :
    mCallback(callback) {
}
SimpleLooperCallback::~SimpleLooperCallback() {
}
int SimpleLooperCallback::handleEvent(int fd, int events, void* data) {
  return mCallback(fd, events, data);
} 

في النهاية نستدعي callback->handleEvent(fd, events, data)، حيث يكون callback هو SimpleLooperCallback، والdata هنا هو نقطة الارتباط التي تم إرسالها مسبقًا لـ CallbackHandler
 لذلك في النهاية تم استدعاء staticHandler، وdata->handler، هو this->handler، وأخيرًا تم استدعاء دالة handler في StubCallbackHandler 

بالطبع يمكننا أيضًا تجنب التعقيد، واستخدام دالة addFd الثانية، وبالطبع يجب أن نصنع callBack الخاصة بنا لننفيذ LooperCallBack فقط، مما يجعل الأمور أسهل
 int addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data);

2.6 إضافة fd في الطبقة java 

كان يعتقد دائمًا أن إضافة fd ممكنة فقط في Looper في الطبقة c، لكن تبين أن هذا يمكن القيام به أيضًا في الطبقة java من خلال JNI 

يمكننا تنفيذ هذه الوظيفة من خلال إضافة addOnFileDescriptorEventListener في MessageQueue

   public void addOnFileDescriptorEventListener(@NonNull FileDescriptor fd,)
      @OnFileDescriptorEventListener.Events int events,
      @NonNull OnFileDescriptorEventListener listener) {
    if (fd == null) {
      throw new IllegalArgumentException("fd must not be null");
    }
    if (listener == null) {
      throw new IllegalArgumentException("listener must not be null");
    }
    synchronized (this) {
      updateOnFileDescriptorEventListenerLocked(fd, events, listener);
    }
  }

我们再来看看OnFileDescriptorEventListener 这个回调

   public interface OnFileDescriptorEventListener {
    public static final int EVENT_INPUT = 1 << 0;
    public static final int EVENT_OUTPUT = 1 << 1;
    public static final int EVENT_ERROR = 1 << 2;
    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(flag=true, value={EVENT_INPUT, EVENT_OUTPUT, EVENT_ERROR})
    public @interface Events {}
    @Events int onFileDescriptorEvents(@NonNull FileDescriptor fd, @Events int events);
  }

接着调用了updateOnFileDescriptorEventListenerLocked函数

 private void updateOnFileDescriptorEventListenerLocked(FileDescriptor fd, int events,)
      OnFileDescriptorEventListener listener) {
    final int fdNum = fd.getInt$();
    int index = -1;
    FileDescriptorRecord record = null;
    if (mFileDescriptorRecords != null) {
      index = mFileDescriptorRecords.indexOfKey(fdNum);
      if (index >= 0) {
        record = mFileDescriptorRecords.valueAt(index);
        if (record != null && record.mEvents == events) {
          return;
        }
      }
    }
    if (events != 0) {
      events |= OnFileDescriptorEventListener.EVENT_ERROR;
      if (record == null) {
        if (mFileDescriptorRecords == null) {
          mFileDescriptorRecords = new SparseArray<FileDescriptorRecord>();
        }
        record = new FileDescriptorRecord(fd, events, listener);//تخزين fd في objekt FileDescriptorRecord
        mFileDescriptorRecords.put(fdNum, record);//تخزين mFileDescriptorRecords
      } else {
        record.mListener = listener;
        record.mEvents = events;
        record.mSeq += 1;
      }
      nativeSetFileDescriptorEvents(mPtr, fdNum, events);//دعوة دالة خارجية
    } else if (record != null) {
      record.mEvents = 0;
      mFileDescriptorRecords.removeAt(index);
    }
  } 

الخارجية الأخيرة دعت دالة setFileDescriptorEvents من NativeMessageQueue 

static void android_os_MessageQueue_nativeSetFileDescriptorEvents(JNIEnv* env, jclass clazz,
    jlong ptr, jint fd, jint events) {
  NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
  nativeMessageQueue->setFileDescriptorEvents(fd, events);
}

setFileDescriptorEvents دالة، هذه هي دالة addFd التي يتم استدعاؤها في المرة الثانية، لذا يمكننا التأكد من أن NativeMessageQueue ورثت من LooperCallback

 void NativeMessageQueue::setFileDescriptorEvents(int fd, int events) {
  if (events) {
    int looperEvents = 0;
    if (events & CALLBACK_EVENT_INPUT) {
      looperEvents |= Looper::EVENT_INPUT;
    }
    if (events & CALLBACK_EVENT_OUTPUT) {
      looperEvents |= Looper::EVENT_OUTPUT;
    }
    mLooper->addFd(fd, Looper::POLL_CALLBACK, looperEvents, this,
        reinterpret_cast<void*>(events));
  } else {
    mLooper->removeFd(fd);
  }
}

بالطبع، يجب تنفيذ دالة handleEvent

 class NativeMessageQueue : public MessageQueue, public LooperCallback {
public:
  NativeMessageQueue();
  فيirtual ~NativeMessageQueue();
  فيirtual void raiseException(JNIEnv* env, const char* msg, jthrowable exceptionObj);
  void pollOnce(JNIEnv* env, jobject obj, int timeoutMillis);
  void wake();
  void setFileDescriptorEvents(int fd, int events);
  virtual int handleEvent(int fd, int events, void* data);

handleEvent هو الدالة التي يتم استدعاؤها بعد epoll_wait في looper، عندما يكون هناك بيانات في fd المضافة، يتم استدعاء هذه الدالة

 int NativeMessageQueue::handleEvent(int fd, int looperEvents, void* data) {
  int events = 0;
  if (looperEvents & Looper::EVENT_INPUT) {
    events |= CALLBACK_EVENT_INPUT;
  }
  if (looperEvents & Looper::EVENT_OUTPUT) {
    events |= CALLBACK_EVENT_OUTPUT;
  }
  if (looperEvents & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP | Looper::EVENT_INVALID)) {
    events |= CALLBACK_EVENT_ERROR;
  }
  int oldWatchedEvents = reinterpret_cast<intptr_t>(data);
  int newWatchedEvents = mPollEnv->CallIntMethod(mPollObj,
      gMessageQueueClassInfo.dispatchEvents, fd, events); // استدعاء التابعة
  if (!newWatchedEvents) {
    return 0; // إلغاء التسجيل للfd
  }
  if (newWatchedEvents != oldWatchedEvents) {
    setFileDescriptorEvents(fd, newWatchedEvents);
  }
  return 1;
}

في النهاية، dispatchEvents في MessageQueue في java يتم استدعاؤه في الطبقة JNI، ثم يتم استدعاء الدوال التابعة المسجلة مسبقًا

// من المكتوب الأصلي.
  private int dispatchEvents(int fd, int events) {
    // الحصول على سجل معالج الملف وأي حالة قد تتغير.
    final FileDescriptorRecord record;
    final int oldWatchedEvents;
    final OnFileDescriptorEventListener listener;
    final int seq;
    synchronized (this) {
      record = mFileDescriptorRecords.get(fd);//الحصول على سجل معالج الملف وأي حالة قد تتغير. 
      if (record == null) {
        return 0; // مزيف، لا يوجد مستمع مسجل
      }
      oldWatchedEvents = record.mEvents;
      events &= oldWatchedEvents; // فرز الأحداث بناءً على مجموعة المراقبة الحالية
      if (events == 0) {
        return oldWatchedEvents; // مزيف، تغيرت الأحداث المراقبة
      }
      listener = record.mListener;
      seq = record.mSeq;
    }
    // دعوة المستمع خارج القفل.
    int newWatchedEvents = listener.onFileDescriptorEvents(//listener回调
        record.mDescriptor, events);
    if (newWatchedEvents != 0) {
      newWatchedEvents |= OnFileDescriptorEventListener.EVENT_ERROR;
    }
    // تحديث سجل معالج الملف إذا غير المستمع مجموعة الأحداث.
    // الأحداث التي يجب مراقبتها ومستمع نفسه لم يتم تحديثه منذ.
    if (newWatchedEvents != oldWatchedEvents) {
      synchronized (this) {
        int index = mFileDescriptorRecords.indexOfKey(fd);
        if (index >= 0 && mFileDescriptorRecords.valueAt(index) == record
            && record.mSeq == seq) {
          record.mEvents = newWatchedEvents;
          if (newWatchedEvents == 0) {
            mFileDescriptorRecords.removeAt(index);
          }
        }
      }
    }
    // Return the new set of events to watch for native code to take care of.
    return newWatchedEvents;
  }

هذا هو نهاية محتوى هذا المقال، نأمل أن يكون قد ساعدكم في التعلم، ونأمل أن تحصلوا على دعمكم لموقع تعليمية呐喊.

بيان: محتوى هذا المقال تم جمعه من الإنترنت، حقوق الطبع والتأليف تخص المالك الأصلي، المحتوى تم إضافته من قبل مستخدمي الإنترنت بشكل تلقائي، هذا الموقع لا يمتلك حقوق الملكية، لم يتم تعديل المحتوى بشكل يدوي، ولا يتحمل أي مسؤولية قانونية. إذا رأيت محتوى يشتبه في حقوق الطبع والتأليف، فلا تتردد في إرسال بريد إلكتروني إلى: notice#oldtoolbag.com (عند إرسال البريد الإلكتروني، يرجى استبدال # ب @) لتقديم الشكوى، وتقديم الأدلة ذات الصلة، وإذا تم التحقق من ذلك، فإن هذا الموقع سيقوم بإزالة المحتوى المشبوه فوراً.

تحبك