تتيح إمكانات 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، اتّبِع الخطوات التالية:
اطلب الأذونات التالية في ملف بيان تطبيقك:
<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" />تحقَّق مما إذا كان الجهاز يتيح استخدام Wi-Fi Aware من خلال واجهة برمجة التطبيقات
PackageManager:Kotlin
context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE)Java
context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);تحقَّق مما إذا كانت ميزة 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() بشكل صريح.
إرسال رسالة
لإرسال رسالة إلى جهاز آخر، تحتاج إلى العناصر التالية:
DiscoverySessionيتيح لك هذا العنصر الاتصال بـsendMessage(). يحصل تطبيقك علىDiscoverySessionإما من خلال نشر خدمة أو الاشتراك في خدمة.PeerHandleللجهاز الآخر، لتوجيه الرسالة يحصل تطبيقك علىPeerHandleلجهاز آخر بإحدى الطريقتَين التاليتَين:- ينشر تطبيقك خدمة ويتلقّى رسالة من أحد المشتركين.
يحصل تطبيقك على
PeerHandleالخاص بالمشترك من خلال دالة الاستدعاءonMessageReceived(). - يشترك تطبيقك في خدمة. بعد ذلك، عندما يكتشف تطبيقك ناشرًا مطابقًا، يحصل على
PeerHandleالخاص بالناشر من دالة الاستدعاءonServiceDiscovered().
- ينشر تطبيقك خدمة ويتلقّى رسالة من أحد المشتركين.
يحصل تطبيقك على
لإرسال رسالة، يُرجى الاتصال بالرقم sendMessage(). قد تحدث بعد ذلك عمليات الاستدعاء التالية:
- عندما يتلقّى الجهاز الآخر الرسالة بنجاح، يستدعي النظام دالة ردّ الاتصال
onMessageSendSucceeded()في التطبيق المُرسِل. - عندما يتلقّى الطرف الآخر رسالة، يستدعي النظام دالة الاستدعاء
onMessageReceived()في التطبيق المستلِم.
على الرغم من أنّ PeerHandle مطلوب للتواصل مع الأجهزة الأخرى، يجب عدم
الاعتماد عليه كمعرّف دائم للأجهزة الأخرى. يمكن لتطبيقك استخدام معرّفات ذات مستوى أعلى، سواء كانت مضمّنة في خدمة الاكتشاف نفسها أو في الرسائل اللاحقة. يمكنك تضمين معرّف في خدمة الاكتشاف باستخدام الطريقة
setMatchFilter() أو setServiceSpecificInfo() من PublishConfig
أو SubscribeConfig. تؤثر طريقة setMatchFilter() في عملية الاكتشاف، بينما لا تؤثر طريقة setServiceSpecificInfo() في عملية الاكتشاف.
يعني تضمين معرّف في رسالة تعديل مصفوفة بايت الرسالة لتضمين معرّف (على سبيل المثال، كأول زوج من وحدات البايت).
إنشاء اتصال
تتيح ميزة Wi-Fi Aware إنشاء شبكة بين جهازَين متوافقَين معها، أحدهما يعمل كعميل والآخر كخادم.
لإعداد الاتصال بين العميل والخادم:
استخدِم ميزة "اكتشاف Wi-Fi Aware" لنشر خدمة والاشتراك في خدمة.
بعد أن يكتشف المشترك الناشر، أرسِل رسالة من المشترك إلى الناشر.
ابدأ
ServerSocketعلى جهاز الناشر واضبط المنفذ أو احصل عليه:Kotlin
val ss = ServerSocket(0) val port = ss.localPortJava
ServerSocket ss = new ServerSocket(0); int port = ss.getLocalPort();استخدِم
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) { ... } }; connMgr.requestNetwork(myNetworkRequest, callback);بعد أن يطلب الناشر شبكة، أرسِل رسالة إلى المشترك.
بعد أن يتلقّى المشترك الرسالة من الناشر، اطلب شبكة Wi-Fi Aware على المشترك باستخدام الطريقة نفسها المتّبعة مع الناشر. لا تحدِّد منفذًا عند إنشاء
NetworkSpecifier. يتم استدعاء طرق رد الاتصال المناسبة عندما يكون الاتصال بالشبكة متاحًا أو يتغيّر أو ينقطع.بعد استدعاء طريقة
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);عند الانتهاء من الاتصال بالشبكة، يمكنك استدعاء
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 حسب
الحاجة لقياس المسافة في أوقات لاحقة.