Wi-Fi Direct로 P2P 연결 만들기

Wi-Fi Direct(P2P라고도 함)를 사용하면 애플리케이션이 블루투스의 기능을 넘어선 범위에서 빠르게 주변 기기를 찾고 상호작용할 수 있습니다.

Wi-Fi P2P API를 사용하면 애플리케이션아 네트워크나 핫스팟에 연결할 필요 없이 주변 기기에 연결할 수 있습니다. 앱이 안전한 근거리 네트워크의 일부로 설계된 경우 다음과 같은 이유로 Wi-Fi Direct가 일반 Wi-Fi 임시 네트워킹보다 더 적합한 옵션입니다.

  • Wi-Fi Direct는 WPA2 암호화를 지원합니다. (일부 임시 네트워크는 WEP 암호화만을 지원합니다.)
  • 기기에서 제공하는 서비스를 브로드캐스트할 수 있으며 이에 따라 다른 기기에서 동종 기기를 더 쉽게 찾을 수 있습니다.
  • 네트워크의 그룹 소유자가 될 기기를 결정할 때 Wi-Fi Direct는 각 기기의 전원 관리, UI 및 서비스 기능을 조사하고 이 정보를 사용하여 서버의 책임을 가장 효과적으로 처리할 수 있는 기기를 선택합니다.
  • Android는 Wi-Fi 임시 모드를 지원하지 않습니다.

이 과정에서는 Wi-Fi P2P를 사용하여 주변 기기를 찾고 연결하는 방법을 보여줍니다.

애플리케이션 권한 설정

Wi-Fi P2P를 사용하려면 ACCESS_FINE_LOCATION, CHANGE_WIFI_STATE, ACCESS_WIFI_STATEINTERNET 권한을 manifest에 추가하세요. Wi-Fi P2P에는 인터넷 연결이 필요하지 않지만 INTERNET 권한이 필요한 표준 자바 소켓을 사용합니다. 따라서 Wi-Fi P2P를 사용하려면 다음 권한이 필요합니다.

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.android.nsdchat"
        ...
        <uses-permission
            android:required="true"
            android:name="android.permission.ACCESS_FINE_LOCATION"/>
        <uses-permission
            android:required="true"
            android:name="android.permission.ACCESS_WIFI_STATE"/>
        <uses-permission
            android:required="true"
            android:name="android.permission.CHANGE_WIFI_STATE"/>
        <uses-permission
            android:required="true"
            android:name="android.permission.INTERNET"/>
        ...
    

위의 권한 외에 다음 API에서 위치 모드를 사용 설정해야 합니다.

broadcast receiver 및 P2P 관리자 설정

Wi-Fi P2P를 사용하려면 특정 이벤트가 발생했을 때 애플리케이션에 알려주는 브로드캐스트 인텐트를 수신 대기해야 합니다. 애플리케이션에서 IntentFilter를 인스턴스화하고 다음 인텐트를 수신 대기하도록 설정하세요.

WIFI_P2P_STATE_CHANGED_ACTION
Wi-Fi P2P가 사용 설정되었는지를 나타냅니다.
WIFI_P2P_PEERS_CHANGED_ACTION
사용 가능한 동종 기기 목록이 변경되었음을 나타냅니다.
WIFI_P2P_CONNECTION_CHANGED_ACTION
Wi-Fi P2P 연결 상태가 변경되었음을 나타냅니다. Android 10부터는 고정되지 않습니다. 브로드캐스트가 고정되었기 때문에 등록 시 앱에서 이러한 브로드캐스트를 수신하는 데 의존한 경우 대신에 초기화 시 적절한 get 메서드를 사용하여 정보를 얻습니다.
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION
이 기기의 구성 세부정보가 변경되었음을 나타냅니다. Android 10부터는 고정되지 않습니다. 브로드캐스트가 고정되었기 때문에 등록 시 앱에서 이러한 브로드캐스트를 수신하는 데 의존한 경우 대신에 초기화 시 적절한 get 메서드를 사용하여 정보를 얻습니다.

Kotlin

    private val intentFilter = IntentFilter()
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)

        // Indicates a change in the Wi-Fi P2P status.
        intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)

        // Indicates a change in the list of available peers.
        intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)

        // Indicates the state of Wi-Fi P2P connectivity has changed.
        intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)

        // Indicates this device's details have changed.
        intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION)
        ...
    }
    

자바

    private final IntentFilter intentFilter = new IntentFilter();
    ...
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // Indicates a change in the Wi-Fi P2P status.
        intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);

        // Indicates a change in the list of available peers.
        intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);

        // Indicates the state of Wi-Fi P2P connectivity has changed.
        intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);

        // Indicates this device's details have changed.
        intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
        ...
    }
    

onCreate() 메서드가 끝나면 WifiP2pManager의 인스턴스를 가져와 initialize() 메서드를 호출하세요. 이 메서드는 WifiP2pManager.Channel 개체를 반환하며 이 개체는 나중에 앱을 Wi-Fi P2P 프레임워크에 연결하는 데 사용됩니다.

Kotlin

    private lateinit var channel: WifiP2pManager.Channel
    private lateinit var manager: WifiP2pManager

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        manager = getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager
        channel = manager.initialize(this, mainLooper, null)
    }
    

자바

    Channel channel;
    WifiP2pManager manager;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        ...
        manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
        channel = manager.initialize(this, getMainLooper(), null);
    }
    

이제 시스템의 Wi-Fi P2P 상태 변경사항을 수신 대기하는 데 사용할 새 BroadcastReceiver 클래스를 만듭니다. onReceive() 메서드에서 위에 나열된 각 P2P 상태 변경사항을 처리하기 위한 조건을 추가하세요.

Kotlin

    override fun onReceive(context: Context, intent: Intent) {
        when(intent.action) {
            WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> {
                // Determine if Wifi P2P mode is enabled or not, alert
                // the Activity.
                val state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1)
                activity.isWifiP2pEnabled = state == WifiP2pManager.WIFI_P2P_STATE_ENABLED
            }
            WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> {

                // The peer list has changed! We should probably do something about
                // that.

            }
            WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> {

                // Connection state changed! We should probably do something about
                // that.

            }
            WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION -> {
                (activity.supportFragmentManager.findFragmentById(R.id.frag_list) as DeviceListFragment)
                        .apply {
                            updateThisDevice(
                                    intent.getParcelableExtra(
                                            WifiP2pManager.EXTRA_WIFI_P2P_DEVICE) as WifiP2pDevice
                            )
                        }
            }
        }
    }
    

자바

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
            // Determine if Wifi P2P mode is enabled or not, alert
            // the Activity.
            int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);
            if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) {
                activity.setIsWifiP2pEnabled(true);
            } else {
                activity.setIsWifiP2pEnabled(false);
            }
        } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {

            // The peer list has changed! We should probably do something about
            // that.

        } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {

            // Connection state changed! We should probably do something about
            // that.

        } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
            DeviceListFragment fragment = (DeviceListFragment) activity.getFragmentManager()
                    .findFragmentById(R.id.frag_list);
            fragment.updateThisDevice((WifiP2pDevice) intent.getParcelableExtra(
                    WifiP2pManager.EXTRA_WIFI_P2P_DEVICE));

        }
    }
    

마지막으로 기본 활동이 활성화되면 인텐트 필터와 broadcast receiver를 등록하고 활동이 일시중지되면 등록 취소하는 코드를 추가하세요. 코드를 추가하기 가장 좋은 장소는 onResume()onPause() 메서드입니다.

Kotlin

    /** register the BroadcastReceiver with the intent values to be matched  */
    public override fun onResume() {
        super.onResume()
        receiver = WiFiDirectBroadcastReceiver(manager, channel, this)
        registerReceiver(receiver, intentFilter)
    }

    public override fun onPause() {
        super.onPause()
        unregisterReceiver(receiver)
    }
    

자바

    /** register the BroadcastReceiver with the intent values to be matched */
    @Override
    public void onResume() {
        super.onResume();
        receiver = new WiFiDirectBroadcastReceiver(manager, channel, this);
        registerReceiver(receiver, intentFilter);
    }

    @Override
    public void onPause() {
        super.onPause();
        unregisterReceiver(receiver);
    }
    

동종 기기 검색 시작

Wi-Fi P2P를 사용하여 주변 기기를 검색하려면 discoverPeers()를 호출하세요. 이 메서드에는 다음 매개변수가 사용됩니다.

Kotlin

    manager.discoverPeers(channel, object : WifiP2pManager.ActionListener {

        override fun onSuccess() {
            // Code for when the discovery initiation is successful goes here.
            // No services have actually been discovered yet, so this method
            // can often be left blank. Code for peer discovery goes in the
            // onReceive method, detailed below.
        }

        override fun onFailure(reasonCode: Int) {
            // Code for when the discovery initiation fails goes here.
            // Alert the user that something went wrong.
        }
    })
    

자바

    manager.discoverPeers(channel, new WifiP2pManager.ActionListener() {

        @Override
        public void onSuccess() {
            // Code for when the discovery initiation is successful goes here.
            // No services have actually been discovered yet, so this method
            // can often be left blank. Code for peer discovery goes in the
            // onReceive method, detailed below.
        }

        @Override
        public void onFailure(int reasonCode) {
            // Code for when the discovery initiation fails goes here.
            // Alert the user that something went wrong.
        }
    });
    

동종 기기 검색만 시작됩니다. discoverPeers() 메서드는 검색 프로세스를 시작한 다음 즉시 반환합니다. 시스템이 제공된 작업 리스너에서 메서드를 호출하여 동종 기기 검색 프로세스가 시작되었는지 알려줍니다. 또한 연결이 시작되거나 P2P 그룹이 형성될 때까지 검색이 활성 상태로 유지됩니다.

동종 기기 목록 가져오기

이제 동종 기기 목록을 가져오고 처리하는 코드를 작성하세요. 먼저 Wi-Fi P2P에서 감지한 동종 기기에 관한 정보를 제공하는 WifiP2pManager.PeerListListener 인터페이스를 구현합니다. 또한 이 정보를 사용하면 앱에서 동종 기기가 네트워크에 들어오거나 나가는 시기를 알 수 있습니다. 다음 코드 스니펫은 동종 기기와 관련된 이러한 작업을 보여줍니다.

Kotlin

    private val peers = mutableListOf<WifiP2pDevice>()
    ...

    private val peerListListener = WifiP2pManager.PeerListListener { peerList ->
        val refreshedPeers = peerList.deviceList
        if (refreshedPeers != peers) {
            peers.clear()
            peers.addAll(refreshedPeers)

            // If an AdapterView is backed by this data, notify it
            // of the change. For instance, if you have a ListView of
            // available peers, trigger an update.
            (listAdapter as WiFiPeerListAdapter).notifyDataSetChanged()

            // Perform any other updates needed based on the new list of
            // peers connected to the Wi-Fi P2P network.
        }

        if (peers.isEmpty()) {
            Log.d(TAG, "No devices found")
            return@PeerListListener
        }
    }
    

자바

    private List<WifiP2pDevice> peers = new ArrayList<WifiP2pDevice>();
    ...

    private PeerListListener peerListListener = new PeerListListener() {
        @Override
        public void onPeersAvailable(WifiP2pDeviceList peerList) {

            List<WifiP2pDevice> refreshedPeers = peerList.getDeviceList();
            if (!refreshedPeers.equals(peers)) {
                peers.clear();
                peers.addAll(refreshedPeers);

                // If an AdapterView is backed by this data, notify it
                // of the change. For instance, if you have a ListView of
                // available peers, trigger an update.
                ((WiFiPeerListAdapter) getListAdapter()).notifyDataSetChanged();

                // Perform any other updates needed based on the new list of
                // peers connected to the Wi-Fi P2P network.
            }

            if (peers.size() == 0) {
                Log.d(WiFiDirectActivity.TAG, "No devices found");
                return;
            }
        }
    }
    

이제 WIFI_P2P_PEERS_CHANGED_ACTION 작업이 포함된 인텐트가 수신될 때 requestPeers()를 호출하도록 broadcast receiver의 onReceive() 메서드를 수정하세요. 어떻게든 이 리스너를 발신자에게 전달해야 합니다. 한 가지 방법은 리스너를 broadcast receiver의 생성자에 인수로 보내는 것입니다.

Kotlin

    fun onReceive(context: Context, intent: Intent) {
        when (intent.action) {
            ...
            WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> {

                // Request available peers from the wifi p2p manager. This is an
                // asynchronous call and the calling activity is notified with a
                // callback on PeerListListener.onPeersAvailable()
                mManager?.requestPeers(channel, peerListListener)
                Log.d(TAG, "P2P peers changed")

            }
            ...
        }
    }
    

자바

    public void onReceive(Context context, Intent intent) {
        ...
        else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {

            // Request available peers from the wifi p2p manager. This is an
            // asynchronous call and the calling activity is notified with a
            // callback on PeerListListener.onPeersAvailable()
            if (mManager != null) {
                mManager.requestPeers(channel, peerListListener);
            }
            Log.d(WiFiDirectActivity.TAG, "P2P peers changed");
        }...
    }
    

이제 WIFI_P2P_PEERS_CHANGED_ACTION 작업 인텐트가 포함된 인텐트가 업데이트된 동종 기기 목록 요청을 트리거합니다.

동종 기기에 연결

동종 기기에 연결하려면 새 WifiP2pConfig 개체를 만들고 연결할 기기를 나타내는 WifiP2pDevice에서 데이터를 개체에 복사하세요. 그런 다음 connect() 메서드를 호출하세요.

Kotlin

    override fun connect() {
        // Picking the first device found on the network.
        val device = peers[0]

        val config = WifiP2pConfig().apply {
            deviceAddress = device.deviceAddress
            wps.setup = WpsInfo.PBC
        }

        manager.connect(channel, config, object : WifiP2pManager.ActionListener {

            override fun onSuccess() {
                // WiFiDirectBroadcastReceiver notifies us. Ignore for now.
            }

            override fun onFailure(reason: Int) {
                Toast.makeText(
                        this@WiFiDirectActivity,
                        "Connect failed. Retry.",
                        Toast.LENGTH_SHORT
                ).show()
            }
        })
    }
    

자바

    @Override
    public void connect() {
        // Picking the first device found on the network.
        WifiP2pDevice device = peers.get(0);

        WifiP2pConfig config = new WifiP2pConfig();
        config.deviceAddress = device.deviceAddress;
        config.wps.setup = WpsInfo.PBC;

        manager.connect(channel, config, new ActionListener() {

            @Override
            public void onSuccess() {
                // WiFiDirectBroadcastReceiver notifies us. Ignore for now.
            }

            @Override
            public void onFailure(int reason) {
                Toast.makeText(WiFiDirectActivity.this, "Connect failed. Retry.",
                        Toast.LENGTH_SHORT).show();
            }
        });
    }
    

그룹의 각 기기가 Wi-Fi Direct를 지원하는 경우 연결 시 그룹의 비밀번호를 명시적으로 요청할 필요가 없습니다. 하지만 Wi-Fi Direct를 지원하지 않는 기기가 그룹에 참여할 수 있도록 허용하려면 다음 코드 스니펫에 표시된 대로 requestGroupInfo()를 호출하여 이 비밀번호를 가져와야 합니다.

Kotlin

    manager.requestGroupInfo(channel) { group ->
        val groupPassword = group.passphrase
    }
    

자바

    manager.requestGroupInfo(channel, new GroupInfoListener() {
      @Override
      public void onGroupInfoAvailable(WifiP2pGroup group) {
          String groupPassword = group.getPassphrase();
      }
    });
    

WifiP2pManager.ActionListener(connect() 메서드에 구현됨)만이 시작에 성공하거나 실패했을 때를 알려줍니다. 연결 상태의 변경사항을 수신 대기하려면 WifiP2pManager.ConnectionInfoListener 인터페이스를 구현하세요. 연결 상태가 변경되면 onConnectionInfoAvailable() 콜백이 알려줍니다. 여러 기기가 단일 기기에 연결되는 경우(예: 플레이어가 세 명 이상인 게임, 채팅 앱) 한 기기가 '그룹 소유자'로 지정됩니다. 그룹 만들기 섹션의 단계에 따라 특정 기기를 네트워크의 그룹 소유자로 지정할 수 있습니다.

Kotlin

    private val connectionListener = WifiP2pManager.ConnectionInfoListener { info ->

        // InetAddress from WifiP2pInfo struct.
        val groupOwnerAddress: String = info.groupOwnerAddress.hostAddress

        // After the group negotiation, we can determine the group owner
        // (server).
        if (info.groupFormed && info.isGroupOwner) {
            // Do whatever tasks are specific to the group owner.
            // One common case is creating a group owner thread and accepting
            // incoming connections.
        } else if (info.groupFormed) {
            // The other device acts as the peer (client). In this case,
            // you'll want to create a peer thread that connects
            // to the group owner.
        }
    }
    

자바

    @Override
    public void onConnectionInfoAvailable(final WifiP2pInfo info) {

        // InetAddress from WifiP2pInfo struct.
        InetAddress groupOwnerAddress = info.groupOwnerAddress.getHostAddress();

        // After the group negotiation, we can determine the group owner
        // (server).
        if (info.groupFormed && info.isGroupOwner) {
            // Do whatever tasks are specific to the group owner.
            // One common case is creating a group owner thread and accepting
            // incoming connections.
        } else if (info.groupFormed) {
            // The other device acts as the peer (client). In this case,
            // you'll want to create a peer thread that connects
            // to the group owner.
        }
    }
    

이제 broadcast receiver의 onReceive() 메서드로 돌아가 WIFI_P2P_CONNECTION_CHANGED_ACTION 인텐트를 수신 대기하는 섹션을 수정하세요. 인텐트가 수신되면 requestConnectionInfo()를 호출하세요. 이 호출은 비동기 호출하므로 매개변수로 제공하는 연결 정보 리스너에서 결과를 수신합니다.

Kotlin

    when (intent.action) {
        ...
        WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> {

            // Connection state changed! We should probably do something about
            // that.

            mManager?.let { manager ->

                val networkInfo: NetworkInfo? = intent
                        .getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO) as NetworkInfo

                if (networkInfo?.isConnected == true) {

                    // We are connected with the other device, request connection
                    // info to find group owner IP

                    manager.requestConnectionInfo(channel, connectionListener)
                }
            }
        }
        ...
    }
    

자바

        ...
        } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {

            if (manager == null) {
                return;
            }

            NetworkInfo networkInfo = (NetworkInfo) intent
                    .getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);

            if (networkInfo.isConnected()) {

                // We are connected with the other device, request connection
                // info to find group owner IP

                manager.requestConnectionInfo(channel, connectionListener);
            }
            ...
    

그룹 만들기

앱을 실행하는 기기가 기존 기기, 즉 Wi-Fi Direct를 지원하지 않는 기기가 포함된 네트워크의 그룹 소유자 역할을 하도록 하려면 동종 기기에 연결 섹션과 같은 단계를 따르세요. 단 WifiP2pManager.ActionListenerconnect() 대신 createGroup()을 사용하여 만들어야 합니다. WifiP2pManager.ActionListener 내 콜백 처리는 다음 코드 스니펫에 표시된 것과 동일합니다.

Kotlin

    manager.createGroup(channel, object : WifiP2pManager.ActionListener {
        override fun onSuccess() {
            // Device is ready to accept incoming connections from peers.
        }

        override fun onFailure(reason: Int) {
            Toast.makeText(
                    this@WiFiDirectActivity,
                    "P2P group creation failed. Retry.",
                    Toast.LENGTH_SHORT
            ).show()
        }
    })
    

자바

    manager.createGroup(channel, new WifiP2pManager.ActionListener() {
        @Override
        public void onSuccess() {
            // Device is ready to accept incoming connections from peers.
        }

        @Override
        public void onFailure(int reason) {
            Toast.makeText(WiFiDirectActivity.this, "P2P group creation failed. Retry.",
                    Toast.LENGTH_SHORT).show();
        }
    });
    

참고: 네트워크의 모든 기기가 Wi-Fi Direct를 지원하는 경우 메서드가 그룹을 만들고 자동으로 그룹 소유자를 선택하므로 각 기기에서 connect() 메서드를 사용할 수 있습니다.

그룹을 만든 후 requestGroupInfo()를 호출하여 기기 이름, 연결 상태 등 네트워크의 동종 기기에 관한 세부정보를 가져올 수 있습니다.