סקירה כללית על Wi-Fi Aware

היכולות של Wi-Fi Aware מאפשרות למכשירים עם Android 8.0 (רמת API‏ 26) ומעלה לגלות מכשירים אחרים ולהתחבר אליהם ישירות, ללא צורך בחיבור מסוג אחר ביניהם. ‫Wi-Fi Aware נקרא גם Neighbor Awareness Networking‏ (NAN).

רשתות Wi-Fi Aware פועלות על ידי יצירת אשכולות עם מכשירים סמוכים, או על ידי יצירת אשכול חדש אם המכשיר הוא הראשון באזור. התנהגות האשכול הזו חלה על המכשיר כולו ומנוהלת על ידי שירות המערכת Wi-Fi Aware. לאפליקציות אין שליטה על התנהגות האשכול. האפליקציות משתמשות בממשקי ה-API של Wi-Fi Aware כדי לתקשר עם שירות המערכת Wi-Fi Aware, שמנהל את חומרת Wi-Fi Aware במכשיר.

ממשקי ה-API של Wi-Fi Aware מאפשרים לאפליקציות לבצע את הפעולות הבאות:

  • איתור מכשירים אחרים: לממשק ה-API יש מנגנון לאיתור מכשירים אחרים שנמצאים בקרבת מקום. התהליך מתחיל כשמכשיר אחד מפרסם שירות אחד או יותר שאפשר לגלות. לאחר מכן, כשמכשיר נרשם למינוי של שירות אחד או יותר ונכנס לטווח ה-Wi-Fi של בעל התוכן הדיגיטלי, המנוי מקבל התראה על כך שזוהה בעל תוכן דיגיטלי תואם. אחרי שהמנוי מגלה את בעל התוכן הדיגיטלי, הוא יכול לשלוח הודעה קצרה או ליצור חיבור לרשת עם המכשיר שהתגלה. מכשירים יכולים להיות גם מוציאים לאור וגם מנויים בו-זמנית.

  • יצירת חיבור לרשת: אחרי ששני מכשירים מגלים אחד את השני, הם יכולים ליצור חיבור לרשת Wi-Fi Aware דו-כיוונית ללא נקודת גישה.

חיבורים לרשתות Wi-Fi Aware תומכים בקצבי העברה גבוהים יותר למרחקים ארוכים יותר מאשר חיבורי Bluetooth. סוגי החיבורים האלה שימושיים לאפליקציות שמשתפות כמויות גדולות של נתונים בין משתמשים, כמו אפליקציות לשיתוף תמונות.

שיפורים ב-Android 13 (רמת API‏ 33)

במכשירים שמותקנת בהם גרסת Android 13 (רמת API‏ 33) ומעלה שתומכים במצב תקשורת מיידית, אפליקציות יכולות להשתמש בשיטות PublishConfig.Builder.setInstantCommunicationModeEnabled() ו-SubscribeConfig.Builder.setInstantCommunicationModeEnabled() כדי להפעיל או להשבית את מצב התקשורת המיידית בסשן של גילוי מפרסם או מנוי. מצב תקשורת מיידית מזרז את חילופי ההודעות, את גילוי השירותים ואת ההגדרה של נתיב הנתונים כחלק מהפעלת גילוי של מפרסם או מנוי. כדי לקבוע אם מכשיר תומך במצב תקשורת מיידית, משתמשים בשיטה isInstantCommunicationModeSupported().

שיפורים ב-Android 12 (רמת API ‏31)

ב-Android 12 (רמת API‏ 31) נוספו שיפורים ל-Wi-Fi Aware:

  • במכשירים עם Android 12 (רמת API‏ 31) ומעלה, אפשר להשתמש בקריאה החוזרת (callback) של onServiceLost() כדי לקבל התראה כשהאפליקציה מאבדת שירות שזוהה בגלל שהשירות מפסיק לפעול או יוצא מטווח הקליטה.
  • הגדרת נתיבי נתונים ב-Wi-Fi Aware פשוטה יותר. בגרסאות קודמות נעשה שימוש בהעברת הודעות ברמה 2 כדי לספק את כתובת ה-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 באמצעות ה-API‏ 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 Direct, ב-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 של המכשיר המפרסם, המנויים מגלים את השירות. כשמשתמש רשום מגלה אתר חדשות, האתר לא מקבל על כך הודעה. עם זאת, אם המשתמש הרשום שולח הודעה לאתר, האתר מקבל הודעה. במקרה כזה, המערכת קוראת ל-callback method‏ onMessageReceived(). אפשר להשתמש בארגומנט PeerHandle מהשיטה הזו כדי לשלוח הודעה בחזרה למנוי או ליצור חיבור אליו.

כדי להפסיק את הפרסום של השירות, צריך להתקשר אל DiscoverySession.close(). סשנים של גילוי משויכים לרכיב ההורה שלהם, WifiAwareSession. אם הסשן הראשי נסגר, גם סשנים של גילוי שמשויכים אליו נסגרים. גם אובייקטים שהוצאו משימוש נסגרים, אבל המערכת לא מבטיחה מתי סשנים שלא נמצאים בהיקף נסגרים, ולכן מומלץ לקרוא באופן מפורש לשיטות close().

הרשמה למינוי לשירות

כדי להירשם לשירות, מפעילים את method‏ 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 של המנוי מהקריאה החוזרת (callback) של onMessageReceived().
    • האפליקציה שלך נרשמת לשירות. לאחר מכן, כשהמערכת מזהה בעל תוכן דיגיטלי תואם, האפליקציה מקבלת את PeerHandle של בעל התוכן הדיגיטלי מהקריאה החוזרת (callback) של onServiceDiscovered().

כדי לשלוח הודעה, מתקשרים אל sendMessage(). אחרי זה יכולות להתבצע הקריאות החוזרות (callback) הבאות:

  • כשההודעה מתקבלת בהצלחה על ידי העמית, המערכת קוראת ל-callback באפליקציית השליחה.onMessageSendSucceeded()
  • כשהעמית מקבל הודעה, המערכת קוראת ל-callback‏ onMessageReceived() באפליקציה המקבלת.

למרות שנדרש PeerHandle כדי לתקשר עם עמיתים, לא מומלץ להסתמך עליו כמזהה קבוע של עמיתים. האפליקציה יכולה להשתמש במזהים ברמה גבוהה יותר – הם מוטמעים בשירות הגילוי עצמו או בהודעות הבאות. אפשר להטמיע מזהה בשירות הגילוי באמצעות השיטה setMatchFilter() או setServiceSpecificInfo() של PublishConfig או SubscribeConfig. השיטה setMatchFilter() משפיעה על הגילוי, ואילו השיטה setServiceSpecificInfo() לא משפיעה על הגילוי.

הטמעה של מזהה בהודעה מרמזת על שינוי של מערך הבייטים של ההודעה כדי לכלול מזהה (למשל, בתור כמה הבייטים הראשונים).

יצירת חיבור

‫Wi-Fi Aware תומך ברשתות לקוח-שרת בין שני מכשירי 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().

הגדרת טווח של מכשירים סמוכים וגילוי מודע-מיקום

מכשיר עם יכולות מיקום באמצעות Wi-Fi RTT יכול למדוד ישירות את המרחק ממכשירים אחרים ולהשתמש במידע הזה כדי להגביל את גילוי השירותים של 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. אם מציינים רק את המרחק המינימלי, המשמעות היא שאין מרחק מקסימלי.

כשמתגלה שירות עמית בתוך גדר וירטואלית, מופעלת קריאה חוזרת (callback) של onServiceDiscoveredWithinRange, שמספקת את המרחק שנמדד עד לעמית. לאחר מכן אפשר להפעיל את ה-API הישיר של Wi-Fi RTT לפי הצורך כדי למדוד מרחק בשלבים מאוחרים יותר.