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

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

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

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

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

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

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

تحسينات على نظام التشغيل Android 12 (المستوى 31)

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

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

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

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

  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 على الجهاز، ولكنّها قد لا تكون متاحة حاليًا لأنّ المستخدم أوقف شبكة Wi-Fi أو الموقع الجغرافي. بناءً على إمكانات الأجهزة والبرامج الثابتة، قد لا تتوافق بعض الأجهزة مع ميزة Wi-Fi Aware إذا كانت هذه الميزة مستخدَمة في كل من Wi-Fi Direct أو SoftAP أو التوصيل. للتحقّق مما إذا كانت خدمة Wi-Fi Aware متاحة حاليًا، يمكنك الاتصال على الرقم isAvailable().

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

    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() صراحةً.

إرسال رسالة

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

  • DiscoverySession: يتيح لك هذا الكائن استدعاء sendMessage(). يحصل تطبيقك على DiscoverySession عن طريق نشر خدمة أو الاشتراك في خدمة.

  • PeerHandle على الجهاز الآخر لتوجيه الرسالة. يحصل تطبيقك على PeerHandle على جهاز آخر بإحدى الطريقتَين التاليتَين:

    • ينشر تطبيقك خدمة ويتلقّى رسالة من أحد المشتركين. يحصل تطبيقك على PeerHandle للمشترك من خلال معاودة الاتصال بـ "onMessageReceived()".
    • اشتراك تطبيقك في إحدى الخدمات وعندما ترصد هذه الإضافة ناشرًا مطابقًا، يحصل تطبيقك على PeerHandle للناشر من خلال معاودة الاتصال في onServiceDiscovered().

لإرسال رسالة، اتصل بالرقم sendMessage(). قد تحدث عندئذٍ عمليات معاودة الاتصال التالية:

  • عندما يتلقى النظير الرسالة بنجاح، يطلب النظام معاودة الاتصال بـ onMessageSendSucceeded() ضمن تطبيق الإرسال.
  • عندما يتلقّى الجهاز المشابه رسالة، يطلب النظام onMessageReceived()من خلال تطبيق الاستلام.

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

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

إنشاء عملية ربط

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

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

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

  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 من الناشر باستخدام 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 من المشترك باستخدام الطريقة نفسها المستخدَمة على ذلك الناشر. لا تحدِّد منفذًا عند إنشاء 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().

تصنيف التطبيقات المشابهة والاكتشاف المستند إلى الموقع

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

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

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

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

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

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

  • يجب أن يحدد المشترك حدودًا جغرافية باستخدام سمتَي setMinSpaceMm وsetMaxمسافةMm.

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

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