Wi-Fi Aware 개요

Wi-Fi Aware 기능을 사용하면 Android 8.0 (API 수준 26) 이상을 실행하는 기기에서 다른 유형의 연결 없이 서로를 검색하고 직접 연결할 수 있습니다. Wi-Fi Aware는 Neighbor Awareness Networking (NAN)이라고도 합니다.

Wi-Fi Aware 네트워킹은 주변 기기와 클러스터를 형성하거나 기기가 해당 영역에서 첫 번째 기기인 경우 새 클러스터를 생성하는 방식으로 작동합니다. 이 클러스터링 동작은 전체 기기에 적용되며 Wi-Fi Aware 시스템 서비스에서 관리합니다. 앱은 클러스터링 동작을 제어할 수 없습니다. 앱은 Wi-Fi Aware API를 사용하여 기기의 Wi-Fi Aware 하드웨어를 관리하는 Wi-Fi Aware 시스템 서비스와 통신합니다.

Wi-Fi Aware API를 통해 앱은 다음 작업을 할 수 있습니다.

  • 다른 기기 검색: API에는 근처에 있는 다른 기기를 찾는 메커니즘이 있습니다. 이 프로세스는 한 기기가 검색 가능한 서비스를 하나 이상 게시할 때 시작됩니다. 그런 다음 기기가 하나 이상의 서비스를 구독하고 게시자의 Wi-Fi 범위에 들어가면 구독자는 일치하는 게시자가 발견되었다는 알림을 받습니다. 구독자가 게시자를 검색한 후 구독자는 짧은 메시지를 보내거나 검색된 기기와 네트워크 연결을 설정할 수 있습니다. 기기는 동시에 게시자와 구독자 모두가 될 수 있습니다.

  • 네트워크 연결 만들기: 두 기기가 서로를 검색한 후 액세스 포인트 없이 양방향 Wi-Fi Aware 네트워크 연결을 만들 수 있습니다.

Wi-Fi Aware 네트워크 연결은 블루투스 연결보다 더 먼 거리에서 더 높은 처리량을 지원합니다. 이러한 유형의 연결은 사진 공유 앱과 같이 사용자 간에 대량의 데이터를 공유하는 앱에 유용합니다.

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) 이상을 실행하는 기기에서는 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. 아래와 같이 PackageManager API를 사용하여 기기가 Wi-Fi Aware를 지원하는지 확인합니다.

    Kotlin

    context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE)

    자바

    context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);
  3. Wi-Fi Aware가 현재 사용 가능한지 확인합니다. Wi-Fi Aware가 기기에 있을 수 있지만 사용자가 Wi-Fi 또는 위치를 사용 중지했기 때문에 현재 사용하지 못할 수 있습니다. 하드웨어 및 펌웨어 기능에 따라 일부 기기는 Wi-Fi Direct, SoftAP 또는 테더링이 사용 중인 경우 Wi-Fi Aware를 지원하지 않을 수 있습니다. Wi-Fi Aware를 현재 사용할 수 있는지 확인하려면 isAvailable()를 호출합니다.

    Wi-Fi Aware의 사용 가능 여부는 언제든지 변경될 수 있습니다. 앱은 사용 가능 여부가 변경될 때마다 전송되는 ACTION_WIFI_AWARE_STATE_CHANGED를 수신하도록 BroadcastReceiver를 등록해야 합니다. 앱이 브로드캐스트 인텐트를 수신하면 기존 세션을 모두 삭제하고 (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)

    자바

    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를 사용하려면 앱에서 attach()를 호출하여 WifiAwareSession를 가져와야 합니다. 이 메서드는 다음을 실행합니다.

  • 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) {
        ...
    }
})

자바

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)

자바

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는 피어와 통신하는 데 필요하지만 피어의 영구 식별자로 사용해서는 안 됩니다. 상위 수준 식별자는 검색 서비스 자체 또는 후속 메시지에 삽입된 애플리케이션에서 사용할 수 있습니다. PublishConfig 또는 SubscribeConfig의 setMatchFilter() 또는 setServiceSpecificInfo() 메서드를 사용하여 탐색 서비스에 식별자를 삽입할 수 있습니다. setMatchFilter() 메서드는 탐색에 영향을 미치지만 setServiceSpecificInfo() 메서드는 탐색에 영향을 미치지 않습니다.

메시지에 식별자를 삽입한다는 것은 식별자를 포함하도록 메시지 바이트 배열을 수정하는 것을 의미합니다 (예: 처음 몇 바이트).

연결 만들기

Wi-Fi Aware는 두 Wi-Fi Aware 기기 간의 클라이언트-서버 네트워킹을 지원합니다.

클라이언트-서버 연결을 설정하려면 다음 단계를 따르세요.

  1. Wi-Fi Aware 검색을 사용하여 (서버에서) 서비스를 게시하고 (클라이언트에서) 서비스를 구독합니다.

  2. 구독자가 게시자를 검색하면 구독자에서 게시자로 메시지를 전송합니다.

  3. 게시자 기기에서 ServerSocket를 시작하고 포트를 설정하거나 가져옵니다.

    Kotlin

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

    자바

    ServerSocket ss = new ServerSocket(0);
    int port = ss.getLocalPort();
  4. ConnectivityManager를 사용하여 WifiAwareNetworkSpecifier를 통해 게시자에게 Wi-Fi Aware 네트워크를 요청합니다. 이때 구독자가 전송한 메시지에서 가져온 구독자의 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);

    자바

    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와 통신할 수 있습니다. 하지만 ServerSocket의 IPv6 주소와 포트를 알아야 합니다. onCapabilitiesChanged() 콜백에 제공된 NetworkCapabilities 객체에서 가져옵니다.

    Kotlin

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

    자바

    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를 사용하면 MAC 주소 또는 PeerHandle을 사용하여 Wi-Fi Aware 피어에 직접 연결할 수 있습니다.

Wi-Fi Aware 검색은 특정 지오펜스 내의 서비스만 검색하도록 제한할 수 있습니다. 예를 들어 3미터 (3,000mm로 지정)보다 가깝지 않고 10미터(10,000mm로 지정)보다 멀지 않은 "Aware_File_Share_Service_Name" 서비스를 게시하는 기기를 검색할 수 있는 지오펜스를 설정할 수 있습니다.

지오펜싱을 사용하도록 설정하려면 게시자와 구독자 모두에서 다음 조치를 취해야 합니다.

  • 게시자는 setRangingEnabled(true)를 사용하여 게시된 서비스에서 범위 지정을 사용 설정해야 합니다.

    게시자가 범위 지정을 사용 설정하지 않으면 구독자가 지정한 지오펜스 제약조건이 무시되고 거리를 무시하고 일반 검색이 실행됩니다.

  • 구독자는 setMinDistanceMm과 setMaxDistanceMm의 조합을 사용하여 지오펜스를 지정해야 합니다.

    두 값 모두 지정하지 않으면 거리에 제한이 없음을 의미합니다. 최대 거리만 지정하면 최소 거리는 0으로 간주됩니다. 최소 거리만 지정하면 최댓값이 없음을 의미합니다.

지오펜스 내에서 피어 서비스가 발견되면 onServiceDiscoveredWithinRange 콜백이 트리거되어 피어까지의 측정된 거리를 제공합니다. 그런 다음 필요에 따라 직접 Wi-Fi RTT API를 호출하여 나중에 거리를 측정할 수 있습니다.