Tổng quan về nhận biết Wi-Fi

Các chức năng Nhận biết Wi-Fi cho phép các thiết bị chạy Android 8.0 (API cấp 26) trở lên khám phá và kết nối trực tiếp với nhau mà không cần bất kỳ kiểu kết nối nào khác. Nhận biết Wi-Fi còn được gọi là Mạng nhận biết hàng xóm (NAN).

Kết nối mạng Wi-Fi Aware hoạt động bằng cách hình thành các cụm với các thiết bị lân cận hoặc bằng cách tạo một cụm mới nếu thiết bị là thiết bị đầu tiên trong một khu vực. Hành vi phân cụm này áp dụng cho toàn bộ thiết bị và do dịch vụ hệ thống Nhận biết Wi-Fi quản lý; các ứng dụng không có quyền kiểm soát hành vi phân cụm. Các ứng dụng dùng API Nhận biết Wi-Fi để giao tiếp với dịch vụ hệ thống Nhận biết Wi-Fi. Dịch vụ này quản lý phần cứng Nhận biết Wi-Fi trên thiết bị.

API Nhận biết Wi-Fi cho phép ứng dụng thực hiện các tác vụ sau:

  • Khám phá các thiết bị khác: API có cơ chế để tìm các thiết bị ở gần khác. Quá trình này bắt đầu khi một thiết bị phát hành một hoặc nhiều dịch vụ có thể khám phá. Sau đó, khi thiết bị đăng ký một hoặc nhiều dịch vụ và đi vào phạm vi Wi-Fi của nhà xuất bản, người đăng ký sẽ nhận được thông báo cho biết đã phát hiện thấy một nhà xuất bản phù hợp. Sau khi phát hiện thấy một nhà xuất bản, người đăng ký có thể gửi một tin nhắn ngắn hoặc thiết lập kết nối mạng với thiết bị đã tìm thấy. Thiết bị có thể vừa là nhà xuất bản vừa là người đăng ký.

  • Tạo kết nối mạng: Sau khi phát hiện thấy hai thiết bị còn lại, các thiết bị có thể tạo kết nối mạng Nhận biết Wi-Fi hai chiều mà không cần có điểm truy cập.

Kết nối mạng Wi-Fi Aware hỗ trợ tốc độ công suất cao hơn trên khoảng cách dài so với kết nối Bluetooth. Những loại kết nối này hữu ích cho các ứng dụng chia sẻ lượng lớn dữ liệu giữa những người dùng, chẳng hạn như ứng dụng chia sẻ ảnh.

Tính năng nâng cao cho Android 12 (API cấp 31)

Android 12 (API cấp 31) bổ sung một số tính năng nâng cao cho tính năng Nhận biết Wi-Fi:

  • Trên các thiết bị chạy Android 12 (API cấp 31) trở lên, bạn có thể sử dụng lệnh gọi lại onServiceLost() để nhận cảnh báo khi ứng dụng của bạn mất một dịch vụ đã phát hiện do dịch vụ dừng hoặc chuyển ra ngoài phạm vi.
  • Quy trình thiết lập đường dẫn dữ liệu của tính năng Nhận biết Wi-Fi đã được mô phỏng hoá. Các phiên bản trước đó sử dụng thông báo L2 để cung cấp địa chỉ MAC của trình khởi tạo, trong đó có độ trễ. Trên các thiết bị chạy Android 12 trở lên, bạn có thể định cấu hình trình phản hồi (máy chủ) để chấp nhận mọi ứng dụng ngang hàng – tức là không cần biết trước địa chỉ MAC của trình khởi tạo. Điều này giúp tăng tốc độ phân chia đường dẫn dữ liệu và cho phép liên kết nhiều điểm đến điểm chỉ bằng một yêu cầu mạng.
  • Các ứng dụng chạy trên Android 12 trở lên có thể sử dụng phương thức WifiAwareManager.getAvailableAwareResources() để lấy số đường dẫn dữ liệu hiện có, phiên phát hành và phiên đăng ký. Điều này có thể giúp ứng dụng xác định xem có đủ tài nguyên để thực thi chức năng mong muốn hay không.

Thiết lập ban đầu

Để thiết lập ứng dụng nhằm dùng tính năng khám phá và kết nối mạng bằng tính năng Nhận biết Wi-Fi, hãy thực hiện các bước sau:

  1. Yêu cầu các quyền sau đây trong tệp kê khai của ứng dụng:

    <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. Kiểm tra xem thiết bị có hỗ trợ tính năng Nhận biết Wi-Fi bằng API PackageManager hay không, như minh hoạ dưới đây:

    Kotlin

    context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE)
    

    Java

    context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);
    
  3. Kiểm tra xem hiện có Wi-Fi Aware hay chưa. Tính năng Nhận biết Wi-Fi có thể có trên thiết bị nhưng hiện không dùng được vì người dùng đã tắt Wi-Fi hoặc dịch vụ Vị trí. Tuỳ thuộc vào khả năng của phần cứng và chương trình cơ sở, một số thiết bị có thể không hỗ trợ tính năng Nhận biết Wi-Fi nếu bạn đang dùng Wi-Fi Direct, SoftAP hoặc tính năng chia sẻ Internet. Để kiểm tra xem có Wi-Fi Aware hay không, hãy gọi isAvailable().

    Khả năng cung cấp tính năng Nhận biết Wi-Fi có thể thay đổi bất cứ lúc nào. Ứng dụng của bạn nên đăng ký BroadcastReceiver để nhận ACTION_WIFI_AWARE_STATE_CHANGED. Thông báo này sẽ được gửi bất cứ khi nào tình trạng còn hàng thay đổi. Khi nhận được ý định truyền tin, ứng dụng của bạn sẽ loại bỏ tất cả các phiên hiện có (giả sử dịch vụ Nhận biết Wi-Fi đã bị gián đoạn), sau đó kiểm tra trạng thái sẵn có hiện tại và điều chỉnh hành vi của ứng dụng cho phù hợp. Ví dụ:

    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);
    

Để biết thêm thông tin, hãy xem phần Chương trình phát sóng.

Nhận một phiên

Để bắt đầu sử dụng tính năng Nhận biết Wi-Fi, ứng dụng của bạn phải có WifiAwareSession bằng cách gọi attach(). Phương thức này thực hiện những việc sau:

  • Bật phần cứng Nhận biết Wi-Fi.
  • Tham gia hoặc tạo một cụm Nhận biết Wi-Fi.
  • Tạo một phiên Nhận biết Wi-Fi có một không gian tên duy nhất, đóng vai trò là vùng chứa cho tất cả các phiên khám phá được tạo trong đó.

Nếu ứng dụng đính kèm thành công, hệ thống sẽ thực thi lệnh gọi lại onAttached(). Lệnh gọi lại này cung cấp một đối tượng WifiAwareSession mà ứng dụng của bạn nên sử dụng cho tất cả các hoạt động sau này trong phiên. Ứng dụng có thể dùng phiên hoạt động này để phát hành một dịch vụ hoặc đăng ký một dịch vụ.

Ứng dụng của bạn chỉ nên gọi attach() một lần. Nếu ứng dụng của bạn gọi attach() nhiều lần, ứng dụng sẽ nhận được một phiên riêng cho mỗi lệnh gọi, mỗi phiên có không gian tên riêng. Điều này có thể hữu ích trong các trường hợp phức tạp, nhưng thường nên tránh.

Xuất bản dịch vụ

Để giúp một dịch vụ có thể phát hiện được, hãy gọi phương thức publish(). Phương thức này lấy các thông số sau:

  • PublishConfig chỉ định tên của dịch vụ và các thuộc tính cấu hình khác, chẳng hạn như bộ lọc so khớp.
  • DiscoverySessionCallback chỉ định các thao tác cần thực thi khi sự kiện xảy ra, chẳng hạn như khi người đăng ký nhận được một tin nhắn.

Ví dụ:

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);

Nếu phát hành thành công, phương thức gọi lại onPublishStarted() sẽ được gọi.

Sau khi phát hành, khi các thiết bị đang chạy ứng dụng phù hợp dành cho người đăng ký chuyển sang phạm vi Wi-Fi của thiết bị phát hành, người đăng ký sẽ phát hiện thấy dịch vụ. Khi người đăng ký phát hiện một nhà xuất bản, thì nhà xuất bản sẽ không nhận được thông báo. Tuy nhiên, nếu người đăng ký gửi tin nhắn đến nhà xuất bản thì nhà xuất bản sẽ nhận được thông báo. Khi điều đó xảy ra, phương thức gọi lại onMessageReceived() sẽ được gọi. Bạn có thể sử dụng đối số PeerHandle từ phương thức này để gửi tin nhắn trở lại cho người đăng ký hoặc tạo kết nối với người đăng ký.

Để dừng phát hành dịch vụ, hãy gọi DiscoverySession.close(). Các phiên khám phá được liên kết với WifiAwareSession gốc. Nếu phiên gốc bị đóng, thì các phiên khám phá liên quan đến phiên đó cũng sẽ bị đóng. Mặc dù các đối tượng bị loại bỏ cũng sẽ bị đóng, nhưng hệ thống không đảm bảo sẽ đóng các phiên nằm ngoài phạm vi. Vì vậy, bạn nên gọi các phương thức close() một cách rõ ràng.

Đăng ký dịch vụ

Để đăng ký một dịch vụ, hãy gọi phương thức subscribe(). Phương thức này nhận các tham số sau:

  • SubscribeConfig chỉ định tên của dịch vụ cần đăng ký và các thuộc tính cấu hình khác, chẳng hạn như bộ lọc so khớp.
  • DiscoverySessionCallback chỉ định các hành động cần thực thi khi sự kiện xảy ra, chẳng hạn như khi phát hiện một nhà xuất bản.

Ví dụ:

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);

Nếu thao tác đăng ký thành công, hệ thống sẽ gọi lệnh gọi lại onSubscribeStarted() trong ứng dụng của bạn. Vì bạn có thể sử dụng đối số SubscribeDiscoverySession trong lệnh gọi lại để giao tiếp với một nhà xuất bản sau khi ứng dụng đã tìm thấy nhà xuất bản, bạn nên lưu tham chiếu này. Bạn có thể cập nhật phiên đăng ký bất cứ lúc nào bằng cách gọi updateSubscribe() trong phiên khám phá.

Tại thời điểm này, gói thuê bao của bạn sẽ chờ các nhà xuất bản phù hợp kết nối với phạm vi Wi-Fi. Khi điều này xảy ra, hệ thống sẽ thực thi phương thức gọi lại onServiceDiscovered(). Bạn có thể sử dụng đối số PeerHandle của lệnh gọi lại này để gửi tin nhắn hoặc tạo kết nối đến nhà xuất bản đó.

Để ngừng đăng ký một dịch vụ, hãy gọi DiscoverySession.close(). Các phiên khám phá được liên kết với WifiAwareSession gốc. Nếu phiên gốc bị đóng, thì các phiên khám phá liên quan đến phiên đó cũng sẽ bị đóng. Mặc dù các đối tượng bị loại bỏ cũng sẽ bị đóng, nhưng hệ thống không đảm bảo sẽ đóng các phiên nằm ngoài phạm vi. Vì vậy, bạn nên gọi các phương thức close() một cách rõ ràng.

Gửi tin nhắn

Để gửi thông báo đến một thiết bị khác, bạn cần có các đối tượng sau:

Để gửi tin nhắn, hãy gọi hàm sendMessage(). Sau đó, các lệnh gọi lại sau có thể xảy ra:

  • Khi ứng dụng ngang hàng nhận được tin nhắn, hệ thống sẽ gọi lệnh gọi lại onMessageSendSucceeded() trong ứng dụng gửi.
  • Khi ứng dụng ngang hàng nhận được một tin nhắn, hệ thống sẽ thực hiện lệnh gọi lại onMessageReceived() trong ứng dụng nhận.

Mặc dù PeerHandle là bắt buộc để giao tiếp với các ứng dụng ngang hàng, nhưng bạn không nên coi đây là giá trị nhận dạng vĩnh viễn của các ứng dụng ngang hàng. Các giá trị nhận dạng cấp cao hơn có thể được ứng dụng nhúng trong chính dịch vụ khám phá hoặc trong các thông báo tiếp theo. Bạn có thể nhúng giá trị nhận dạng trong dịch vụ khám phá bằng phương thức setMatchFilter() hoặc setServiceSpecificInfo() của PublishConfig hoặc SubscribeConfig. Phương thức setMatchFilter() ảnh hưởng đến hoạt động khám phá, trong khi phương thức setServiceSpecificInfo() không ảnh hưởng đến hoạt động khám phá.

Việc nhúng giá trị nhận dạng vào thông báo ngụ ý việc sửa đổi mảng byte của thông báo để đưa giá trị nhận dạng vào (ví dụ: dưới dạng một vài byte đầu tiên).

Tạo kết nối

Tính năng Wi-Fi Aware hỗ trợ kết nối mạng máy khách-máy chủ giữa hai thiết bị Nhận biết Wi-Fi.

Cách thiết lập kết nối máy khách-máy chủ:

  1. Sử dụng tính năng khám phá Nhận biết Wi-Fi để phát hành một dịch vụ (trên máy chủ) và đăng ký một dịch vụ (trên máy khách).

  2. Sau khi người đăng ký tìm thấy nhà xuất bản, hãy gửi tin nhắn từ người đăng ký tới nhà xuất bản.

  3. Bắt đầu ServerSocket trên thiết bị của nhà xuất bản và đặt hoặc lấy cổng của thiết bị đó:

    Kotlin

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

    Java

    ServerSocket ss = new ServerSocket(0);
    int port = ss.getLocalPort();
    
  4. Sử dụng ConnectivityManager để yêu cầu mạng Nhận biết Wi-Fi trên nhà xuất bản bằng cách sử dụng WifiAwareNetworkSpecifier, trong đó chỉ định phiên khám phá và PeerHandle của người đăng ký mà bạn đã nhận được từ tin nhắn do người đăng ký truyền đi:

    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. Sau khi yêu cầu mạng, nhà xuất bản sẽ gửi thông báo cho người đăng ký.

  6. Sau khi người đăng ký nhận được thông báo từ nhà xuất bản, hãy yêu cầu mạng Nhận biết Wi-Fi trên thuê bao bằng cùng một phương thức như trên nhà xuất bản. Không chỉ định cổng khi tạo NetworkSpecifier. Các phương thức gọi lại thích hợp được gọi khi có, thay đổi hoặc mất kết nối mạng.

  7. Sau khi phương thức onAvailable() được gọi trên thuê bao, bạn có thể sử dụng đối tượng Network để mở một Socket để giao tiếp với ServerSocket trên nhà xuất bản, nhưng bạn cần biết cổng và địa chỉ IPv6 của ServerSocket. Bạn nhận được các giá trị này từ đối tượng NetworkCapabilities được cung cấp trong lệnh gọi lại 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. Khi bạn đã kết nối xong mạng, hãy gọi unregisterNetworkCallback().

Khác nhau giữa các ứng dụng ngang hàng và khám phá nhận biết vị trí

Thiết bị có tính năng Vị trí Wi-Fi RTT có thể đo lường trực tiếp khoảng cách đến các thiết bị ngang hàng và sử dụng thông tin này để hạn chế việc khám phá dịch vụ Nhận biết Wi-Fi.

API RTT Wi-Fi cho phép kết nối trực tiếp với ứng dụng ngang hàng Nhận biết Wi-Fi bằng cách sử dụng địa chỉ MAC hoặc PeerHandle.

Có thể giới hạn khả năng khám phá dịch vụ Nhận biết Wi-Fi chỉ phát hiện được các dịch vụ trong một khoanh vùng địa lý cụ thể. Ví dụ: bạn có thể thiết lập khoanh vùng địa lý cho phép phát hiện một thiết bị phát hành dịch vụ "Aware_File_Share_Service_Name" không gần hơn 3 mét (được chỉ định là 3.000 mm) và không quá 10 mét (được chỉ định là 10.000 mm).

Để bật tính năng khoanh vùng địa lý, cả nhà xuất bản và người đăng ký đều phải thực hiện:

  • Nhà xuất bản phải bật phạm vi trên dịch vụ đã phát hành bằng cách sử dụng setRangingEnabled(true).

    Nếu nhà xuất bản không bật phạm vi, thì mọi giới hạn khoanh vùng địa lý do người đăng ký chỉ định sẽ bị bỏ qua và hoạt động khám phá thông thường sẽ được thực hiện, bỏ qua khoảng cách.

  • Người đăng ký phải chỉ định khoanh vùng địa lý bằng cách sử dụng một số tổ hợp setMinDistanceMmsetMaxDistanceMm.

    Đối với cả hai giá trị, khoảng cách không xác định ngụ ý không có giới hạn. Việc chỉ định khoảng cách tối đa ngụ ý khoảng cách tối thiểu là 0. Việc chỉ xác định khoảng cách tối thiểu không có nghĩa là không có khoảng cách tối đa.

Khi một dịch vụ ngang hàng được phát hiện trong một khoanh vùng địa lý, lệnh gọi lại onServiceDiscoveredOnceRange sẽ được kích hoạt để cung cấp khoảng cách đã đo được tới ứng dụng ngang hàng. Sau đó, bạn có thể gọi API RTT Wi-Fi trực tiếp khi cần để đo lường khoảng cách vào những thời điểm sau.