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

    Java

    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의 사용 가능 여부는 언제든지 변경될 수 있습니다. 앱에서 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 사용을 시작하려면 앱에서 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) {
        ...
    }
})

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() 메서드를 명시적으로 호출하는 것이 좋습니다.

메시지 보내 줘

다른 기기에 메시지를 보내려면 다음 객체가 필요합니다.

메시지를 보내려면 sendMessage()를 호출합니다. 그러면 다음과 같은 콜백이 발생할 수 있습니다.

  • 메시지가 동종 기기에 의해 성공적으로 수신되면 시스템은 전송 앱에서 onMessageSendSucceeded() 콜백을 호출합니다.
  • 동종 앱이 메시지를 수신하면 시스템은 수신 앱에서 onMessageReceived() 콜백을 호출합니다.

PeerHandle는 동종 기기와 통신하는 데 필요하지만 동종 기기의 영구 식별자로 신뢰해서는 안 됩니다. 상위 수준 식별자는 애플리케이션에서 사용할 수 있으며 탐색 서비스 자체 또는 후속 메시지에 삽입됩니다. PublishConfig 또는 SubscribeConfigsetMatchFilter() 또는 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
    

    Java

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

    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() 메서드가 호출되면 Socket를 열어 게시자의 ServerSocket와 통신할 수 있는 Network 객체를 사용할 수 있지만 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)
    

    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를 사용하면 MAC 주소 또는 PeerHandle을 사용하여 Wi-Fi Aware 동종 앱의 범위를 직접 지정할 수 있습니다.

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

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

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

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

  • 구독자는 setMinDistanceMmsetMaxDistanceMm의 몇 가지 조합을 사용하여 지오펜싱을 지정해야 합니다.

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

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