قائمة انتظار الرسائل السريعة (FMQ)

تستخدم البنية الأساسية لاستدعاء الإجراءات عن بُعد (RPC) في HIDL آليات الربط، مما يعني أن الاتصالات تتضمن عبء زائد، وتتطلب عمليات kernel، ويمكنها تشغيل إجراء الجدولة. ومع ذلك، في الحالات التي يجب فيها نقل البيانات بين العمليات مع تقليل الوقت المستغرَق في عمليات المعالجة الثانوية وعدم تدخل النواة، يتم استخدام نظام "قائمة الرسائل السريعة" (FMQ).

تُنشئ واجهة FMQ قوائم انتظار الرسائل باستخدام السمات المطلوبة. يمكنك إرسال كائن MQDescriptorSync أو MQDescriptorUnsync عبر استدعاء HIDL RPC، وتستخدم عملية الاستلام للوصول إلى قائمة انتظار الرسائل.

أنواع قوائم الانتظار

يتيح Android نوعَين من قوائم الانتظار (المعروفَين باسم النُسخ):

  • يُسمح بتجاوز سعة قوائم الانتظار غير المتزامنة، ويمكن أن تتضمّن العديد من القارئين، ويجب أن يقرأ كل قارئ البيانات في الوقت المناسب وإلا سيفقدها.
  • لا يُسمح بتجاوز سعة قوائم الانتظار المتزامنة، ويمكن أن يكون لها قارِئ واحد فقط.

لا يُسمح لكلا النوعين من قوائم الانتظار بمواصلة العمل (تفشل القراءة من قائمة انتظار فارغة) ويمكن أن يكون لكاتب واحد فقط.

قوائم الانتظار غير المتزامنة

تحتوي "القائمة الانتظار غير المتزامنة" على كاتب واحد فقط، ولكن يمكن أن تحتوي على أي عدد من القارئين. هناك موضع كتابة واحد للانتظار، ومع ذلك، يحتفظ كل قارئ بموضع القراءة المستقل الخاص به.

تنجح عمليات الكتابة إلى "القائمة الانتظار" دائمًا (لا يتم التحقّق من حدوث تدفّق زائد) ما دام حجمها ليس أكبر من سعة "القائمة الانتظار" التي تم ضبطها (تتعطّل عمليات الكتابة التي تزيد عن سعة "القائمة الانتظار" على الفور). وبما أنّ كل قارئ قد يكون لديه موضع قراءة مختلف، بدلاً من الانتظار إلى أن يقرأ كل قارئ كل قطعة من البيانات، يتمّ إزالة البيانات من "قائمة الانتظار" كلما احتجت عمليات الكتابة الجديدة إلى المساحة.

القراء هم مسئولون عن استرداد البيانات قبل أن تنزل عن نهاية قائمة الانتظار. يتعذّر على القراءة التي تحاول قراءة بيانات أكثر من المتاحة إما على الفور (إذا كانت غير محظورة) أو تنتظر توفُّر بيانات كافية (في حال الحظر). فدائمًا ما تخفق القراءة التي تحاول قراءة بيانات أكثر من سعة قائمة الانتظار على الفور.

إذا فشل القارئ في متابعة الكاتب، بحيث أصبح مقدار البيانات التي كتبها ولم يقرأها ذلك القارئ بعد أكبر من سعة قائمة الانتظار، فإن القراءة التالية لا تعرض أي بيانات، وإنما يعيد تعيين موضع القراءة للقارئ ليعادل آخر موضع كتابة ثم يعرض الفشل. إذا تم التحقّق من البيانات المتاحة للقراءة بعد حدوث تدفّق زائد ولكن قبل القراءة التالية، فإنه يعرض بيانات أكثر متاحة للقراءة من سعة الانتظار، ما يشير إلى حدوث تدفّق زائد. (إذا امتلأت قائمة الانتظار بين التحقّق من البيانات المتاحة ومحاولة قراءة هذه البيانات، فإنّ المؤشر الوحيد على الامتلاء هو تعذُّر قراءة البيانات).

قوائم الانتظار المتزامنة

تحتوي "القائمة الانتظار المتزامنة" على كاتب واحد وقارئ واحد مع موضع كتابة واحد وموضع قراءة واحد. من المستحيل كتابة بيانات أكثر مما يمكن أن تستوعبه الطابور أو قراءة بيانات أكثر مما يحتوي عليه حاليًا. استنادًا إلى ما إذا تم استدعاء دالة القراءة أو الكتابة المحظورة أو غير المحظورة، تؤدي محاولات تجاوز المساحة أو البيانات المتاحة إلى تعذُّر الإجراء أو حظره إلى أن يتم إكمال العملية المطلوبة. إنّ محاولات قراءة أو كتابة بيانات أكثر من سعة الانتظار تفشل دائمًا على الفور.

إعداد طلب معلومات فورية

تتطلّب قائمة الرسائل الواردة عناصر MessageQueue متعددة: عنصر واحد لكتابة الرسائل فيه وعنصر واحد أو أكثر لقراءتها. لا تتوفّر عملية برمجة صريحة لتحديد العنصر الذي يتم استخدامه للكتابة أو القراءة، ويتحمل المستخدم مسؤولية التأكّد من عدم استخدام أي عنصر للقراءة والكتابة معًا، ومن توفّر كاتب واحد على الأكثر، ومن توفّر قارئ واحد على الأكثر في الطوابير المتزامنة.

إنشاء أول كائن Messageplaylist

يتم إنشاء قائمة انتظار للرسائل وضبطها من خلال مكالمة واحدة:

#include <fmq/MessageQueue.h>
using android::hardware::kSynchronizedReadWrite;
using android::hardware::kUnsynchronizedWrite;
using android::hardware::MQDescriptorSync;
using android::hardware::MQDescriptorUnsync;
using android::hardware::MessageQueue;
....
// For a synchronized nonblocking FMQ
mFmqSynchronized =
  new (std::nothrow) MessageQueue<uint16_t, kSynchronizedReadWrite>
      (kNumElementsInQueue);
// For an unsynchronized FMQ that supports blocking
mFmqUnsynchronizedBlocking =
  new (std::nothrow) MessageQueue<uint16_t, kUnsynchronizedWrite>
      (kNumElementsInQueue, true /* enable blocking operations */);
  • ينشئ MessageQueue<T, flavor>(numElements) initializer عنصرًا يتيح وظيفة "قائمة انتظار الرسائل" ويُنشئها.
  • ينشئ برنامج تهيئة MessageQueue<T, flavor>(numElements, configureEventFlagWord) ويهيئ عنصرًا يتوافق مع وظيفة قائمة انتظار الرسائل مع الحظر.
  • يمكن أن يكون flavor إما kSynchronizedReadWrite لقائمة انتظار متزامنة أو kUnsynchronizedWrite لقائمة انتظار غير متزامنة.
  • يمكن أن يكون uint16_t (في هذا المثال) أي نوع حدَّده HIDL لا يتضمّن مخازن مؤقتة متداخلة (بدون أنواع string أو vec ) أو معالِمات أو واجهات.
  • يشير kNumElementsInQueue إلى حجم "القائمة الانتظار" بعدد الإدخالات، ويحدِّد حجم ذاكرة التخزين المؤقت المشتركة التي يتم تخصيصها للقائمة الانتظار.

أنشئ عنصر MessageQueue الثاني.

يتم إنشاء الجانب الثاني من قائمة انتظار الرسائل باستخدام كائن MQDescriptor تم الحصول عليه من الجانب الأول. يتم إرسال الكائن MQDescriptor عبر استدعاء HIDL أو AIDL RPC إلى العملية التي تحتوي على الطرف الثاني من قائمة انتظار الرسائل. يحتوي الرمز MQDescriptor على معلومات عن "قائمة الانتظار"، بما في ذلك:

  • معلومات لتعيين المخزن المؤقت وكتابة المؤشر
  • معلومات لربط مؤشر القراءة (إذا تمت مزامنة "القائمة الانتظار")
  • معلومات لربط كلمة علامة الحدث (إذا كانت قائمة الانتظار تحظر).
  • نوع العنصر (<T, flavor>)، الذي يتضمّن النوع المحدّد من خلال HIDL لعناصر السلسلة ونوع السلسلة (متزامنة أو غير متزامنة).

يمكنك استخدام عنصر MQDescriptor لإنشاء عنصر MessageQueue:

MessageQueue<T, flavor>::MessageQueue(const MQDescriptor<T, flavor>& Desc, bool resetPointers)

تشير المَعلمة resetPointers إلى ما إذا كان يجب إعادة ضبط مواضع القراءة والكتابة على 0 أثناء إنشاء عنصر MessageQueue هذا. في قائمة الانتظار غير المتزامنة، يتم دائمًا ضبط موضع القراءة (الذي يكون محليًا لكل كائن MessageQueue في قوائم الانتظار غير المتزامنة) على 0 أثناء الإنشاء. عادةً ما يتمّ إعداد MQDescriptor أثناء إنشاء كائن "قائمة انتظار الرسائل" الأولى. للتحكّم بشكل أكبر في الذاكرة المشترَكة، يمكنك إعداد MQDescriptor يدويًا (يتم تعريف MQDescriptor في system/libhidl/base/include/hidl/MQDescriptor.h)، ثم إنشاء كل عنصر MessageQueue كما هو موضّح في هذا القسم.

قوائم انتظار الحظر وعلامات الأحداث

لا تتيح قوائم الانتظار تلقائيًا حظر عمليات القراءة والكتابة. هناك نوعان من عمليات حظر طلبات القراءة والكتابة:

  • النموذج القصير، الذي يتضمّن ثلاث مَعلمات (مُشير البيانات وعدد العناصر ومهلة الانتظار)، يتيح حظر عمليات القراءة والكتابة الفردية في ملف محتوى واحد في "قائمة الانتظار". عند استخدام هذا النموذج، تعالج "القائمة" علامة الحدث وشبكات البت داخليًا، ويجب تهيئة كائن قائمة الرسائل الأولى باستخدام مَعلمة ثانية هي true. مثلاً:
    // For an unsynchronized FMQ that supports blocking
    mFmqUnsynchronizedBlocking =
      new (std::nothrow) MessageQueue<uint16_t, kUnsynchronizedWrite>
          (kNumElementsInQueue, true /* enable blocking operations */);
    
  • الشكل الطويل، الذي يتضمّن ستة مَعلمات (تتضمّن علامة الحدث وطوابع البت)، يسمح باستخدام عنصر EventFlag مشترَك بين قوائم انتظار متعددة ويسمح بتحديد طوابع بت الإشعارات التي سيتم استخدامها. في هذه الحالة، يجب تقديم علامة الحدث وشبكات البتات لكل طلب قراءة وكتابة.

بالنسبة إلى النموذج الطويل، يمكنك تقديم EventFlag صراحةً في كلّ مكالمة readBlocking() وwriteBlocking(). يمكنك بدء إحدى القوائم باستخدام علامة حدث داخلية، والتي يجب بعد ذلك استخراجها من كائنات MessageQueue الخاصة بهذه القائمة باستخدام getEventFlagWord() واستخدامها لإنشاء كائنات EventFlag في كل عملية لاستخدامها مع قوائم FMQ الأخرى. بدلاً من ذلك، يمكنك بدء عناصر EventFlag باستخدام أي ذاكرة مشترَكة مناسبة.

بشكل عام، يجب أن تستخدم كل قائمة انتظار طريقة واحدة فقط من بين الحظر غير المشروط أو حظر المحتوى القصير أو حظر المحتوى الطويل. ولا يشكّل خلطهما خطأ، ولكن يجب برمجة المحتوى بعناية للحصول على النتيجة المطلوبة.

وضع علامة "للقراءة فقط" على الذاكرة

تمتلك الذاكرة المشتركة أذونات القراءة والكتابة تلقائيًا. بالنسبة إلى طوابير الرسائل غير المتزامنة (kUnsynchronizedWrite)، قد يريد الكاتب إزالة أذونات الكتابة لجميع القراء قبل توزيع عناصر MQDescriptorUnsync. يضمن ذلك عدم تمكّن العمليات الأخرى من الكتابة في "قائمة الانتظار"، وهو إجراء يُنصح به للحماية من الأخطاء أو السلوك السيئ في عمليات القراءة. إذا أراد الكاتب أن يتمكّن القرّاء من إعادة ضبط "القائمة الانتظار" كلما استخدموا MQDescriptorUnsync لإنشاء جانب القراءة من "القائمة الانتظار"، لا يمكن وضع علامة على الذاكرة بصفتها للقراءة فقط. وهذا هو السلوك التلقائي للدالة الإنشائية MessageQueue. وبالتالي، إذا كان هناك مستخدمون حاليون في قائمة الانتظار هذه، يجب تغيير الرمز الخاص بهم لإنشاء قائمة المحتوى التالي باستخدام resetPointer=false.

  • الكاتب: استدعاء ashmem_set_prot_region باستخدام وصف ملف MQDescriptor والمنطقة التي تم ضبطها على القراءة فقط (PROT_READ):
    int res = ashmem_set_prot_region(mqDesc->handle->data[0], PROT_READ)
  • القارئ: إنشاء قائمة انتظار رسائل باستخدام resetPointer=false (القيمة التلقائية هي true):
    mFmq = new (std::nothrow) MessageQueue(mqDesc, false);

استخدام MessageQueue

واجهة برمجة التطبيقات العامة للكائن MessageQueue هي:

size_t availableToWrite() // Space available (number of elements).
size_t availableToRead() // Number of elements available.
size_t getQuantumSize() // Size of type T in bytes.
size_t getQuantumCount() // Number of items of type T that fit in the FMQ.
bool isValid() // Whether the FMQ is configured correctly.
const MQDescriptor<T, flavor>* getDesc() // Return info to send to other process.

bool write(const T* data)  // Write one T to FMQ; true if successful.
bool write(const T* data, size_t count) // Write count T's; no partial writes.

bool read(T* data); // read one T from FMQ; true if successful.
bool read(T* data, size_t count); // Read count T's; no partial reads.

bool writeBlocking(const T* data, size_t count, int64_t timeOutNanos = 0);
bool readBlocking(T* data, size_t count, int64_t timeOutNanos = 0);

// Allows multiple queues to share a single event flag word
std::atomic<uint32_t>* getEventFlagWord();

bool writeBlocking(const T* data, size_t count, uint32_t readNotification,
uint32_t writeNotification, int64_t timeOutNanos = 0,
android::hardware::EventFlag* evFlag = nullptr); // Blocking write operation for count Ts.

bool readBlocking(T* data, size_t count, uint32_t readNotification,
uint32_t writeNotification, int64_t timeOutNanos = 0,
android::hardware::EventFlag* evFlag = nullptr) // Blocking read operation for count Ts;

// APIs to allow zero copy read/write operations
bool beginWrite(size_t nMessages, MemTransaction* memTx) const;
bool commitWrite(size_t nMessages);
bool beginRead(size_t nMessages, MemTransaction* memTx) const;
bool commitRead(size_t nMessages);

يمكنك استخدام availableToWrite() وavailableToRead() لتحديد مقدار البيانات التي يمكن نقلها في عملية واحدة. في ملف شخصي غير متزامن:

  • تعرض دالة availableToWrite() دائمًا سعة الانتظار.
  • يمتلك كل قارئ موضع قراءة خاص به ويجري عملية حسابية خاصة به لتحديد قيمة availableToRead().
  • من وجهة نظر قارئ بطيء، يُسمح بتجاوز السعة القصوى للصفّ، ويمكن أن يؤدي ذلك إلى عرض availableToRead() لقيمة أكبر من حجم الصفّ. تؤدي القراءة الأولى بعد حدوث تدفّق إلى تعذُّر ضبط موضع القراءة لهذا القارئ على أنّه يساوي مؤشر الكتابة الحالي، سواء تم الإبلاغ عن تدفّق البيانات من خلال availableToRead() أم لا.

تُعرِض الطريقتان read() وwrite() القيمة true إذا كان بالإمكان نقل جميع البيانات المطلوبة (وتم نقلها) من وإلى القائمة. لا تؤدي هذه الطرق إلى الحظر، بل إما أن تنجح (وتعرض true) أو تؤدي إلى ظهور خطأ (false) على الفور.

تنتظر الطريقتان readBlocking() وwriteBlocking() إلى أن يتم إكمال العملية المطلوبة، أو إلى أن تنتهي مهلة التنفيذ (تعني قيمة timeOutNanos 0 عدم انتهاء المهلة مطلقًا).

يتم تنفيذ عمليات الحظر باستخدام كلمة علامة حدث. بشكلٍ تلقائي، تُنشئ كل قائمة انتظار كلمة علامة خاصة بها وتستخدمها للسماح بالشكل المختصر من readBlocking() وwriteBlocking(). يمكن أن تشترك قوائم الانتظار المتعددة في كلمة واحدة، بحيث يمكن للعملية انتظار الكتابة أو القراءة في أي من قوائم الانتظار. عند استدعاء getEventFlagWord()، يمكنك الحصول على مؤشر لكلمة علامة حدث في إحدى القوائم، ويمكنك استخدام هذا المؤشر (أو أي مؤشر إلى موقع مناسب للذاكرة) لإنشاء كائن EventFlag لتمريره إلى الشكل الطويل من readBlocking() وwriteBlocking()لقائمة انتظار مختلفة. تُحدِّد المَعلمتَان readNotification وwriteNotification البتّات التي يجب استخدامها في علامة الحدث للإشارة إلى عمليات القراءة والكتابة في هذه الطابور. readNotification و writeNotification هما قناع بت 32 بت.

ينتظر readBlocking() على writeNotification بت، إذا كانت هذه المَعلمة هي 0، يتعذّر دائمًا إجراء الاستدعاء. إذا كانت قيمة readNotification هي 0، لن تنجح عملية الاستدعاء، ولكن لن تؤدي القراءة الناجحة إلى ضبط أي وحدات بت للإشعارات. في قائمة الانتظار المتزامنة، يعني ذلك أنّ طلب writeBlocking() المقابل لن يتم تفعيله أبدًا ما لم يتم ضبط القيمة في مكان آخر. في قائمة الانتظار غير المتزامنة، لا ينتظر writeBlocking() (يجب أن يظلّ قيد الاستخدام لضبط وبت إشعار القيد)، ومن المناسب ألا تضبط عمليات القراءة أي وبت إشعار. وبالمثل، يتعذّر تنفيذ writeblocking() إذا كانت قيمة readNotification هي 0، ويؤدي الإجراء الناجح إلى ضبط وحدات البت المحدّدة writeNotification.

للانتظار في قوائم انتظار متعددة في الوقت نفسه، استخدِم طريقة wait() في كائن EventFlag للانتظار في قناع بتات للإشعارات. تُرجع الطريقة wait() كلمة حالة تحتوي على وحدات البت التي تسببت في مجموعة التنشيط. وتُستخدَم هذه المعلومات بعد ذلك للتحقّق من توفّر مساحة أو بيانات كافية في "القائمة الانتظار" المقابلة لإجراء عملية القراءة والكتابة المطلوبة وتنفيذ write() وread() غير المحظورة. لتلقي إشعار بعملية النشر، يمكنك استخدام استدعاء آخر لطريقة wake() لكائن EventFlag. للحصول على تعريف للتجريد EventFlag، راجع system/libfmq/include/fmq/EventFlag.h.

عمليات النسخ بدون أي تكلفة

توجّه الطرق read وwrite وreadBlocking وwriteBlocking() مؤشرًا إلى مخزن مؤقت للإدخال والإخراج كوسيطة وتستخدم طلبات memcpy() داخليًا لنسخ البيانات بين المخزن المؤقت نفسه والمخزن المؤقت لحلقة FMQ. لتحسين الأداء، يتضمّن الإصدار 8.0 من نظام التشغيل Android والإصدارات الأحدث مجموعة من واجهتَي برمجة التطبيقات (API) اللتين توفّران إمكانية وصول المؤشر مباشرةً إلى المخزن الدائري، ما يزيل الحاجة إلى استخدام طلبات memcpy.

استخدِم واجهات برمجة التطبيقات العامة التالية لإجراء عمليات "الطلبات المجمّعة بدون نسخ":

bool beginWrite(size_t nMessages, MemTransaction* memTx) const;
bool commitWrite(size_t nMessages);

bool beginRead(size_t nMessages, MemTransaction* memTx) const;
bool commitRead(size_t nMessages);
  • توفّر طريقة beginWrite مؤشرات أساسية إلى ملف تخزين دوار للفوترة (FMQ). بعد كتابة البيانات، احفظها باستخدام commitWrite(). تعمل الطريقتان beginRead وcommitRead بالطريقة نفسها.
  • تستخدم الطريقتان beginRead وWrite كإدخال لعدد الرسائل المطلوب قراءتها وكتابتها وعرض قيمة منطقية تشير إلى ما إذا كانت القراءة أو الكتابة ممكنة. إذا كان من الممكن القراءة أو الكتابة، تتم تعبئة memTx البنية بمؤشرات أساسية يمكن استخدامها للوصول المباشر باستخدام المؤشر إلى الذاكرة المشتركة للوسيط الدائري.
  • يحتوي عنصر MemRegion على تفاصيل عن كتلة ذاكرة، بما في ذلك المؤشر الأساسي (العنوان الأساسي لكتلة الذاكرة) والطول بوحدة T (طول كتلة الذاكرة بوحدة نوع قائمة الرسائل التي تحدّدها HIDL).
  • تحتوي بنية MemTransaction على بنيتي MemRegion first وsecond، لأنّ القراءة أو الكتابة في المخزّن الدائري قد تتطلّب لفّ البيانات إلى بداية الطابور. وهذا يعني أنّه يجب استخدام مؤشّرَين أساسيَّين لقراءة البيانات وكتابتها في ملف التخزين المؤقت الدائري لـ FMQ.

للحصول على العنوان الأساسي والطول من بنية MemRegion:

T* getAddress(); // gets the base address
size_t getLength(); // gets the length of the memory region in terms of T
size_t getLengthInBytes(); // gets the length of the memory region in bytes

للاطّلاع على إشارات بنيتَي MemRegion الأولى والثانية ضمن كائن MemTransaction، اتّبِع الخطوات التالية:

const MemRegion& getFirstRegion(); // get a reference to the first MemRegion
const MemRegion& getSecondRegion(); // get a reference to the second MemRegion

مثال على الكتابة إلى FMQ باستخدام واجهات برمجة التطبيقات التي لا تتطلب نسخ البيانات:

MessageQueueSync::MemTransaction tx;
if (mQueue->beginRead(dataLen, &tx)) {
    auto first = tx.getFirstRegion();
    auto second = tx.getSecondRegion();

    foo(first.getAddress(), first.getLength()); // method that performs the data write
    foo(second.getAddress(), second.getLength()); // method that performs the data write

    if(commitWrite(dataLen) == false) {
       // report error
    }
} else {
   // report error
}

تشكّل أيضًا طرق المساعدة التالية جزءًا من MemTransaction:

  • تعرض T* getSlot(size_t idx); مؤشرًا إلى الفتحة idx داخل MemRegions التي تعد جزءًا من كائن MemTransaction هذا. إذا كان العنصر MemTransaction يمثّل مناطق الذاكرة لقراءة وكتابة N عنصر من النوع T، يكون النطاق الصالح ل idx بين 0 وN-1.
  • يكتب bool copyTo(const T* data, size_t startIdx, size_t nMessages = 1); nMessages عنصرًا من النوع T في مناطق الذاكرة التي يصفّها العنصر، بدءًا من الفهرس startIdx. تستخدم هذه الطريقة memcpy()، وليست مصممة بغرض استخدامها. إذا كان عنصر MemTransaction يمثّل ذاكرة لقراءة وكتابة N عنصر من النوع T، يكون النطاق الصالح لـ idx بين 0 وN-1.
  • bool copyFrom(T* data, size_t startIdx, size_t nMessages = 1); هي طريقة مساعدة لقراءة nMessages عنصر من النوع T من مناطق الذاكرة التي يصفها الكائن بدءًا من startIdx. تستخدِم هذه المحاولةmemcpy() ولا يُقصد استخدامها في عملية بدون نسخة.

إرسال قائمة المحتوى التالي عبر HIDL

من جانب الإنشاء:

  1. أنشئ عنصرًا في "قائمة انتظار الرسائل" كما هو موضّح أعلاه.
  2. تحقَّق من صلاحية العنصر من خلال isValid().
  3. إذا كنت في انتظار قوائم انتظار متعددة من خلال تمرير EventFlag إلى الشكل الطويل من readBlocking() أو writeBlocking()، يمكنك استخراج مُشير علامة الحدث (باستخدام getEventFlagWord()) من ملف برمجي MessageQueue تم إعداده لإنشاء العلامة، و استخدام هذه العلامة لإنشاء ملف برمجي EventFlag ضروري.
  4. استخدِم الطريقة MessageQueue getDesc() للحصول على ملف تعريف.
  5. في ملف HAL، امنح الطريقة مَعلمة من النوع fmq_sync أو fmq_unsync حيث يكون T هو نوع مناسب محدّد من HIDL. يمكنك استخدامه لإرسال العنصر الذي تم إرجاعه من خلال "getDesc()" إلى عملية الاستلام.

على الجانب المستلِم:

  1. استخدِم كائن الوصف لإنشاء كائن MessageQueue. استخدِم نوع البيانات ونكهة "القائمة الانتظار" نفسها، وإلا لن يتم compiling (تجميع) النموذج.
  2. إذا استخرجت علامة حدث، استخرِج العلامة من عنصر MessageQueue المقابل في عملية الاستقبال.
  3. استخدِم عنصر MessageQueue لنقل البيانات.