نظرة عامة على Wi-Fi Aware

تتيح إمكانات Wi-Fi Aware للأجهزة التي تعمل بنظام التشغيل Android 8.0 (المستوى 26 لواجهة برمجة التطبيقات) والإصدارات الأحدث اكتشاف بعضها البعض والاتصال ببعضها مباشرةً بدون أي نوع آخر من الاتصال بينها. تُعرف ميزة Wi-Fi Aware أيضًا باسم الاتصال المباشر بمحطات لاسلكية مجاورة (NAN).

تعمل شبكة Wi-Fi Aware من خلال تشكيل مجموعات مع الأجهزة المجاورة، أو من خلال إنشاء مجموعة جديدة إذا كان الجهاز هو الأول في المنطقة. ينطبق سلوك التجميع هذا على الجهاز بأكمله وتتم إدارته من خلال خدمة نظام Wi-Fi Aware، ولا يمكن للتطبيقات التحكّم في سلوك التجميع. تستخدم التطبيقات واجهات برمجة تطبيقات Wi-Fi Aware للتواصل مع خدمة نظام Wi-Fi Aware التي تدير أجهزة Wi-Fi Aware على الجهاز.

تتيح واجهات برمجة التطبيقات Wi-Fi Aware للتطبيقات تنفيذ العمليات التالية:

  • اكتشاف الأجهزة الأخرى: تتضمّن واجهة برمجة التطبيقات آلية للعثور على الأجهزة الأخرى المجاورة. تبدأ العملية عندما ينشر جهاز واحد خدمة واحدة أو أكثر من الخدمات القابلة للاكتشاف. بعد ذلك، عندما يشترك جهاز في خدمة واحدة أو أكثر ويدخل في نطاق شبكة Wi-Fi الخاصة بالناشر، يتلقّى المشترك إشعارًا بأنّه تم العثور على ناشر مطابق. بعد أن يكتشف المشترك ناشرًا، يمكن للمشترك إما إرسال رسالة قصيرة أو إنشاء اتصال بالشبكة مع الجهاز الذي تم اكتشافه. يمكن أن تكون الأجهزة ناشرين ومشتركين في الوقت نفسه.

  • إنشاء اتصال بالشبكة: بعد أن يكتشف جهازان بعضهما، يمكنهما إنشاء اتصال ثنائي الاتجاه بشبكة Wi-Fi Aware بدون نقطة وصول.

تتيح اتصالات شبكة Wi-Fi Aware معدلات نقل بيانات أعلى عبر مسافات أطول من اتصالات البلوتوث. هذه الأنواع من الاتصالات مفيدة للتطبيقات التي تشارك كميات كبيرة من البيانات بين المستخدمين، مثل تطبيقات مشاركة الصور.

تحسينات Android 13 (المستوى 33 لواجهة برمجة التطبيقات)

على الأجهزة التي تعمل بالإصدار 13 من نظام التشغيل Android (المستوى 33 لواجهة برمجة التطبيقات) والإصدارات الأحدث التي تتيح وضع الاتصال الفوري، يمكن للتطبيقات استخدام الطريقتَين PublishConfig.Builder.setInstantCommunicationModeEnabled() وSubscribeConfig.Builder.setInstantCommunicationModeEnabled() لتفعيل وضع الاتصال الفوري أو إيقافه لجلسة اكتشاف الناشر أو المشترك. يسرِّع وضع الاتصال الفوري عملية تبادل الرسائل واكتشاف الخدمة وإعداد أي مسار بيانات كجزء من جلسة اكتشاف الناشر أو المشترك. لتحديد ما إذا كان الجهاز يتيح وضع الاتصال الفوري، استخدِم طريقة isInstantCommunicationModeSupported().

تحسينات Android 12 (المستوى 31 لواجهة برمجة التطبيقات)

يضيف Android 12 (المستوى 31 لواجهة برمجة التطبيقات) بعض التحسينات إلى Wi-Fi Aware:

  • على الأجهزة التي تعمل بنظام التشغيل Android 12 (مستوى واجهة برمجة التطبيقات 31) أو الإصدارات الأحدث، يمكنك استخدام دالة الاستدعاء onServiceLost() لتلقّي تنبيه عندما يفقد تطبيقك خدمة تم اكتشافها بسبب توقّف الخدمة أو خروجها عن النطاق.
  • تم تبسيط عملية إعداد مسارات بيانات Wi-Fi Aware. استخدمت الإصدارات السابقة رسائل L2 لتوفير عنوان MAC الخاص بأداة البدء، ما أدى إلى حدوث وقت استجابة. على الأجهزة التي تعمل بنظام التشغيل Android 12 والإصدارات الأحدث، يمكن ضبط المستجيب (الخادم) لقبول أي جهاز آخر، أي أنّه لا يحتاج إلى معرفة عنوان MAC الخاص بالجهاز المُبادر مسبقًا. يؤدي ذلك إلى تسريع عملية إعداد مسار البيانات ويتيح إنشاء روابط متعددة بين نقطتين من خلال طلب شبكة واحد فقط.
  • يمكن للتطبيقات التي تعمل بنظام التشغيل Android 12 أو الإصدارات الأحدث استخدام طريقة WifiAwareManager.getAvailableAwareResources() للحصول على عدد مسارات البيانات المتاحة حاليًا وجلسات النشر وجلسات الاشتراك. يمكن أن يساعد ذلك التطبيق في تحديد ما إذا كانت هناك موارد كافية متاحة لتنفيذ الوظيفة المطلوبة.

عملية الإعداد الأولية

لإعداد تطبيقك لاستخدام ميزة "اكتشاف Wi-Fi Aware" والشبكات، اتّبِع الخطوات التالية:

  1. اطلب الأذونات التالية في ملف بيان تطبيقك:

    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- If your app targets Android 13 (API level 33)
         or higher, you must declare the NEARBY_WIFI_DEVICES permission. -->
    <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
                     <!-- If your app derives location information from
                          Wi-Fi APIs, don't include the "usesPermissionFlags"
                          attribute. -->
                     android:usesPermissionFlags="neverForLocation" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
                     <!-- If any feature in your app relies on precise location
                          information, don't include the "maxSdkVersion"
                          attribute. -->
                     android:maxSdkVersion="32" />
  2. تحقَّق مما إذا كان الجهاز يتيح استخدام Wi-Fi Aware من خلال واجهة برمجة التطبيقات PackageManager ، كما هو موضّح أدناه:

    Kotlin

    context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE)

    Java

    context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);
  3. تحقَّق مما إذا كانت ميزة Wi-Fi Aware متاحة حاليًا. قد تكون ميزة Wi-Fi Aware متوفّرة على الجهاز، ولكن قد لا تكون متاحة حاليًا لأنّ المستخدم أوقف Wi-Fi أو الموقع الجغرافي. استنادًا إلى إمكانات الأجهزة والبرامج الثابتة، قد لا تتوافق بعض الأجهزة مع Wi-Fi Aware في حال استخدام اتصال Wi-Fi مباشر أو SoftAP أو ميزة "التوصيل". للتحقّق مما إذا كانت ميزة Wi-Fi Aware متاحة حاليًا، يمكنك استدعاء isAvailable().

    يمكن أن يتغيّر مدى توفّر Wi-Fi Aware في أي وقت. يجب أن يسجّل تطبيقك BroadcastReceiver لتلقّي ACTION_WIFI_AWARE_STATE_CHANGED، والذي يتم إرساله كلما تغيّرت حالة التوفّر. عندما يتلقّى تطبيقك الهدف من البث، عليه تجاهل جميع الجلسات الحالية (بافتراض أنّ خدمة Wi-Fi Aware قد تعطّلت)، ثم التحقّق من الحالة الحالية للتوفّر وتعديل سلوكه وفقًا لذلك. على سبيل المثال:

    Kotlin

    val wifiAwareManager = context.getSystemService(Context.WIFI_AWARE_SERVICE) as WifiAwareManager?
    val filter = IntentFilter(WifiAwareManager.ACTION_WIFI_AWARE_STATE_CHANGED)
    val myReceiver = object : BroadcastReceiver() {
    
        override fun onReceive(context: Context, intent: Intent) {
            // discard current sessions
            if (wifiAwareManager?.isAvailable) {
                ...
            } else {
                ...
            }
        }
    }
    context.registerReceiver(myReceiver, filter)

    Java

    WifiAwareManager wifiAwareManager = 
            (WifiAwareManager)context.getSystemService(Context.WIFI_AWARE_SERVICE)
    IntentFilter filter =
            new IntentFilter(WifiAwareManager.ACTION_WIFI_AWARE_STATE_CHANGED);
    BroadcastReceiver myReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // discard current sessions
            if (wifiAwareManager.isAvailable()) {
                ...
            } else {
                ...
            }
        }
    };
    context.registerReceiver(myReceiver, filter);

لمزيد من المعلومات، يُرجى الاطّلاع على عمليات البث.

الحصول على جلسة

لبدء استخدام Wi-Fi Aware، يجب أن يحصل تطبيقك على WifiAwareSession من خلال استدعاء attach(). تؤدي هذه الطريقة ما يلي:

  • يؤدي هذا الإجراء إلى تفعيل أجهزة Wi-Fi Aware.
  • الانضمام إلى مجموعة Wi-Fi Aware أو إنشاؤها
  • تنشئ هذه الطريقة جلسة Wi-Fi Aware بمساحة اسم فريدة تعمل كحاوية لجميع جلسات الاكتشاف التي يتم إنشاؤها داخلها.

في حال نجاح عملية إرفاق التطبيق، ينفِّذ النظام دالة الاستدعاء onAttached(). توفّر دالة الاستدعاء هذه عنصر WifiAwareSession يجب أن يستخدمه تطبيقك في جميع عمليات الجلسة الإضافية. يمكن لأي تطبيق استخدام الجلسة لنشر خدمة أو الاشتراك في خدمة.

يجب أن يطلب تطبيقك attach() مرة واحدة فقط. إذا كان تطبيقك يستدعي attach() عدة مرات، يتلقّى التطبيق جلسة مختلفة لكل استدعاء، ولكل منها مساحة الاسم الخاصة بها. قد يكون هذا مفيدًا في السيناريوهات المعقدة، ولكن يجب تجنبه بشكل عام.

نشر خدمة

لجعل الخدمة قابلة للاكتشاف، يمكنك استدعاء الطريقة publish() التي تتطلّب المَعلمات التالية:

  • يحدّد PublishConfig اسم الخدمة وخصائص الإعدادات الأخرى، مثل فلتر المطابقة.
  • يحدّد DiscoverySessionCallback الإجراءات التي يجب تنفيذها عند وقوع الأحداث، مثلاً عندما يتلقّى المشترك رسالة.

وفي ما يلي مثال لذلك:

Kotlin

val config: PublishConfig = PublishConfig.Builder()
        .setServiceName(AWARE_FILE_SHARE_SERVICE_NAME)
        .build()
awareSession.publish(config, object : DiscoverySessionCallback() {

    override fun onPublishStarted(session: PublishDiscoverySession) {
        ...
    }

    override fun onMessageReceived(peerHandle: PeerHandle, message: ByteArray) {
        ...
    }
})

Java

PublishConfig config = new PublishConfig.Builder()
    .setServiceName(“Aware_File_Share_Service_Name”)
    .build();

awareSession.publish(config, new DiscoverySessionCallback() {
    @Override
    public void onPublishStarted(PublishDiscoverySession session) {
        ...
    }
    @Override
    public void onMessageReceived(PeerHandle peerHandle, byte[] message) {
        ...
    }
}, null);

إذا نجحت عملية النشر، يتم استدعاء طريقة ردّ الاتصال onPublishStarted().

بعد النشر، عندما تنتقل الأجهزة التي تشغّل تطبيقات المشتركين المطابقة إلى نطاق Wi-Fi الخاص بجهاز النشر، يكتشف المشتركون الخدمة. عندما يكتشف المشترك ناشرًا، لا يتلقّى الناشر إشعارًا، ولكن إذا أرسل المشترك رسالة إلى الناشر، يتلقّى الناشر إشعارًا. وعند حدوث ذلك، يتم استدعاء طريقة رد الاتصال onMessageReceived(). يمكنك استخدام الوسيطة PeerHandle من هذه الطريقة لإرسال رسالة إلى المشترك أو إنشاء اتصال به.

لإيقاف نشر الخدمة، يُرجى الاتصال بالرقم DiscoverySession.close(). ترتبط جلسات الاستكشاف بـ "WifiAwareSession" الرئيسي. إذا تم إغلاق الجلسة الرئيسية، سيتم أيضًا إغلاق جلسات الاستكشاف المرتبطة بها. وعلى الرغم من أنّ العناصر التي يتم تجاهلها يتم إغلاقها أيضًا، لا يضمن النظام وقت إغلاق الجلسات الخارجة عن النطاق، لذا ننصحك باستدعاء طرق close() بشكل صريح.

الاشتراك في خدمة

للاشتراك في خدمة، يمكنك استدعاء طريقة subscribe() التي تتطلّب المَعلمات التالية:

  • يحدّد SubscribeConfig اسم الخدمة المطلوب الاشتراك فيها وخصائص الإعدادات الأخرى، مثل فلتر المطابقة.
  • يحدّد DiscoverySessionCallback الإجراءات التي يجب تنفيذها عند وقوع الأحداث، مثل اكتشاف ناشر.

وفي ما يلي مثال لذلك:

Kotlin

val config: SubscribeConfig = SubscribeConfig.Builder()
        .setServiceName(AWARE_FILE_SHARE_SERVICE_NAME)
        .build()
awareSession.subscribe(config, object : DiscoverySessionCallback() {

    override fun onSubscribeStarted(session: SubscribeDiscoverySession) {
        ...
    }

    override fun onServiceDiscovered(
            peerHandle: PeerHandle,
            serviceSpecificInfo: ByteArray,
            matchFilter: List<ByteArray>
    ) {
        ...
    }
}, null)

Java

SubscribeConfig config = new SubscribeConfig.Builder()
    .setServiceName("Aware_File_Share_Service_Name")
    .build();

awareSession.subscribe(config, new DiscoverySessionCallback() {
    @Override
    public void onSubscribeStarted(SubscribeDiscoverySession session) {
        ...
    }

    @Override
    public void onServiceDiscovered(PeerHandle peerHandle,
            byte[] serviceSpecificInfo, List<byte[]> matchFilter) {
        ...
    }
}, null);

إذا نجحت عملية الاشتراك، يستدعي النظام دالة الاستدعاء onSubscribeStarted() في تطبيقك. وبما أنه يمكنك استخدام الوسيطة SubscribeDiscoverySession في دالة الاستدعاء للتواصل مع ناشر بعد أن يكتشفه تطبيقك، عليك حفظ هذا المرجع. يمكنك تعديل جلسة الاشتراك في أي وقت من خلال استدعاء updateSubscribe() في جلسة الاكتشاف.

في هذه المرحلة، ينتظر اشتراكك وصول الناشرين المطابقين إلى نطاق شبكة Wi-Fi. عند حدوث ذلك، ينفّذ النظام طريقة رد الاتصال onServiceDiscovered(). يمكنك استخدام الوسيطة PeerHandle من ردّ الاتصال هذا لإرسال رسالة أو إنشاء اتصال مع ذلك الناشر.

لإلغاء الاشتراك في خدمة، اتصل بالرقم DiscoverySession.close(). ترتبط جلسات الاستكشاف بـ "WifiAwareSession" الرئيسي. إذا تم إغلاق الجلسة الرئيسية، سيتم أيضًا إغلاق جلسات الاستكشاف المرتبطة بها. على الرغم من أنّ العناصر التي يتم تجاهلها يتم إغلاقها أيضًا، لا يضمن النظام وقت إغلاق الجلسات الخارجة عن النطاق، لذا ننصحك باستدعاء طرق close() بشكل صريح.

إرسال رسالة

لإرسال رسالة إلى جهاز آخر، تحتاج إلى العناصر التالية:

لإرسال رسالة، يُرجى الاتصال بالرقم sendMessage(). قد تحدث بعد ذلك عمليات معاودة الاتصال التالية:

  • عندما يتلقّى الجهاز الآخر الرسالة بنجاح، يستدعي النظام دالة ردّ الاتصال onMessageSendSucceeded() في التطبيق المُرسِل.
  • عندما يتلقّى الطرف الآخر رسالة، يستدعي النظام دالة الاستدعاء onMessageReceived() في التطبيق المستلِم.

على الرغم من أنّ السمة PeerHandle مطلوبة للتواصل مع الأجهزة المشابهة، يجب عدم الاعتماد عليها كمعرّف دائم للأجهزة المشابهة. يمكن استخدام المعرّفات ذات المستوى الأعلى من خلال التطبيق، سواء كانت مضمّنة في خدمة الاكتشاف نفسها أو في الرسائل اللاحقة. يمكنك تضمين معرّف في خدمة الاكتشاف باستخدام الطريقة setMatchFilter() أو setServiceSpecificInfo() من PublishConfig أو SubscribeConfig. تؤثّر طريقة setMatchFilter() في العثور على المحتوى، في حين أنّ طريقة setServiceSpecificInfo() لا تؤثّر في ذلك.

يعني تضمين معرّف في رسالة تعديل مصفوفة بايت الرسالة لتضمين معرّف (على سبيل المثال، كأول زوج من وحدات البايت).

إنشاء اتصال

تتيح ميزة Wi-Fi Aware الاتصال بين جهازين متوافقين معها من خلال شبكة خادم-عميل.

لإعداد الاتصال بين العميل والخادم:

  1. استخدِم ميزة "اكتشاف Wi-Fi Aware" لنشر خدمة (على الخادم) والاشتراك في خدمة (على العميل).

  2. بعد أن يكتشف المشترك الناشر، أرسِل رسالة من المشترك إلى الناشر.

  3. ابدأ ServerSocket على جهاز الناشر واضبط المنفذ أو احصل عليه:

    Kotlin

    val ss = ServerSocket(0)
    val port = ss.localPort

    Java

    ServerSocket ss = new ServerSocket(0);
    int port = ss.getLocalPort();
  4. استخدِم ConnectivityManager لطلب شبكة Wi-Fi Aware على الناشر باستخدام WifiAwareNetworkSpecifier، مع تحديد جلسة الاكتشاف وPeerHandle للمشترك، والتي حصلت عليها من الرسالة التي أرسلها المشترك:

    Kotlin

    val networkSpecifier = WifiAwareNetworkSpecifier.Builder(discoverySession, peerHandle)
        .setPskPassphrase("somePassword")
        .setPort(port)
        .build()
    val myNetworkRequest = NetworkRequest.Builder()
        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
        .setNetworkSpecifier(networkSpecifier)
        .build()
    val callback = object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            ...
        }
    
        override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
            ...
        }
    
        override fun onLost(network: Network) {
            ...
        }
    }
    
    connMgr.requestNetwork(myNetworkRequest, callback);

    Java

    NetworkSpecifier networkSpecifier = new WifiAwareNetworkSpecifier.Builder(discoverySession, peerHandle)
        .setPskPassphrase("somePassword")
        .setPort(port)
        .build();
    NetworkRequest myNetworkRequest = new NetworkRequest.Builder()
        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
        .setNetworkSpecifier(networkSpecifier)
        .build();
    ConnectivityManager.NetworkCallback callback = new ConnectivityManager.NetworkCallback() {
        @Override
        public void onAvailable(Network network) {
            ...
        }
    
        @Override
        public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
            ...
        }
    
        @Override
        public void onLost(Network network) {
            ...
        }
    };
    
    ConnectivityManager connMgr.requestNetwork(myNetworkRequest, callback);
  5. بعد أن يطلب الناشر شبكة، عليه إرسال رسالة إلى المشترك.

  6. بمجرد أن يتلقى المشترك الرسالة من الناشر، اطلب شبكة Wi-Fi Aware على المشترك باستخدام نفس الطريقة المتبعة مع الناشر. لا تحدِّد منفذًا عند إنشاء NetworkSpecifier. يتم استدعاء طرق رد الاتصال المناسبة عندما يكون الاتصال بالشبكة متاحًا أو عند تغييره أو انقطاعه.

  7. بعد استدعاء الإجراء onAvailable() في المشترك، يتوفّر عنصر Network يمكنك من خلاله فتح Socket للتواصل مع ServerSocket في الناشر، ولكن عليك معرفة عنوان IPv6 والمنفذ الخاصين بـ ServerSocket. يمكنك الحصول عليها من عنصر NetworkCapabilities المقدَّم في دالة الاستدعاء onCapabilitiesChanged():

    Kotlin

    val peerAwareInfo = networkCapabilities.transportInfo as WifiAwareNetworkInfo
    val peerIpv6 = peerAwareInfo.peerIpv6Addr
    val peerPort = peerAwareInfo.port
    ...
    val socket = network.getSocketFactory().createSocket(peerIpv6, peerPort)

    Java

    WifiAwareNetworkInfo peerAwareInfo = (WifiAwareNetworkInfo) networkCapabilities.getTransportInfo();
    Inet6Address peerIpv6 = peerAwareInfo.getPeerIpv6Addr();
    int peerPort = peerAwareInfo.getPort();
    ...
    Socket socket = network.getSocketFactory().createSocket(peerIpv6, peerPort);
  8. عند الانتهاء من الاتصال بالشبكة، يمكنك استدعاء unregisterNetworkCallback().

تحديد نطاق الأجهزة النظيرة واكتشاف الأجهزة التي تتعرّف على الموقع الجغرافي

يمكن للجهاز الذي يتضمّن إمكانات الموقع الجغرافي لبروتوكول RTT لشبكة Wi-Fi قياس المسافة مباشرةً إلى الأجهزة المشابهة واستخدام هذه المعلومات لتقييد اكتشاف خدمة Wi-Fi Aware.

تتيح واجهة برمجة التطبيقات Wi-Fi RTT API تحديد النطاق مباشرةً لجهاز نظير متوافق مع Wi-Fi Aware باستخدام عنوان MAC الخاص به أو PeerHandle.

يمكن حصر عملية اكتشاف Wi-Fi Aware على اكتشاف الخدمات ضمن حدود جغرافية معيّنة فقط. على سبيل المثال، يمكنك إعداد حدود جغرافية تتيح اكتشاف جهاز ينشر خدمة "Aware_File_Share_Service_Name" على مسافة لا تقل عن 3 أمتار (محدّدة بـ 3,000 ملم) ولا تزيد عن 10 أمتار (محدّدة بـ 10,000 ملم).

لتفعيل ميزة "الحدود الجغرافية"، على كلّ من الناشر والمشترك اتّخاذ الإجراءات التالية:

  • يجب أن يفعّل الناشر ميزة تحديد النطاق في الخدمة المنشورة باستخدام setRangingEnabled(true).

    إذا لم يفعّل الناشر ميزة تحديد النطاق، سيتم تجاهل أي قيود على الحدود الجغرافية يحدّدها المشترك وسيتم إجراء عملية الاكتشاف العادية، مع تجاهل المسافة.

  • يجب أن يحدّد المشترك حدودًا جغرافية باستخدام مجموعة من setMinDistanceMm وsetMaxDistanceMm.

    بالنسبة إلى أي من القيمتَين، تعني المسافة غير المحدّدة عدم وجود حدّ أقصى. ويعني تحديد الحد الأقصى للمسافة فقط أنّ الحد الأدنى للمسافة هو 0. ويعني تحديد الحد الأدنى للمسافة فقط عدم وجود حد أقصى.

عند اكتشاف خدمة نظير ضمن حدود جغرافية، يتم تشغيل دالة الاستدعاء onServiceDiscoveredWithinRange التي توفّر المسافة التي تم قياسها إلى النظير. ويمكن بعد ذلك طلب واجهة برمجة التطبيقات Wi-Fi RTT المباشرة حسب الحاجة لقياس المسافة في أوقات لاحقة.